GCC和Borland拆解C代码的差异?

时间:2010-12-04 20:15:43

标签: c assembly disassembly

最近我对拆解C代码(非常简单的C代码)感兴趣,并且遵循使用Borland C ++ Compiler v 5.5(编译C代码就好)的教程,一切正常。然后我决定尝试自己的c代码并在Dev C ++(使用gcc)中编译它们。在IDA Pro中打开它后,我感到惊讶,与Borland相比,gcc的asm真的与众不同。我预计会有一些差异,但C代码非常简单,所以只是gcc没有优化那么多,还是他们使用不同的默认编译器设置?

C代码

int main(int argc, char **argv)
{
   int a;
   a = 1;
}

Borland ASM

.text:00401150 ; int __cdecl main(int argc,const char **argv,const char *envp)
.text:00401150 _main           proc near               ; DATA XREF: .data:004090D0
.text:00401150
.text:00401150 argc            = dword ptr  8
.text:00401150 argv            = dword ptr  0Ch
.text:00401150 envp            = dword ptr  10h
.text:00401150
.text:00401150                 push    ebp
.text:00401151                 mov     ebp, esp
.text:00401153                 pop     ebp
.text:00401154                 retn
.text:00401154 _main           endp

GCC ASM(更新的BELLOW)

.text:00401220 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:00401220
.text:00401220 ; Attributes: bp-based frame
.text:00401220
.text:00401220                 public start
.text:00401220 start           proc near
.text:00401220
.text:00401220 var_14          = dword ptr -14h
.text:00401220 var_8           = dword ptr -8
.text:00401220
.text:00401220                 push    ebp
.text:00401221                 mov     ebp, esp
.text:00401223                 sub     esp, 8
.text:00401226                 mov     [esp+8+var_8], 1
.text:0040122D                 call    ds:__set_app_type
.text:00401233                 call    sub_401100
.text:00401238                 nop
.text:00401239                 lea     esi, [esi+0]
.text:00401240                 push    ebp
.text:00401241                 mov     ebp, esp
.text:00401243                 sub     esp, 8
.text:00401246                 mov     [esp+14h+var_14], 2
.text:0040124D                 call    ds:__set_app_type
.text:00401253                 call    sub_401100
.text:00401258                 nop
.text:00401259                 lea     esi, [esi+0]
.text:00401259 start           endp

GCC更新 按照JimR的建议,我去看看sub_401100是什么,然后我跟着那个代码到另一个,这似乎是代码(我在这个假设中是否正确,如果sowhy GCC在主函数中有它的所有代码? ):

.text:00401100 sub_401100      proc near               ; CODE XREF: .text:004010F1j
.text:00401100                                         ; start+13p ...
.text:00401100
.text:00401100 var_28          = dword ptr -28h
.text:00401100 var_24          = dword ptr -24h
.text:00401100 var_20          = dword ptr -20h
.text:00401100 var_1C          = dword ptr -1Ch
.text:00401100 var_18          = dword ptr -18h
.text:00401100 var_C           = dword ptr -0Ch
.text:00401100 var_8           = dword ptr -8
.text:00401100
.text:00401100                 push    ebp
.text:00401101                 mov     ebp, esp
.text:00401103                 push    ebx
.text:00401104                 sub     esp, 24h        ; lpTopLevelExceptionFilter
.text:00401107                 lea     ebx, [ebp+var_8]
.text:0040110A                 mov     [esp+28h+var_28], offset sub_401000
.text:00401111                 call    SetUnhandledExceptionFilter
.text:00401116                 sub     esp, 4          ; uExitCode
.text:00401119                 call    sub_4012E0
.text:0040111E                 mov     [ebp+var_8], 0
.text:00401125                 mov     eax, offset dword_404000
.text:0040112A                 lea     edx, [ebp+var_C]
.text:0040112D                 mov     [esp+28h+var_18], ebx
.text:00401131                 mov     ecx, dword_402000
.text:00401137                 mov     [esp+28h+var_24], eax
.text:0040113B                 mov     [esp+28h+var_20], edx
.text:0040113F                 mov     [esp+28h+var_1C], ecx
.text:00401143                 mov     [esp+28h+var_28], offset dword_404004
.text:0040114A                 call    __getmainargs
.text:0040114F                 mov     eax, ds:dword_404010
.text:00401154                 test    eax, eax
.text:00401156                 jz      short loc_4011B0
.text:00401158                 mov     dword_402010, eax
.text:0040115D                 mov     edx, ds:_iob
.text:00401163                 test    edx, edx
.text:00401165                 jnz     loc_4011F6

.text:004012E0 sub_4012E0      proc near               ; CODE XREF: sub_401000+C6p
.text:004012E0                                         ; sub_401100+19p
.text:004012E0                 push    ebp
.text:004012E1                 mov     ebp, esp
.text:004012E3                 fninit
.text:004012E5                 pop     ebp
.text:004012E6                 retn
.text:004012E6 sub_4012E0      endp

6 个答案:

答案 0 :(得分:3)

最有可能发生的事情是Borland在使用运行时库中存在的代码初始化所有代码后,从其启动代码调用main。

gcc代码看起来不像我的主要内容,但是像生成的代码一样调用main。在sub_401100处反汇编码,看看它是否与主过程相似。

答案 1 :(得分:3)

编译器输出预计会有所不同,有时对于同一来源会有很大的不同。与丰田和本田不同的方式相同。四个轮子和一些座椅确定,但是当你看细节时,它们会比同样的更多。

同样,具有不同编译器选项的相同编译器可以并且通常将为相同的源代码产生显着不同的输出。即使是看似简单的程序。

对于你的简单程序,它实际上什么都不做(代码不影响输入,输出,也不影响函数之外的任何东西),一个好的优化编译器只会导致main:返回一些随机数,因为你没有指定返回值。实际上它应该发出警告或错误。这是我比较编译器输出时所遇到的最大问题,它使得一些东西变得足够简单,可以看到它们正在做什么但复杂到足以使编译器做的不仅仅是预先计算答案并返回它。

在x86的情况下,我假设你在这里谈论的是,这些天被微编码,对于良好的代码与糟糕的代码,每个处理器系列他们改变了胆量以及过去的内容确实没有答案。快速是慢的,现在快速的旧处理器速度很慢。因此对于像gcc这样的编译器来说,随着新内核的不断发展,优化既可以是所有x86的通用优化,也可以是特定系列的特定功能(尽管进行了最大程度的优化,但仍会产生不同的代码)。

由于您对拆解的新兴趣,您将继续看到相似之处和不同之处,并找出可以编译相同代码的不同方式。即使对于琐碎的计划,也存在差异。我鼓励你尝试尽可能多的编译器。即使在gcc系列中,2.x,3.x,4.x以及构建它的不同方法也会产生不同的代码,这些代码可能被认为是同一个编译器。

良好与糟糕的输出是在旁观者的眼中。使用调试器的人会希望他们的代码可以操作并且他们的变量是可观察的(以书面的代码顺序)。这会产生非常大,笨重和慢的代码(特别是对于x86)。当你编译发布时,你最终会得到一个完全不同的程序,到目前为止你已经花了零时间进行调试。同样优化性能,您可能会冒险编译器优化您希望它做的事情(上面的例子,没有变量将被分配,没有代码可以逐步完成,即使是经过小幅优化)。或者更糟糕的是,你暴露了编译器中的错误,你的程序根本不起作用(这就是为什么-O3不鼓励gcc)。那和/或你发现C标准中的大量地方,其解释是实施定义的。

未经优化的代码更易于编译,因为它更加明显。在您的示例的情况下,期望是在堆栈上分配变量,设置某种堆栈指针排列,立即1最终写入该位置,堆栈清理并且函数返回。更难以让编译器出错并且更有可能使您的程序按预期工作。检测和删除死代码是优化和业务 这是风险的地方。通常风险值得奖励。但这取决于用户,美丽是在旁观者的眼中。

底线,简短回答。预计会有差异(甚至是戏剧性的差异)。默认编译选项因编译器而异。尝试编译/优化选项和不同的编译器,并继续反汇编您的程序,以便更好地了解您使用的语言和编译器。到目前为止,你走在正确的轨道上。对于borland输出,它检测到您的程序什么都不做,没有使用输入变量,没有使用返回变量,也没有与局部变量相关,也没有使用全局变量或函数资源的其他外部变量。整数a和立即赋值都是死代码,一个好的优化器基本上会删除/忽略这两行代码。因此,设置堆栈帧然后清理它不需要做,然后返回它是困扰的。 gcc看起来正在设置一个异常处理程序,即使它不需要,也可以开始优化或使用除main()之外的函数名称,并且你会看到不同的结果。

答案 2 :(得分:2)

首先,确保至少为gcc启用了-O2优化标志,否则根本就没有优化。

通过这个小例子,你没有真正测试优化,你看到程序初始化是如何工作的,例如gcc调用__set_app_type来通知窗口应用程序类型以及其他初始化。例如sub_401100为运行时注册atexit处理程序。 Borland可能事先调用运行时初始化,而gcc在main()中执行。

答案 3 :(得分:0)

看起来Borland编译器正在认识到你实际上从未对a做任何事情而只是给你一个空主函数的等效程序集。

答案 4 :(得分:0)

以下是我在Gdb中从MinGW的gcc 4.5.1获得的main()的反汇编(最后我添加了return 0,因此GCC不会抱怨):

首先,当使用-O3优化编译程序时:

(gdb) set disassembly-flavor intel
(gdb) disassemble
Dump of assembler code for function main:
   0x00401350 <+0>:     push   ebp
   0x00401351 <+1>:     mov    ebp,esp
   0x00401353 <+3>:     and    esp,0xfffffff0
   0x00401356 <+6>:     call   0x4018aa <__main>
=> 0x0040135b <+11>:    xor    eax,eax
   0x0040135d <+13>:    mov    esp,ebp
   0x0040135f <+15>:    pop    ebp
   0x00401360 <+16>:    ret
End of assembler dump.

没有优化:

(gdb) set disassembly-flavor intel
(gdb) disassemble
Dump of assembler code for function main:
   0x00401350 <+0>:     push   ebp
   0x00401351 <+1>:     mov    ebp,esp
   0x00401353 <+3>:     and    esp,0xfffffff0
   0x00401356 <+6>:     sub    esp,0x10
   0x00401359 <+9>:     call   0x4018aa <__main>
=> 0x0040135e <+14>:    mov    DWORD PTR [esp+0xc],0x1
   0x00401366 <+22>:    mov    eax,0x0
   0x0040136b <+27>:    leave
   0x0040136c <+28>:    ret
End of assembler dump.

这些比Borland的例子稍微复杂一点,但并不过分。

注意,对0x4018aa的调用是对库/编译器提供的函数的调用,以构造C ++对象。以下是一些GCC工具链文档的片段:

  

对构造函数的实际调用是由一个名为__main的子程序执行的,该子程序在main主体的开头被调用(自动)(提供的main是用GNU CC编译的)。即使在编译C代码时,调用__main也是必要的,以允许将C和C ++目标代码链接在一起。 (如果使用'-nostdlib',则会得到一个未解析的__main引用,因为它是在标准GCC库中定义的。在编译器命令行的末尾包含'-lgcc'以解析此引用。)

我不确定IDA Pro在您的示例中显示的是什么。 IDA Pro将其显示的内容标记为start而不是main,因此我猜测JimR's answer是正确的 - 它可能是运行时的初始化(可能是.exe标头中描述的入口点 - 它不是main(),而是运行时初始化入口点。)

IDA Pro是否了解gcc的调试符号?您是否使用-g选项进行编译以生成调试符号?

答案 5 :(得分:0)

这里的差异很少是在编译代码中,而是在反汇编程序中向您显示的内容。 您可能认为 main 是程序中唯一的功能,但事实并非如此。事实上你的程序是这样的:

void start()
{
    ... some initialization code here
    int result = main();
    ... some deinitialization code here
    ExitProcess(result);
}

IDA Pro了解Borland的工作原理,因此它可以直接导航到您的 main ,但它不知道gcc是如何工作的,因此它会向您显示程序的真正切入点。您可以在Borland ASM中看到从其他一些函数调用 main 。在GCC ASM中,您可以通过所有 sub_40xxx 来查找主要