在大多数情况下,编译过程是一堆处理器指令,它们在代码段中占据连续的字节范围。它当然可能包含有条件的和无条件的跳转以及非线性执行流程,但是查看反汇编列表,您可以明确地说出程序的开始(即入口点)在哪里以及程序在哪里结束。
但是,有时 CL 将过程拆分为多个部分并将这些部分混合在一起,以便您可以在proc_a的前半部分和proc_a的后半部分之间获得proc_b。
问题是:什么命令行开关使编译器生成如下所述的代码。
我正在我的调试器/反汇编器中分析一个可执行文件,并注意到大量具有碎片化主体的函数。我有二进制文件本身,它的调试符号,我知道它是使用 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 - 一个所谓的功能级链接选项。此选项指示编译器将每个单独的函数包装到单独的 COMDAT 中,以便在链接时可以以任何所需的顺序重新排序函数,并且可以排除其中的一些函数。但是,据我所见,它将整个函数包装到单个 COMDAT 中,但就我而言,它需要将单个函数的单独片段放入单独的 COMDAT 中,以 (1) 允许链接器将函数片段放置在很远的地方彼此和(2)允许这样的小片段在调试符号表中拥有自己的名称。
再一次,我的问题是 CL/LINK 的哪些命令行选项/开关控制这种行为。