在lab4中,我们要做一个backtrace的函数,来打印出函数调用的过程。
这是xv6 栈的布局,fp为栈帧指针,sp为栈底指针。栈是从高地址向低地址扩张,整个调用过程的第一个函数在高地址,最近的函数在低地址。如A()->B()->C(),则A在高地址,C在低地址。
注意:每一个栈帧的大小是不固定的,和具体调用函数有关(局部变量不同)。但是每一个栈帧中往下0x8是返回地址,往下0x10是上一个函数对应的栈帧指针,这是固定的。
注意:分清楚栈和栈帧的概念,栈中有多个栈帧。
(借用一下知乎大佬的图)
backtrace代码:
关于这两行代码,实则是为了解引用取出返回值和上一个栈帧的地址。(curr_fp本来就是一个地址,所以我们通过偏移加解引用)
将整数值转换为指针并解引用也就是取该地址的值
uint64 ret = *(pte_t *)(curr_fp - 0x8);
uint64 prev_fp = *(pte_t *)(curr_fp - 0x10);
打印一下fp看看是不是从低地址往高地址走。(因为入栈是从高地址开始向低地址扩张,现在进行返回则是相反的)
可以看出当前fp在低地址,它的前一个fp在高地址。
那么我们返回地址都是些什么呢,这实际上就是ecall调用的过程,以公开课的write函数为例
在进入内核后,调用过程为uservec->usertrap->syscall->sys_write,我们打印返回地址一看究竟:
可以看到是符合我们预期的,栈底是sys_sleep,往上依次是syscall、usertrap
终于搞懂了函数调用的过程了,开心!