这两个函数序言指令序列有什么区别?

逆向工程 部件 x86 C 海湾合作委员会
2021-06-22 10:10:13

考试开始了32-bit x86我编译了代码gcc 4.2,优化级别o2我把C代码编译成二进制,然后objdump用来反汇编。

以下是用于函数序言的两个指令序列:

0804a6f0 <quotearg_n>:
804a6f0:       8b 44 24 04             mov    0x4(%esp),%eax
804a6f4:       b9 ff ff ff ff          mov    $0xffffffff,%ecx
804a6f9:       8b 54 24 08             mov    0x8(%esp),%edx
804a6fd:       c7 44 24 04 40 e1 04    movl   $0x804e140,0x4(%esp)
804a704:       08 
804a705:       e9 c6 fa ff ff          jmp    804a1d0 <quotearg_n_options>
804a70a:       8d b6 00 00 00 00       lea    0x0(%esi),%esi


0804a730 <quotearg>:
804a730:       83 ec 1c                sub    $0x1c,%esp
804a733:       8b 44 24 20             mov    0x20(%esp),%eax
804a737:       c7 04 24 00 00 00 00    movl   $0x0,(%esp)
804a73e:       89 44 24 04             mov    %eax,0x4(%esp)
804a742:       e8 a9 ff ff ff          call   804a6f0 <quotearg_n>
804a747:       83 c4 1c                add    $0x1c,%esp
804a74a:       c3                      ret
804a74b:       90                      nop
804a74c:       8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi

请注意,在 function 中quotearg, register在用于访问堆栈并获取一些参数之前esp减少了0x1c实际上,根据我的经验,我认为subthenaccess模式对于使用O2.

但是需要注意的是,在 function 中quotearg_nesp直接添加了register0x4来访问堆栈。(我认为address处指令的意思0x804a6f0是把调用点的返回地址注册eax,对不对..?)根据我的观察,第一个函数使用的模式很少见,大约5%的gcc编译中等大小带有O2.

所以这是我的问题:

为什么编译器会以类似的方式生成函数序言指令quoterag_n从地址开始的前三个指令的确切含义是0x804a6f0什么?

为什么编译器不总是按照subthenaccess模式生成函数序言指令(例如quoterag

我清楚吗?多谢

1个回答

似乎函数是这样定义的:

int quotearg_n(int a, int b) {
    return quotearg_n_options(a, b, -1, "some_string");
}

int quotearg(int a) {
    return quotearg_n(0, a);
}

ints 也可能是指针,无法从您的片段中看出这一点,而“某个字符串”可能是指向预初始化结构的指针)

这些函数具有正常的ABI,这意味着它们传递堆栈上的所有参数,而quotearg_n_options在寄存器中接收其参数的前三个,并且仅在堆栈中接收最后一个。这可能是由于函数原型中的修饰符,也可能是由于函数被声明static,因此编译器知道不能从当前源文件外部调用它,因此可以安全地更改它的ABI.

现在,从quoteargquotearg_n,堆栈上的参数数量增加,因此编译器需要为它们腾出空间,初始化它们,调用子例程,然后返回。

quotearg_nto quotearg_n_options,参数的数量再次增加(从 2 到 4),但由于三个参数在寄存器eax, edxand中传递ecx,因此只有字符串必须在堆栈上。这意味着堆栈上的参数数量减少,因此调用需要更少的堆栈空间,这允许编译器回收当前堆栈。所以编译器所做的是一种叫做尾调用消除的东西:它不是调用函数,然后返回,而是按照被调用者期望的方式设置堆栈,然后跳转到它而不是使用调用。那个函数( quotearg_n_options)会执行,当它返回时,它会直接返回调用的函数quotearg_n,原来的。

所以回答你的问题:编译器使用尾调用消除优化,只有当被调用函数堆栈上的参数数量小于调用者堆栈上的参数数量时,它才能这样做