什么开关/选项强制CL(Microsoft C / C ++编译器)生成“碎片”程序?

时间:2015-02-24 11:06:10

标签: visual-c++ compiler-optimization debug-symbols disassembly branch-prediction

在大多数情况下,编译过程是一堆处理器指令,占用代码段中连续字节范围。它当然可能包含条件和无条件跳转以及非线性执行流程,但是查看反汇编列表,您可以明确地说明过程的起点(即入口点)以及过程结束的位置。

然而,有时CL会将程序分成几部分并将这些部分混合在一起,这样你就可以在 proc_a 的前半部分和的后半部分之间获得 proc_b proc_a

问题是:什么命令行开关使编译器生成如下所述的代码。

我在调试器/反汇编程序中分析了一个可执行文件,并注意到大量具有碎片体的函数。我有二进制本身,调试符号,我知道它是使用CL编译的,但我没有源码,makefile,因此我不知道使用了什么命令行选项来编译它。

让我向您展示一个小例子(它只是一个演示代码,而不是现实生活中的代码)。

假设你有以下用C ++编写的函数(boo是一个类,方法是virtual):

int foo(boo *x)
{
    if(x->ready == 0)
    {
        return 0;
    }
    else
    {
        x->func_a(x->bzz);
        x->func_b(x->kee);
        return x->func_c();
    }
}

现在使用一些神秘的命令行选项运行,CL决定接受一小部分指令(表示return 0;条件的分支)并将其移向远离边界的代码段的末尾foo程序的基本部分。此外,这一小部分指令在调试符号表中有自己的条目,其名称由被拆分的过程的名称,下划线字符和表示(在大多数情况下,但并非在所有情况下)的十进制数字组成。跳转指令相对于过程入口点(例如foo_13)的跳转目标(偏移本身)的偏移量。

因此, CL 编译如下:

foo:
  push ebp
  mov ebp, esp
  push edi
  mov edi, [esp+8]
  cmp [edi+4], 0
  je foo_X   <----- jump down below to the isolated (!) piece of 'foo'
  push esi
  mov esi, [edi]
  mov ecx, edi
  push [edi+8]
  call [esi]
  push [edi+12d]
  mov ecx, edi
  call [esi+4]
  mov ecx, edi
  call [esi+8]
  pop esi
  pop edi <---- return from small piece 'foo_X' leads here
  pop ebp
  retn 4

OtherFunc1:
  <code for other function>
  <code for other function>   
  <code for other function>
OtherFunc2:
  <code for other function>
  <code for other function>   
  <code for other function>

  <many many code not related to 'foo' at all>

foo_X:
  xor eax, eax
  jmp <address of 'pop edi' within main part of 'foo'>

foo_X (X代表一些十进制数,如上所述)是一个小的双指令块,表示if语句的真分支。

在我的情况下,有很多这样的小块。它们中的大多数(但不是全部)是双指令的(通过xor reg, reg重置一些寄存器并跳回到函数的主要部分,或者将EAX归零并执行RETN)。它们在调试符号表中都有自己的名称。如果我们有 foo bar baaz 等功能,还有 foo_7 bar_22 bar_43 baaz_19 。这些小块中的大多数组合在一起并在代码段中彼此靠近,但远离它们的对应物(foo,bar,baaz),因此跳转到这些块遍布代码部分。


它可能与基于分支预测的优化有关:编译器移动它认为不太可能发生在远离基本执行流路径的执行分支。但是,我在1998年编译的二进制文件中观察到了这些技巧,所以很明显,使用了来自MSVC6(甚至是MSVC5!)的CL.EXE,并且没有办法给出那些旧版本的优化器的分支预测提示。 CL。是的,现代版本的CL支持profile-guided optimization,但我们正在讨论1998年编译的代码。

我正在寻找的选项之一是 / Gy - 所谓的function-level linking的选项。此选项指示编译器将每个单独的函数包装到单独的COMDAT中,以便在链接时,函数可以按任何所需的顺序重新排序,并且可以排除其中的一些。但是,据我所知,它将整个函数包装到单个COMDAT中,但对于我的情况,它需要将单个函数的单独片段放入单独的COMDAT中,以(1)允许链接器将函数片段放置在远处彼此之间以及(2)允许这些小片段在调试符号表中有自己的名称。

再一次,我的问题是CL / LINK的命令行选项/开关控制了这种行为。

0 个答案:

没有答案