在大多数情况下,编译过程是一堆处理器指令,占用代码段中连续字节范围。它当然可能包含条件和无条件跳转以及非线性执行流程,但是查看反汇编列表,您可以明确地说明过程的起点(即入口点)以及过程结束的位置。
然而,有时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的命令行选项/开关控制了这种行为。