本文实现环境 ubuntu12.04 gcc4.4.5
测试程序采用《专业嵌入式软件开发-李云》书中的示例源码test.c如下:
#include <stdio.h>
void tail(int _param)
{
int local = 0;
int reg_esp,reg_ebp;
asm volatile(
"movl %%ebp,%0 \n"
"movl %%esp,%1 \n"
: "=r" (reg_ebp), "=r" (reg_esp)
);
printf("tail (): EBP = %x\n",reg_ebp);
printf("tail (): ESP = %x\n",reg_esp);
printf("tail (): (EBP) = %x\n",*(int *)reg_ebp);
printf("tail (): rerurn address = %x\n",*(((int *)reg_ebp + 1)));
printf("tail (): &local = %p\n",&local);
printf("tail (): ®_esp = %p\n",®_esp);
printf("tail (): ®_ebp = %p\n",®_ebp);
printf("tail (): &_param = %p\n",&_param);
}
int middle(int _p0, int _p1, int _p2)
{
int reg_esp,reg_ebp;
asm volatile(
"movl %%ebp,%0 \n"
"movl %%esp,%1 \n"
: "=r" (reg_ebp), "=r" (reg_esp)
);
tail(_p0);
printf("middle (): EBP = %x\n",reg_ebp);
printf("middle (): ESP = %x\n",reg_esp);
printf("middle (): (EBP) = %x\n",*(int *)reg_ebp);
printf("middle (): rerurn address = %x\n",*(((int *)reg_ebp + 1)));
printf("middle (): ®_esp = %p\n",®_esp);
printf("middle (): ®_ebp = %p\n",®_ebp);
printf("middle (): &_p0 = %p\n",&_p0);
printf("middle (): &_p1 = %p\n",&_p1);
printf("middle (): &_p2 = %p\n",&_p2);
return 1;
}
int main()
{
int reg_esp,reg_ebp;
int local = middle(1,2,3);
asm volatile(
"movl %%ebp,%0 \n"
"movl %%esp,%1 \n"
: "=r" (reg_ebp), "=r" (reg_esp)
);
printf("main (): EBP = %x\n",reg_ebp);
printf("main (): ESP = %x\n",reg_esp);
printf("main (): (EBP) = %x\n",*(int *)reg_ebp);
printf("main (): rerurn address = %x\n",*(((int *)reg_ebp + 1)));
printf("main (): ®_esp = %p\n",®_esp);
printf("main (): ®_ebp = %p\n",®_ebp);
printf("main (): &local = %p\n",&local);
return 0;
}
在文件所在目录下输入:gcc –o stackframe.exe test.c 编译,./stackframe.exe运行结果(不同机器可能不同):
tail (): EBP = bf8d4ea8
tail (): ESP = bf8d4e80
tail (): (EBP) = bf8d4ed8
tail (): rerurn address = 804849f
tail (): &local = 0xbf8d4e9c
tail (): ®_esp = 0xbf8d4e98
tail (): ®_ebp = 0xbf8d4e94
tail (): &_param = 0xbf8d4eb0
middle (): EBP = bf8d4ed8
middle (): ESP = bf8d4eb0
middle (): (EBP) = bf8d4f08
middle (): rerurn address = 8048586
middle (): ®_esp = 0xbf8d4ecc
middle (): ®_ebp = 0xbf8d4ec8
middle (): &_p0 = 0xbf8d4ee0
middle (): &_p1 = 0xbf8d4ee4
middle (): &_p2 = 0xbf8d4ee8
main (): EBP = bf8d4f08
main (): ESP = bf8d4ee0
main (): (EBP) = bf8d4f88
main (): rerurn address = 83bce7
main (): ®_esp = 0xbf8d4efc
main (): ®_ebp = 0xbf8d4ef8
main (): &local = 0xbf8d4ef4
通过以上打印出来的地址可以绘制出以下函数通用时的栈帧图:
上图左侧是内存地址,这幅图虽然简单但是却反映了函数调用时发生的所有事情。
main函数调用时堆栈变化:
1、返回地址入栈
2、上个函数(原函数)的ebp入栈
3、开辟main函数栈帧
4、mian函数局部变量入栈
5、调用middle函数的3个参数(p0、p1、p2)入栈
middle函数调用时堆栈变化:
1、返回地址入栈
2、上个函数(这里就是main函数)的ebp地址入栈
3、开辟middle函数栈帧
4、middle函数局部变量入栈
5、调用tail函数的1个参数(_param)入栈
tail函数一样就不说了
其他函数调用堆栈变化都是这样的过程,这里有几点还需要解释下。
函数栈帧是如何开辟的。我们需要查看一下原程序的汇编代码。用objdump –d stackframe.exe命令可以查看执行文件的汇编代码, gcc 的as汇编器的汇编代码和x86汇编器代码语法不同,具体信息请查阅相关资料。
由于文件很大,这里我只截取3个函数的汇编代码:
080483c4 <tail>:
80483c4: 55 push %ebp
80483c5: 89 e5 mov %esp,%ebp
80483c7: 83 ec 28 sub $0x28,%esp
80483ca: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp)
80483d1: 89 ea mov %ebp,%edx
80483d3: 89 e0 mov %esp,%eax
80483d5: 89 55 ec mov %edx,-0x14(%ebp)
80483d8: 89 45 f0 mov %eax,-0x10(%ebp)
80483db: 8b 55 ec mov -0x14(%ebp),%edx
80483de: b8 00 87 04 08 mov $0x8048700,%eax
80483e3: 89 54 24 04 mov %edx,0x4(%esp)
80483e7: 89 04 24 mov %eax,(%esp)
80483ea: e8 05 ff ff ff call 80482f4 <printf@plt>
80483ef: 8b 55 f0 mov -0x10(%ebp),%edx
80483f2: b8 13 87 04 08 mov $0x8048713,%eax
80483f7: 89 54 24 04 mov %edx,0x4(%esp)
80483fb: 89 04 24 mov %eax,(%esp)
80483fe: e8 f1 fe ff ff call 80482f4 <printf@plt>
8048403: 8b 45 ec mov -0x14(%ebp),%eax
8048406: 8b 10 mov (%eax),%edx
8048408: b8 26 87 04 08 mov $0x8048726,%eax
804840d: 89 54 24 04 mov %edx,0x4(%esp)
8048411: 89 04 24 mov %eax,(%esp)
8048414: e8 db fe ff ff call 80482f4 <printf@plt>
8048419: 8b 45 ec mov -0x14(%ebp),%eax
804841c: 83 c0 04 add $0x4,%eax
804841f: 8b 10 mov (%eax),%edx
8048421: b8 3b 87 04 08 mov $0x804873b,%eax
8048426: 89 54 24 04 mov %edx,0x4(%esp)
804842a: 89 04 24 mov %eax,(%esp)
804842d: e8 c2 fe ff ff call 80482f4 <printf@plt>
8048432: b8 59 87 04 08 mov $0x8048759,%eax
8048437: 8d 55 f4 lea -0xc(%ebp),%edx
804843a: 89 54 24 04 mov %edx,0x4(%esp)
804843e: 89 04 24 mov %eax,(%esp)
8048441: e8 ae fe ff ff call 80482f4 <printf@plt>
8048446: b8 6f 87 04 08 mov $0x804876f,%eax
804844b: 8d 55 f0 lea -0x10(%ebp),%edx
804844e: 89 54 24 04 mov %edx,0x4(%esp)
8048452: 89 04 24 mov %eax,(%esp)
8048455: e8 9a fe ff ff call 80482f4 <printf@plt>
804845a: b8 87 87 04 08 mov $0x8048787,%eax
804845f: 8d 55 ec lea -0x14(%ebp),%edx
8048462: 89 54 24 04 mov %edx,0x4(%esp)
8048466: 89 04 24 mov %eax,(%esp)
8048469: e8 86 fe ff ff call 80482f4 <printf@plt>
804846e: b8 9f 87 04 08 mov $0x804879f,%eax
8048473: 8d 55 08 lea 0x8(%ebp),%edx
8048476: 89 54 24 04 mov %edx,0x4(%esp)
804847a: 89 04 24 mov %eax,(%esp)
804847d: e8 72 fe ff ff call 80482f4 <printf@plt>
8048482: c9 leave
8048483: c3 ret
08048484 <middle>:
8048484: 55 push %ebp
8048485: 89 e5 mov %esp,%ebp
8048487: 83 ec 28 sub $0x28,%esp
804848a: 89 ea mov %ebp,%edx
804848c: 89 e0 mov %esp,%eax
804848e: 89 55 f0 mov %edx,-0x10(%ebp)
8048491: 89 45 f4 mov %eax,-0xc(%ebp)
8048494: 8b 45 08 mov 0x8(%ebp),%eax
8048497: 89 04 24 mov %eax,(%esp)
804849a: e8 25 ff ff ff call 80483c4 <tail>
804849f: 8b 55 f0 mov -0x10(%ebp),%edx
80484a2: b8 b6 87 04 08 mov $0x80487b6,%eax
80484a7: 89 54 24 04 mov %edx,0x4(%esp)
80484ab: 89 04 24 mov %eax,(%esp)
80484ae: e8 41 fe ff ff call 80482f4 <printf@plt>
80484b3: 8b 55 f4 mov -0xc(%ebp),%edx
80484b6: b8 cb 87 04 08 mov $0x80487cb,%eax
80484bb: 89 54 24 04 mov %edx,0x4(%esp)
80484bf: 89 04 24 mov %eax,(%esp)
80484c2: e8 2d fe ff ff call 80482f4 <printf@plt>
80484c7: 8b 45 f0 mov -0x10(%ebp),%eax
80484ca: 8b 10 mov (%eax),%edx
80484cc: b8 e0 87 04 08 mov $0x80487e0,%eax
80484d1: 89 54 24 04 mov %edx,0x4(%esp)
80484d5: 89 04 24 mov %eax,(%esp)
80484d8: e8 17 fe ff ff call 80482f4 <printf@plt>
80484dd: 8b 45 f0 mov -0x10(%ebp),%eax
80484e0: 83 c0 04 add $0x4,%eax
80484e3: 8b 10 mov (%eax),%edx
80484e5: b8 f8 87 04 08 mov $0x80487f8,%eax
80484ea: 89 54 24 04 mov %edx,0x4(%esp)
80484ee: 89 04 24 mov %eax,(%esp)
80484f1: e8 fe fd ff ff call 80482f4 <printf@plt>
80484f6: b8 18 88 04 08 mov $0x8048818,%eax
80484fb: 8d 55 f4 lea -0xc(%ebp),%edx
80484fe: 89 54 24 04 mov %edx,0x4(%esp)
8048502: 89 04 24 mov %eax,(%esp)
8048505: e8 ea fd ff ff call 80482f4 <printf@plt>
804850a: b8 32 88 04 08 mov $0x8048832,%eax
804850f: 8d 55 f0 lea -0x10(%ebp),%edx
8048512: 89 54 24 04 mov %edx,0x4(%esp)
8048516: 89 04 24 mov %eax,(%esp)
8048519: e8 d6 fd ff ff call 80482f4 <printf@plt>
804851e: b8 4c 88 04 08 mov $0x804884c,%eax
8048523: 8d 55 08 lea 0x8(%ebp),%edx
8048526: 89 54 24 04 mov %edx,0x4(%esp)
804852a: 89 04 24 mov %eax,(%esp)
804852d: e8 c2 fd ff ff call 80482f4 <printf@plt>
8048532: b8 62 88 04 08 mov $0x8048862,%eax
8048537: 8d 55 0c lea 0xc(%ebp),%edx
804853a: 89 54 24 04 mov %edx,0x4(%esp)
804853e: 89 04 24 mov %eax,(%esp)
8048541: e8 ae fd ff ff call 80482f4 <printf@plt>
8048546: b8 78 88 04 08 mov $0x8048878,%eax
804854b: 8d 55 10 lea 0x10(%ebp),%edx
804854e: 89 54 24 04 mov %edx,0x4(%esp)
8048552: 89 04 24 mov %eax,(%esp)
8048555: e8 9a fd ff ff call 80482f4 <printf@plt>
804855a: 8b 45 ec mov -0x14(%ebp),%eax
804855d: c9 leave
804855e: c3 ret
0804855f <main>:
804855f: 55 push %ebp
8048560: 89 e5 mov %esp,%ebp
8048562: 83 e4 f0 and $0xfffffff0,%esp
8048565: 83 ec 20 sub $0x20,%esp
8048568: c7 44 24 08 03 00 00 movl $0x3,0x8(%esp)
804856f: 00
8048570: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp)
8048577: 00
8048578: c7 04 24 01 00 00 00 movl $0x1,(%esp)
804857f: e8 00 ff ff ff call 8048484 <middle>
8048584: 89 44 24 14 mov %eax,0x14(%esp)
8048588: 89 ea mov %ebp,%edx
804858a: 89 e0 mov %esp,%eax
804858c: 89 54 24 18 mov %edx,0x18(%esp)
8048590: 89 44 24 1c mov %eax,0x1c(%esp)
8048594: 8b 54 24 18 mov 0x18(%esp),%edx
8048598: b8 8e 88 04 08 mov $0x804888e,%eax
804859d: 89 54 24 04 mov %edx,0x4(%esp)
80485a1: 89 04 24 mov %eax,(%esp)
80485a4: e8 4b fd ff ff call 80482f4 <printf@plt>
80485a9: 8b 54 24 1c mov 0x1c(%esp),%edx
80485ad: b8 a1 88 04 08 mov $0x80488a1,%eax
80485b2: 89 54 24 04 mov %edx,0x4(%esp)
80485b6: 89 04 24 mov %eax,(%esp)
80485b9: e8 36 fd ff ff call 80482f4 <printf@plt>
80485be: 8b 44 24 18 mov 0x18(%esp),%eax
80485c2: 8b 10 mov (%eax),%edx
80485c4: b8 b4 88 04 08 mov $0x80488b4,%eax
80485c9: 89 54 24 04 mov %edx,0x4(%esp)
80485cd: 89 04 24 mov %eax,(%esp)
80485d0: e8 1f fd ff ff call 80482f4 <printf@plt>
80485d5: 8b 44 24 18 mov 0x18(%esp),%eax
80485d9: 83 c0 04 add $0x4,%eax
80485dc: 8b 10 mov (%eax),%edx
80485de: b8 c9 88 04 08 mov $0x80488c9,%eax
80485e3: 89 54 24 04 mov %edx,0x4(%esp)
80485e7: 89 04 24 mov %eax,(%esp)
80485ea: e8 05 fd ff ff call 80482f4 <printf@plt>
80485ef: b8 e7 88 04 08 mov $0x80488e7,%eax
80485f4: 8d 54 24 1c lea 0x1c(%esp),%edx
80485f8: 89 54 24 04 mov %edx,0x4(%esp)
80485fc: 89 04 24 mov %eax,(%esp)
80485ff: e8 f0 fc ff ff call 80482f4 <printf@plt>
8048604: b8 ff 88 04 08 mov $0x80488ff,%eax
8048609: 8d 54 24 18 lea 0x18(%esp),%edx
804860d: 89 54 24 04 mov %edx,0x4(%esp)
8048611: 89 04 24 mov %eax,(%esp)
8048614: e8 db fc ff ff call 80482f4 <printf@plt>
8048619: b8 17 89 04 08 mov $0x8048917,%eax
804861e: 8d 54 24 14 lea 0x14(%esp),%edx
8048622: 89 54 24 04 mov %edx,0x4(%esp)
8048626: 89 04 24 mov %eax,(%esp)
8048629: e8 c6 fc ff ff call 80482f4 <printf@plt>
804862e: b8 00 00 00 00 mov $0x0,%eax
8048633: c9 leave
8048634: c3 ret
8048635: 90 nop
8048636: 90 nop
8048637: 90 nop
8048638: 90 nop
8048639: 90 nop
804863a: 90 nop
804863b: 90 nop
804863c: 90 nop
804863d: 90 nop
804863e: 90 nop
804863f: 90 nop
这里我们分析下main函数的栈帧开辟步骤:
0804855f <main>:
8048560: 89 e5 mov %esp,%ebp //更新栈基址为当前栈指针所指地址
8048562: 83 e4 f0 and $0xfffffff0,%esp // 栈指针地址16字节对齐
8048565: 83 ec 20 sub $0x20,%esp //栈指针向下偏移32个字节
8048568: c7 44 24 08 03 00 00 movl $0x3,0x8(%esp) //初始化middle函数参数p2 = 0x03
804856f: 00
8048570: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp) //初始化p1 = 0x02
8048577: 00
8048578: c7 04 24 01 00 00 00 movl $0x1,(%esp) //初始化p1 = 0x01
804857f: e8 00 ff ff ff call 8048484 <middle> //调用middle函数
左侧是内存地址,中间的数字是指令机器码,右侧是mian函数调用middle函数之前的汇编指令,x86堆栈是向下增长的(即:高地址向地地址增长)指令:sub $0x20,%esp,堆栈指针向下偏移0x20,其实就是开辟一个 32字节的栈帧空间,所以函数开辟栈帧的方式就是通过偏移栈指针的方式。由汇编文件看返回地址并没有在栈帧空间中保存,因为函数本身并不知调用它的函数是谁,所以也就不知道返回地址。函数栈帧空间是ebp和esp所指的空间范围。
函数调用时栈帧分析
于 2014-10-25 16:37:13 首次发布