本文描述的是在intel X86_64体系架构中,C语言函数在执行过程中的内存栈结构情况。其它体系会有所不同,仅供参考,实验在Ubuntu14.04 X64中使用,考虑到寄存器的熟悉程度,编译的可执行程序是32位的。
源码如下:
#include<stdio.h>
void func(int a, int b)
{
int sec[5];
sec[2] = a;
return;
}
int main()
{
func(0xFF, 0x02);
return 0;
}
编译源码 gcc -win32 func.c -o func
执行gdb调用可执行程序 gdb func
使用gdb反汇编func和main函数
func:
(gdb) disas func
Dump of assembler code for function func:
0x080483ed <+0>: push %ebp
0x080483ee <+1>: mov %esp,%ebp
0x080483f0 <+3>: sub $0x20,%esp
0x080483f3 <+6>: mov 0x8(%ebp),%eax
0x080483f6 <+9>: mov %eax,-0xc(%ebp)
0x080483f9 <+12>: nop
0x080483fa <+13>: leave
0x080483fb <+14>: ret
End of assembler dump.
main:
(gdb) disas main
Dump of assembler code for function main:
0x080483fc <+0>: push %ebp
0x080483fd <+1>: mov %esp,%ebp
0x080483ff <+3>: sub $0x8,%esp
0x08048402 <+6>: movl $0x2,0x4(%esp)
0x0804840a <+14>: movl $0xff,(%esp)
0x08048411 <+21>: call 0x80483ed <func>
0x08048416 <+26>: mov $0x0,%eax
0x0804841b <+31>: leave
0x0804841c <+32>: ret
End of assembler dump.
在函数func添加断点 (gdb)b func,使用gdb执行程序(gdb) r,查看此刻寄存器的值。
Breakpoint 1, 0x080483f3 in func ()
(gdb) i r
eax 0x1 1
ecx 0xd14b7383 -783584381
edx 0xffffd104 -12028
ebx 0xf7fbf000 -134483968
esp 0xffffd0a8 0xffffd0a8
ebp 0xffffd0c8 0xffffd0c8
esi 0x0 0
edi 0x0 0
eip 0x80483f3 0x80483f3 <func+6>
eflags 0x282 [ SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
因为函数栈地址是逐渐减小的,此时查看ebp地址8个32位整数值。
(gdb) x /10x 0xffffd0c8
0xffffd0c8: 0xffffd0d8 0x08048416 0x000000ff 0x00000002
0xffffd0d8: 0x00000000 0xf7e31a63 0x00000001 0xffffd174
从ebp所存地址保存的数据可以看到:
1、参数1 0x000000ff在offset 2上,参数2 0x00000002在offset 3上,由此可见函数的参数是从右向左入栈,所以如果函数参数中有表达式时,也会先执行右边的参数;
2、offset 1 存放的值是0x08048416,可以从main的反汇编代码中看见,该值为func函数返回要执行指令的地址,即offset 1存放的是函数返回地址。
3、offset 0 存放的值是0xffffd0d8,该值在文中没有显示可查处,请读者可以自行测试,可以在执行main函数时查看寄存器ebp的值,即ebp存放值指向地址对应的值是主调函数的ebp。
4、从操作
0x080483f3 <+6>: mov 0x8(%ebp),%eax
0x080483f6 <+9>: mov %eax,-0xc(%ebp)
可以看出,局部数据sec是从地址ebp开始申请,所以局部变量是保存在函数栈中。
OK,综上所述,ebp向下位置存放函数返回地址,再向下是函数使用的参数值,而ebp存放值是主调函数的ebp,可以得出下图的结果,请将func抽象为被调函数,main抽象为主调函数。