这篇文章用于实践关于x86下关于调用堆栈恢复。
具体原理请参看这篇文章 http://blog.youkuaiyun.com/djvc/article/details/10202201
函数调用栈的结构布局如下图所示
| . |
+-------------------------+
| 被调用者保存的寄存器现场 |
| EBX,ESI和EDI(根据需要) |
+-------------------------+
| 临时空间 |
+-------------------------+
| 局部变量#2 | [EBP - 8]
+-------------------------+
| 局部变量#1 | [EBP - 4]
+-------------------------+
EBP==>| 调用者的EBP |
+-------------------------+
| 返回地址 |
+-------------------------+
| 实际参数#1 | [EBP + 8]
+-------------------------+
| 实际参数#2 | [EBP + 12]
+-------------------------+
| 实际参数#3 | [EBP + 16]
+-------------------------+
| 调用者保存的寄存器现场 |
| EAX,ECX和EDX(根据需要)|
+-------------------------+
| : |
| . |
图 1
测试代码片段:
#include<stdlib.h>
#include<stdio.h>
void func1()
{
printf("func1");
}
void func2(int a)
{
int b = a+2;
printf("func2 %d",b);
func1();
}
int main(int argc,const char* argv[])
{
func2(argc);
return 0;
}
用gdb调试该程序
[pirate@ljf callstack]$ gdb callstack
(gdb) b callstack.c:6
Breakpoint 1 at 0x4005a8: file callstack.c, line 6.
(gdb) r
Starting program: /home/ejoy/cplus_test/callstack/callstack
Breakpoint 1, func1 () at callstack.c:6
6 printf("func1");
(gdb) info reg
rax 0x7 7
rbx 0x0 0
rcx 0x0 0
rdx 0x3e2937ae10 266979487248
rsi 0x400716 4196118
rdi 0x3e29379780 266979481472
rbp 0x7fffffffe4f0 0x7fffffffe4f0
rsp 0x7fffffffe4f0 0x7fffffffe4f0
r8 0x1 1
r9 0x1 1
r10 0xffffffff 4294967295
r11 0x246 582
r12 0x4004c0 4195520
r13 0x7fffffffe620 140737488348704
r14 0x0 0
r15 0x0 0
rip 0x4005a8 0x4005a8 <func1()+4>
eflags 0x202 [ IF ]
cs 0xe033 57395
ss 0xe02b 57387
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
在这里,我得到了rbp里的值,就是我们需要关注的ebp,我们看看rbp指向的内存信息,
(gdb) x/100xg 0x7fffffffe4f0 ##由于虚拟机是64位的,所以显示按8字节处理
0x7fffffffe4f0: 0x00007fffffffe520 0x00000000004005e6
0x7fffffffe500: 0x00007fffffffe638 0x0000000100400665
0x7fffffffe510: 0x0000003e2900fb68 0x0000000300400620
0x7fffffffe520: 0x00007fffffffe540 0x0000000000400601
0x7fffffffe530: 0x00007fffffffe628 0x0000000100000000
0x7fffffffe540: 0x0000000000000000 0x0000003e2901ec5d
0x7fffffffe550: 0x0000000000000000 0x00007fffffffe628
由于是8字节,所以一行就显示2个字段。从上面我们就可以看出调用的层次关系了,譬如
0x7fffffffe4f0: 0x00007fffffffe520 0x00000000004005e6
| |
上一层的栈帧地址 下一条指令的地址(函数返回后的执行命令地址)
顺藤摸瓜,
(gdb) b *0x4005a8
Note: breakpoint 1 also set at pc 0x4005a8.
Breakpoint 2 at 0x4005a8: file callstack.c, line 6.
(gdb) b *0x4005e6
Breakpoint 3 at 0x4005e6: file callstack.c, line 15.
(gdb) b *0x400601
Breakpoint 4 at 0x400601: file callstack.c, line 24.
对于指定文件和指定行数(上面代码行数)就可以指定调用层次了。