栈帧的含义和作用栈由栈帧组成,每个栈帧对应于一个(未执行完的)函数。接下来我们通过讲解栈帧的布局、形成和消亡来理解栈帧在函数调用时是如何起作用的。
栈帧的布局图10.7所示是一个简单的测试程序,用于帮助我们了解栈帧。
embedded/code/application/stackframe/main.c
00001: #include <stdio.h>
00002:
00003: //lint -e530 -e123
00004:
00005: void tail (int _param)
00006: {
00007: int local = 0;
00008: int reg_esp, reg_ebp;
00009:
00010: asm volatile(
00011: // get EBP
00012: "movl %%ebp, %0 \n"
00013: // get ESP
00014: "movl %%esp, %1 \n"
00015: : "=r" (reg_ebp), "=r" (reg_esp)
00016: );
00017: printf ("tail (): EBP = %x\n", reg_ebp);
00018: printf ("tail (): ESP = %x\n", reg_esp);
00019: printf ("tail (): (EBP) = %x\n", *(int *)reg_ebp);
00020: printf ("tail (): return address = %x\n", *(((int *)reg_ebp + 1)));
00021: printf ("tail (): &local = %p\n", &local);
00022: printf ("tail (): ®_esp = %p\n", ®_esp);
00023: printf ("tail (): ®_ebp = %p\n", ®_ebp);
00024: printf ("tail (): &_param = %p\n", &_param);
00025: }
00026:
00027: int middle (int _p0, int _p1, int _p2)
00028: {
00029: int reg_esp, reg_ebp;
00030:
00031: asm volatile(
00032: // get EBP
00033: "movl %%ebp, %0 \n"
00034: // get ESP
00035: "movl %%esp, %1 \n"
00036: : "=r" (reg_ebp), "=r" (reg_esp)
00037: );
00038: tail (_p0);
00039: printf ("middle (): EBP = %x\n", reg_ebp);
00040: printf ("middle (): ESP = %x\n", reg_esp);
00041: printf ("middle (): (EBP) = %x\n", *(int *)reg_ebp);
00042: printf ("middle (): return address = %x\n", *(((int *)reg_ebp + 1)));
00043: printf ("middle (): ®_esp = %p\n", ®_esp);
00044: printf ("middle (): ®_ebp = %p\n", ®_ebp);
00045: printf ("middle (): &_p0 = %p\n", &_p0);
00046: printf ("middle (): &_p1 = %p\n", &_p1);
00047: printf ("middle (): &_p2 = %p\n", &_p2);
00048: return 1;
00049: }
00050:
00051: int main ()
00052: {
00053: int reg_esp, reg_ebp;
00054: int local = middle (1, 2, 3);
00055:
00056: asm volatile(
00057: // get EBP
00058: "movl %%ebp, %0 \n"
00059: // get ESP
00060: "movl %%esp, %1 \n"
00061: : "=r" (reg_ebp), "=r" (reg_esp)
00062: );
00063: printf ("main (): EBP = %x\n", reg_ebp);
00064: printf ("main (): ESP = %x\n", reg_esp);
00065: printf ("main (): (EBP) = %x\n", *(int *)reg_ebp);
00066: printf ("main (): return address = %x\n", *(((int *)reg_ebp + 1)));
00067: printf ("main (): ®_esp = %p\n", ®_esp);
00068: printf ("main (): ®_ebp = %p\n", ®_ebp);
00069: printf ("main (): &local = %p\n", &local);
00070: return 0;
00071: }
图10.7
这个小程序的每个函数中都嵌入了汇编代码,以便获得各函数运行时刻ESP和EBP寄存器的值。另外,每一个函数中都打印出了EBP寄存器所指向内存地址处的值,以及位于其后的函数返回地址,这样做的原因后面还会细讲。图10.8显示了这一程序的编译和运行结果。
yunli.blog.51CTO.com
/embedded/build
$
make
yunli.blog.51CTO.com
/embedded/build
$
./release/stackframe.exe
tail (): EBP = 22cd08
tail (): ESP = 22ccf0
tail (): (EBP) = 22cd28
tail (): return address = 40120b
tail (): &local = 0x22cd04
tail (): ®_esp = 0x22cd00
tail (): ®_ebp = 0x22ccfc
tail (): &_param = 0x22cd10
middle (): EBP = 22cd28
middle (): ESP = 22cd10
middle (): (EBP) = 22cd58
middle (): return address = 401302
middle (): ®_esp = 0x22cd24
middle (): ®_ebp = 0x22cd20
middle (): &_p0 = 0x22cd30
middle (): &_p1 = 0x22cd34
middle (): &_p2 = 0x22cd38
main (): EBP = 22cd58
main (): ESP = 22cd30
main (): (EBP) = 22cd98
main (): return address = 61006e73
main (): ®_esp = 0x22cd50
main (): ®_ebp = 0x22cd4c
main (): &local = 0x22cd48
图10.8
为了更好地理解输出结果中各数据间的关系,我们将其转化为图,如图10.9所示。图的左边还示例说明了栈的增长方向和栈的内存地址。黑色的箭头和寄存器名表示当前栈帧,否则用灰色表示。图中表示的是站在tail()函数内所看到的栈布局,其中完整地示例说明了tail()和middle()两个函数的栈帧结构,以及main()函数的一部分。