【发布时间】:2016-12-11 09:21:50
【问题描述】:
我目前正在学习汇编的基础知识,在查看 GCC(6.1.1) 生成的指令时遇到了一些奇怪的事情。
这里是来源:
#include <stdio.h>
int foo(int x, int y){
return x*y;
}
int main(){
int a = 5;
int b = foo(a, 0xF00D);
printf("0x%X\n", b);
return 0;
}
用于编译的命令:gcc -m32 -g test.c -o test
在检查 GDB 中的函数时,我得到以下信息:
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x080483f7 <+0>: lea ecx,[esp+0x4]
0x080483fb <+4>: and esp,0xfffffff0
0x080483fe <+7>: push DWORD PTR [ecx-0x4]
0x08048401 <+10>: push ebp
0x08048402 <+11>: mov ebp,esp
0x08048404 <+13>: push ecx
0x08048405 <+14>: sub esp,0x14
0x08048408 <+17>: mov DWORD PTR [ebp-0xc],0x5
0x0804840f <+24>: push 0xf00d
0x08048414 <+29>: push DWORD PTR [ebp-0xc]
0x08048417 <+32>: call 0x80483eb <foo>
0x0804841c <+37>: add esp,0x8
0x0804841f <+40>: mov DWORD PTR [ebp-0x10],eax
0x08048422 <+43>: sub esp,0x8
0x08048425 <+46>: push DWORD PTR [ebp-0x10]
0x08048428 <+49>: push 0x80484d0
0x0804842d <+54>: call 0x80482c0 <printf@plt>
0x08048432 <+59>: add esp,0x10
0x08048435 <+62>: mov eax,0x0
0x0804843a <+67>: mov ecx,DWORD PTR [ebp-0x4]
0x0804843d <+70>: leave
0x0804843e <+71>: lea esp,[ecx-0x4]
0x08048441 <+74>: ret
End of assembler dump.
(gdb) disas foo
Dump of assembler code for function foo:
0x080483eb <+0>: push ebp
0x080483ec <+1>: mov ebp,esp
0x080483ee <+3>: mov eax,DWORD PTR [ebp+0x8]
0x080483f1 <+6>: imul eax,DWORD PTR [ebp+0xc]
0x080483f5 <+10>: pop ebp
0x080483f6 <+11>: ret
End of assembler dump.
让我困惑的部分是它试图用堆栈做什么。 据我了解,这就是它的作用:
- 它引用了堆栈中高 4 个字节的某个内存地址,据我所知,这应该是传递给 main 的变量,因为
esp当前指向内存中的返回地址。 - 出于性能原因,它将堆栈对齐到 0 边界。
- 它推送到新的堆栈区域
ecx+4,这应该转换为将我们假设要返回的地址推送到堆栈上。 - 它将旧的帧指针压入堆栈并设置新的。
- 它将
ecx(仍然指向应该是main的参数)推入堆栈。
然后程序做它应该做的并开始返回的过程:
- 它通过使用
-0x4上的-0x4偏移来恢复ecx,这应该访问第一个局部变量。 - 它执行离开指令,实际上只是将
esp设置为ebp,然后从堆栈中弹出ebp。
那么现在堆栈上的下一件事是返回地址,esp 和 ebp 寄存器应该返回到它们需要返回的位置,对吗?
显然不是因为它接下来要做的是用ecx-0x4 加载esp,因为ecx 仍然指向传递给main 的变量,所以应该把它放在堆栈上的返回地址的地址。
这工作得很好,但提出了一个问题:为什么在步骤 3 中将返回地址放入堆栈,因为它在实际从函数返回之前将堆栈返回到最后的原始位置?
【问题讨论】:
-
您应该启用优化并使用
gcc -m32 -O -Wall -S -fverbose-asm test.c进行编译,然后查看生成的test.s内部 -
这是它生成的 (pastebin.com/raw/1ZdhPLf6)。据我所知,它仍然有额外的退货地址。
-
阅读更多关于x86 calling conventions和ABI的信息。他们可能会决定通话的方式。
-
这可能只是为了让调试器可以将堆栈追溯到
main。 -
@PeterCordes 您不能通过跟踪保存的 EBP 值链可靠地展开堆栈,因为它不是 ABI 的一部分,因此它只是有用的堆栈跟踪。因此,我不认为这样做是出于 ABI 的原因,只是为了调试。
标签: assembly gcc x86 memory-alignment stack-memory