MIT6.828 lab1/Exercise 11

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;
}

你可能感兴趣的:(MIT6.828 lab1/Exercise 11)