【问题标题】:GCC generated assemblyGCC 生成的程序集
【发布时间】:2017-04-29 15:14:22
【问题描述】:

为什么printf函数会导致prologue的变化?

C 代码_1:

#include <cstdio>

int main(){
  int a = 11;
  printf("%d", a);
}

GCC -m32 生成一个:

.LC0:
        .string "%d"
main:
        lea     ecx, [esp+4]           // What's purpose of this three
        and     esp, -16               // lines?
        push    DWORD PTR [ecx-4]      // 
        push    ebp
        mov     ebp, esp
        push    ecx
        sub     esp, 20                // why sub 20?
        mov     DWORD PTR [ebp-12], 11
        sub     esp, 8
        push    DWORD PTR [ebp-12]
        push    OFFSET FLAT:.LC0
        call    printf
        add     esp, 16
        mov     eax, 0
        mov     ecx, DWORD PTR [ebp-4]
        leave
        lea     esp, [ecx-4]
        ret

C 代码_2:

#include <cstdio>

int main(){
  int a = 11;
}

GCC -m32:

main:
        push    ebp
        mov     ebp, esp
        sub     esp, 16
        mov     DWORD PTR [ebp-4], 11
        mov     eax, 0
        leave
        ret

在第一个代码中添加前三行的目的是什么? 请解释一下第一个汇编代码,如果可以的话。

编辑:

64 位模式:

.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 11
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave
        ret

【问题讨论】:

  • 也许Godbolt-Tool 可以帮助您进行分析。
  • 我正在使用 Godbolt。这个程序集是在 Godbolt 上生成的 :)

标签: c++ c gcc assembly reverse-engineering


【解决方案1】:

洞察力是编译器在函数调用时保持堆栈对齐。
对齐为 16 字节。

lea     ecx, [esp+4]           ;Save original ESP to ECX (ESP+4 actually)
and     esp, -16               ;Align stack on 16 bytes (Lower esp)

push    DWORD PTR [ecx-4]      ;Push main return address (Stack at 16B + 4)
                               ;My guess is to aid debugging tools that expect the RA
                               ;to be at [ebp+04h]
push    ebp
mov     ebp, esp               ;Prolog (Stack at 16B+8)

push    ecx                    ;Save ECX (Original stack pointer) (Stack at 16B+12)

sub     esp, 20                ;Reserve 20 bytes (Stack at 16B+0, ALIGNED AGAIN)
                               ;4 for alignment + 1x16 for a variable (variable space is
                               ;allocated in multiple of 16)

mov     DWORD PTR [ebp-12], 11 ;a = 11

sub     esp, 8                 ;Stack at 16B+8 for later alignment
push    DWORD PTR [ebp-12]     ;a
push    OFFSET FLAT:.LC0       ;"%d"     (Stack at 16B)
call    printf
add     esp, 16                ;Remove args+pad from the stack (Stack at 16B)

mov     eax, 0                 ;Return 0

mov     ecx, DWORD PTR [ebp-4] ;Restore ECX without the need to add to esp
leave                          ;Restore EBP

lea     esp, [ecx-4]           ;Restore original ESP
ret

不知道为什么编译器将esp+4保存在ecx而不是espesp+4main的第一个参数的地址)。

【讨论】:

  • 如果前三行是保存信息所必需的,为什么 64 位代码使用相同的东西? (查看编辑后的帖子)
  • @J.Doe 同样是什么? 64 位代码有一个标准的序言。
  • 为什么 64 位模式不将原始 ESP 保存为 32 位模式?
  • @J.Doe 因为 64 位的 ABI 要求在函数开始时堆栈位于 16B+8,而对于 32 位,main 则不是这样。知道堆栈指针的初始状态后,编译器就可以省略and 指令,而使用rsp 上的推入和算术运算。 and 不可逆,因此必须保存原始状态。推/算术是可逆的,在序言中恢复原始状态。
  • @J.Doe 是的,这就是 ABI,应该有指向 PDF 文档的链接(或者您可以谷歌搜索关键字)。 16B+X 表示地址是 16 的倍数后的 X 个字节。例如 16B+8 可以是 8 (16*0+8) 或 24 (16*1+8) 或 40 (16*2+8) 并且以此类推。
猜你喜欢
  • 2010-11-20
  • 1970-01-01
  • 2014-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-04
  • 2018-05-13
相关资源
最近更新 更多