据说,要学会编程,就应该"read code, and write code". 读了这么久的代码,终于要动手写些东西了。 :-)
实现原理:C函数调用时,首先将参数push入栈,然后push返回地址,接着将原来的EBP push入栈,然后将ESP的值赋给EBP,令ESP指向新的栈顶。而函数返回时,会将EBP的值赋予ESP,然后pop出原来的EBP的值赋予EBP指针。 更详细准确的信息请参考《linux内核完全剖析》一书中的"3.4.1 C函数调用机制"。
由于EBP的内容即为调用它的函数的EBP的地址,因此追踪EBP的值的变化,即可知道函数调用顺序及其他信息。
实现方法:由于代码中已经提供了函数read_ebp(), 因此可以利用来获得当前的EBP的值,即一个内存地址(指针地址)。而该内存地址所保存的值(指针值)即为调用它的函数的EBP地址值。循环比较该变量,直到找到kernel的栈底为止。
判断kernel栈底的方法有两种。假设定义了变量uint32_t *ebp, 则既可比较ebp是否为栈底位置,也可比较*ebp是否为0.
方法1: 文件entry.S中有这样几行:
bootstack:
.space KSTKSIZE
.globl bootstacktop
可知栈底地址为变量bootstacktop的位置,栈长为KSTKSIZE=32KB。
方法2: 文件entry.S中有如下内容:
# Clear the frame pointer register (EBP)
# so that once we get into debugging C code,
# stack backtraces will be terminated properly.
movl $0x0,%ebp # nuke frame pointer
# Set the stack pointer
movl $(bootstacktop),%esp
# now to C code
call i386_init
可知调用i386_init()时,将值为0的ebp值push到了栈底,因此判断 *ebp==0 既可。
详细实现(使用方法2):
int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
// Your code here.
uint32_t *ebp;
extern char bootstacktop[];
cprintf("Stack backtrace:\n");
ebp = (uint32_t *)read_ebp();
while(ebp < (uint32_t *)bootstacktop){
cprintf("ebp %08x eip %08x args %08x %08x %08x %08x %08x\n",
ebp, *(ebp+1), *(ebp+2), *(ebp+2+1),
*(ebp+2+2), *(ebp+2+3), *(ebp+2+4));
ebp = (uint32_t *)*ebp;
}
return 0;
}
将此功能添加到monitor中,只需修改文件kern/monitor.c, 添加以下代码到变量struct Command commands[]中既可:
{ "mon_backtrace", "Backtrace functions call chain", mon_backtrace },
(转贴请注明: by: chunchengfh, from: chunchengfh.cublog.cn)
阅读(2126) | 评论(0) | 转发(0) |