本文主要是介绍Linux C函数调用栈帧结构,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Linux C程序的反汇编,每个函数第一个指令都是push %rbp
,即当caller者调callee时,指令callq <fun addr>
callee返回后的下一个指令地址压入栈帧,然后push %rbp
保存 rbp
寄存器,紧接着mov %rsp,%rbp
更新rbp
寄存器这样层层调用构成函数调用栈,每个栈帧开始就是存的上一个栈帧的rbp
, 结尾就是调用callee后的下一个执行令地址。函数调用详细介绍可以看这篇文章从汇编看Linux C函数的调用约定和参数传递的细节。
通过前面分析,可以得出,rbp寄存器存放的是当前栈帧的基址,当前栈帧的基址即开始地址则存放的是上一个栈帧的基址,即*rbp就是上一个栈帧的基址。对于x64,每个栈帧开头的八个字节存放的就是上个栈帧的基址。调用栈的结构图大致如下:
下面用一个例子来说明栈帧结构的细节。
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:5
5 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 // rbp0x0040051f 0x00000000 // return addr
0x7fff1c4d0cf0: 0xa68271a8 0x00000007 0xa6dfe4c0 0x00007fe4
0x7fff1c4d0d00: 0x00000002 0x00007fe40x1c4d0d30 0x00007fff // rbp
0x7fff1c4d0d10: 0x00400543 0x00000000 // return addr0x004005bd 0x00000007
0x7fff1c4d0d20: 0x1c4d0d50 0x00007fff 0x00000001 0x00000000
0x7fff1c4d0d30: 0x1c4d0d50 0x00007fff // rbp0x00400564 0x00000000 // return addr
还可以用pmap
命令看布局:
root@ubuntu:~# pmap -p 23620
23620: ./a.out
0000000000400000 4K r-x-- /root/a.out
0000000000600000 4K r---- /root/a.out
0000000000601000 4K rw--- /root/a.out
00007fe4a6817000 1776K r-x-- /lib/x86_64-linux-gnu/libc-2.19.so
00007fe4a69d3000 2044K ----- /lib/x86_64-linux-gnu/libc-2.19.so
00007fe4a6bd2000 16K r---- /lib/x86_64-linux-gnu/libc-2.19.so
00007fe4a6bd6000 8K rw--- /lib/x86_64-linux-gnu/libc-2.19.so
00007fe4a6bd8000 20K rw--- [ anon ]
00007fe4a6bdd000 140K r-x-- /lib/x86_64-linux-gnu/ld-2.19.so
00007fe4a6de6000 12K rw--- [ anon ]
00007fe4a6dfd000 8K rw--- [ anon ]
00007fe4a6dff000 4K r---- /lib/x86_64-linux-gnu/ld-2.19.so
00007fe4a6e00000 4K rw--- /lib/x86_64-linux-gnu/ld-2.19.so
00007fe4a6e01000 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)
函数调用栈的获取原理分析
这篇关于Linux C函数调用栈帧结构的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!