Exercise 11
要求
借助x86提供的read_ebp()在kern/monitor.c的mon_backtrace中打印出函数调用的栈中的ebp和eip的信息,实现下面所示的效果
Stack backtrace:
ebp f0109e58 eip f0100a62 args 00000001 f0109e80 f0109e98 f0100ed2 00000031
ebp f0109ed8 eip f01000d6 args 00000000 00000000 f0100058 f0109f28 00000061
...
分析
先来看一个例子
int bar(int a, int b) {
return a+b;
}
int foo(int a, int b) {
return bar(2, 3);
}
int main() {
foo(2, 3);
return 0;
}
将其转换成汇编代码,删除了一些无用的信息,因为我的机子是64位的,所以rbp == ebp,rsp == esp ...:
bar:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
popq %rbp
ret
foo:
pushq %rbp
movq %rsp, %rbp
subq $8, %rsp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -8(%rbp), %edx
movl -4(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call bar
leave
ret
main:
pushq %rbp
movq %rsp, %rbp
movl $3, %esi
movl $2, %edi
call foo
movl $0, %eax
popq %rbp
ret
从上面代码可以画出调用到bar时候栈的结构:
函数调用栈结构
通过获取当前rbp寄存器的值就可以根据栈中的地址访问到rip和参数。rip是程序运行的下一条指令,当函数调用完,rip指向的应该是当初call的位置,所以rip = *(rbp+1)。
代码:
int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
uint32_t *ebp = (uint32_t*)read_ebp(); // 获取ebp当前寄存器的值,将其转换为地址操作
uint32_t eip = 0;
while(ebp) {
eip = *(ebp+1); // 如图所示,eip就在ebp的上面一个地址
cprintf("ebp %x eip %x args", ebp, eip);
uint32_t* args = ebp+2; // 往上第二个开始就是函数调用的参数
for(int i = 0; i < 5; ++i) { // 默认打印出5个参数
uint32_t argv = args[i];
cprintf(" %08x ", argv);
}
cprintf("\n");
ebp = (uint32_t*)*ebp;
}
return 0;
}