【问题标题】:Detect if C++ binary is optimized检测 C++ 二进制文件是否已优化
【发布时间】:2022-01-16 17:47:55
【问题描述】:

是否有标志或其他可靠的方法来检测已编译的 C++ 二进制文件是否经过优化编译?

我可以接受特定于编译器的解决方案。


编辑:这是一个构建部署系统,它可能会意外部署未正确构建的二进制文件。不透水的解决方案不太可能,但如果可以在某些时候检测到这一点,它将节省一些痛苦(和金钱)。

编译器通常是 gcc,有时是 sun,如果有 MSVC 解决方案,为了社区的利益,我不想排除它。

【问题讨论】:

  • 你需要它做什么?您的问题可能有更好的解决方案
  • 你有来源吗?您是否允许重新编译以查看编译器的功能?还是只是二进制文件?

标签: c++ optimization


【解决方案1】:

最新版本的 GCC 有一个 way to report which flags were used to compile a binary(第三个要点)。

有一个相关的命令行开关(--fverbose-asm)“只将汇编输出文件中的信息记录为cmets,因此信息永远不会到达目标文件”。 --frecord-gcc-switches 开关“导致用于调用编译器的命令行被记录到正在创建的目标文件中。”

【讨论】:

  • 这看起来很有希望,Max。我将如何读回这些信息?
  • 不幸的是,信息是以平台相关的方式存储的。我将从 nabble.com/… 处的 objdump 命令行开始。
  • nabble 链接已失效。有人知道 objdump 需要什么标志吗?
  • 字符串 foo.so | grep fPIC
【解决方案2】:

可能的启发式解决方案

如果给我这个任务并证明这是一个合理的任务(见下文),我将对可执行反汇编中看到的模式执行“频率分析”。作为一名程序员,我一眼就能区分出优化和未优化(调试)的代码。我会尝试将决策过程正式化,使用 Visual Studio 和 x86 平台,在未优化的 exe 中看到的典型特征是:

  • 具有完整序言/尾声的函数(基于 ebp 的堆栈帧)
  • 大量 mov-s 从/到内存(所有变量都放在内存中)

这绝对不是 100 %,但如果 exe 更长,我希望结果相当可靠。

我假设对于包括 GCC 在内的其他 x86 平台,规则将是相似的,如果不一样的话,并且对于其他平台,可以找到类似的规则。

其他启发式方法(如检测运行时库)也可能起作用,具体取决于编译器设置。

任务听起来很傻

也就是说,我认为这样的任务“有味道”,在大多数情况下,完全避免它是明智的。如果您能提供此类任务背后的真正原因,很可能会找到一些明智的解决方案。

【讨论】:

    【解决方案3】:

    我们使用的技术只是创建一个符号,只有在构建调试时才会出现在每个库中:

    // included_by_everything.h
    #ifdef (_DEBUG)
    extern "C" __declspec( dllexport ) void WasBuiltInDebug() {}
    #else
    // nothing!
    #endif
    

    然后在运行时(或在 Perforce 触发器中)加载每个模块时,我们可以通过简单地查找该符号来询问二进制文件是否正在调试:

    bool IsDebugDLL( HMODULE DllHandle )
    {
      // this system call returns a nonzero address if it can 
      // find the symbol, and NULL otherwise:
      return GetProcAddress( DllHandle , "WasBuiltInDebug" );
    }
    

    【讨论】:

    • 这也是我正在考虑的,但它并没有直接解决这个问题。二进制文件可能会在发布时编译但未开启优化。
    • 反之,调试但有优化。
    【解决方案4】:

    在 Windows 上,您可以检查是否从二进制文件中删除了调试信息。如果您检查 COFF 标头的二进制图像,则在 COFF 标头的字节偏移 18 处有一个称为特性的 2 字节标志块。如果标志

     IMAGE_FILE_DEBUG_STRIPPED = 0x0200
    

    设置,然后从图像文件中删除调试信息。

    这可以通过从文件偏移量 0x52 读取一个简单的 2 字节并检查标志是否设置来完成。

    可以按如下方式检查标志

     short flag_block;
    
     FILE * p_image_file = fopen (...); 
    
     fseek(p_image_file,0x52,SEEK_SET);
    
     fread(&flag_block,2,1,p_image_file);
    
     if(flag_block & 0x0200 > 0)
        then debug info is stripped
    

    您可以找到来自 Microsoft 的 Windows PE format document,它更详细地描述了它,以便您计算要读取的字节偏移量等...

    【讨论】:

      【解决方案5】:

      您没有提到具体的平台,所以答案可能是肯定的。但对于大多数常见平台,答案是否定的。


      【讨论】:

        【解决方案6】:

        对于 Microsoft C++,如果可执行文件引用运行时 DLL 的调试版本,您可以假设优化已关闭。不过,这不是保证。

        【讨论】:

          【解决方案7】:

          根据我的经验,唯一可靠的方法是检查反汇编本身如果你不知道编译器。首先尝试使用您最喜欢的工具转储所有字符串,并在链接的库名称和导出的符号中寻找线索。如果做不到这一点,请寻找常见的指令序列,这些指令序列在堆栈帧序言和有序且易于理解的寄存器使用等优化后大多被丢弃。如果像 ollydbg 这样的工具对函数的开始和结束位置感到困惑,则很有可能在编译时启用了优化。

          检查反编译器的输出也可能会有所帮助。上次我检查时,它们并不比反汇编器好,但从那时起情况可能有所改善。

          【讨论】:

            【解决方案8】:

            如果您还没有这样做,我会感到惊讶,但是:

            如果您“信任”构建系统,请将用于构建的选项放入可以从正在运行的程序中提取的字符串中。 (即,来自 --version 或 about 对话框)

            这不会检测到由于构建系统损坏而导致未优化的目标文件进入构建的问题,但它可以让您轻松区分开发构建和发布构建。

            然后您的发布过程可以包括检查版本以确保它没有调试/未优化。

            【讨论】:

              【解决方案9】:

              我很确定没有办法。除非您对其中的函数有特定的了解,否则您可以检查代码序列是否是最优的。

              【讨论】:

                【解决方案10】:

                也许您可以使用不同的指标来确定您是否正在部署正确的二进制文件。例如

                • 调试信息:也许您的发布二进制文件的不同之处在于它们没有调试信息?您可以使用 elfdump 或 gdb 进行检查。
                • 已剥离:如果您的发布二进制文件已被剥离,您可以使用“文件”来检查这一点

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-10-04
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-03-06
                  • 1970-01-01
                  • 2010-10-28
                  相关资源
                  最近更新 更多