C语言程序函数调用栈帧结构

来源:互联网 发布:国际局势知乎 编辑:程序博客网 时间:2024/06/05 21:14

Linux C程序的反汇编,每个函数第一个指令都是push %rbp,即当caller者调callee时,指令callq <fun addr> callee返回后的下一个指令地址压入栈帧,然后push %rbp 保存 rbp寄存器,紧接着mov %rsp,%rbp 更新rbp 寄存器这样层层调用构成函数调用栈,每个栈帧开始就是存的上一个栈帧的rbp, 结尾就是调用callee后的下一个执行令地址。函数调用详细介绍可以看这篇文章从汇编看Linux C函数的调用约定和参数传递的细节。

通过前面分析,可以得出,rbp寄存器存放的是当前栈帧的基址,当前栈帧的基址即开始地址则存放的是上一个栈帧的基址,即*rbp就是上一个栈帧的基址。对于x64,每个栈帧开头的八个字节存放的就是上个栈帧的基址。调用栈的结构图大致如下:

call stack

下面用一个例子来说明栈帧结构的细节。

int fun3(int a3){    int a = 3;    int re = a3;    while(1);    return re;}int fun2(int a2){    int a = 2;    int re = fun3(a2);    return re;}int fun1(int a1){    int a = 1;    int re = fun2(a1);    return re;}int main(){    int a = 7;    int b = fun1(a);    return 0;}

利用gdb对上面里进行详细分析,编译gcc -g main.c, 执行./a.out, 然后新开一个shell窗口ps afxu | grep a.out查找进程PID,gdb - <PID>进行调试。可以很容易分析出函数的栈帧结构组织关系,详细如下:

fun3 (a3=7) at main.c:55       while(1);(gdb) bt#0  fun3 (a3=7) at main.c:5#1  0x000000000040051f in fun2 (a2=7) at main.c:12#2  0x0000000000400543 in fun1 (a1=7) at main.c:19#3  0x0000000000400564 in main () at main.c:26(gdb) p $rbp                    // 当前栈帧0 rbp$1 = (void *) 0x7fff1c4d0ce0(gdb) x/i *(long*)($1 + 8)      // 栈帧1 返回地址   0x40051f <fun2+28>:  mov    %eax,-0x4(%rbp)(gdb) p/x *(unsigned long*)$rbp // 栈帧1 rbp地址$2 = 0x7fff1c4d0d08(gdb) x/i *(long*)($2 + 8)      // 栈帧2 返回地址   0x400543 <fun1+28>:  mov    %eax,-0x4(%rbp)(gdb) p/x *(unsigned long*)$2   // 栈帧3 rbp$3 = 0x7fff1c4d0d30(gdb) x/i *(long*)($3 + 8)      // 栈帧3 返回地址   0x400564 <main+25>:  mov    %eax,-0x4(%rbp)(gdb) x/24x $rsp                // 从rsp开始24 DWORD的栈内容0x7fff1c4d0ce0: 0x1c4d0d08  0x00007fff                          // rbp                                        0x0040051f  0x00000000  // return addr0x7fff1c4d0cf0: 0xa68271a8  0x00000007  0xa6dfe4c0  0x00007fe40x7fff1c4d0d00: 0x00000002  0x00007fe4                                        0x1c4d0d30  0x00007fff  // rbp0x7fff1c4d0d10: 0x00400543  0x00000000                          // return addr                                        0x004005bd  0x000000070x7fff1c4d0d20: 0x1c4d0d50  0x00007fff  0x00000001  0x000000000x7fff1c4d0d30: 0x1c4d0d50  0x00007fff                          // rbp                                        0x00400564  0x00000000  // return addr

还可以用pmap命令看布局:

root@ubuntu:~# pmap -p 2362023620:   ./a.out0000000000400000      4K r-x-- /root/a.out0000000000600000      4K r---- /root/a.out0000000000601000      4K rw--- /root/a.out00007fe4a6817000   1776K r-x-- /lib/x86_64-linux-gnu/libc-2.19.so00007fe4a69d3000   2044K ----- /lib/x86_64-linux-gnu/libc-2.19.so00007fe4a6bd2000     16K r---- /lib/x86_64-linux-gnu/libc-2.19.so00007fe4a6bd6000      8K rw--- /lib/x86_64-linux-gnu/libc-2.19.so00007fe4a6bd8000     20K rw---   [ anon ]00007fe4a6bdd000    140K r-x-- /lib/x86_64-linux-gnu/ld-2.19.so00007fe4a6de6000     12K rw---   [ anon ]00007fe4a6dfd000      8K rw---   [ anon ]00007fe4a6dff000      4K r---- /lib/x86_64-linux-gnu/ld-2.19.so00007fe4a6e00000      4K rw--- /lib/x86_64-linux-gnu/ld-2.19.so00007fe4a6e01000      4K rw---   [ anon ]00007fff1c4b2000    132K rw---   [ stack ]00007fff1c5dd000      8K r-x--   [ anon ]ffffffffff600000      4K r-x--   [ anon ]

参考

Computer Systems: A Programmer’s Perspective, 3/E (CS:APP3e)
函数调用栈的获取原理分析

1 0