4
Lab1还差最后一部分,就是给出具体的调试信息,如下面所示:
K> backtrace
Stack backtrace:
ebp f010ff78 eip f01008ae args 00000001 f010ff8c 00000000 f0110580 00000000
kern/monitor.c:143: monitor+106
ebp f010ffd8 eip f0100193 args 00000000 00001aac 00000660 00000000 00000000
kern/init.c:49: i386_init+59
ebp f010fff8 eip f010003d args 00000000 00000000 0000ffff 10cf9a00 0000ffff
kern/entry.S:70: <unknown>+0
K>
和上次的montacktrace相比,增加了eip所在的源文件、对应的源文件行数、函数名以及在相对于该函数地址后的汇编代码的地址偏移。
首先明确,这个功能既然能实现,那么肯定在某个地方以某种方式地方记录着eip和源文件之间的相互对应的信息。
而这个存储的地方就是stab表。
首先使用objdump -G 查看一下kernel生成的obj(obj/kern/kernel)中stab表中的内容,如下:
前5列是stab,第六列不知道含义,第七列对应stabstr,换句话说这个表会原封不动的读入内存,stab部分的地址就是kdebug.c里的stab_begin,后面字符串地址就是stabstr_begin。
这个表很直观,根据类型不同在某些行(用index表示)会给出一个地址与源文件的对应关系(SO),紧接着给出这个源文件中的函数地址和函数名的对应关系(FUN),之后是这个函数里的每一行(SLINE)和地址偏移的对应关系。
而我们所要做的就是读取这些关系并显示出来。
问题1 这个表何时以何种方式加入的内存。
很遗憾kdebug.c里面stab表的地址符号是extern进来的,我并没有找到这个符号第一次出现的位置,不过估计应该是和内核一起,通过elf头的文件信息加载进来的,但具体加载到了哪里我没有深究。
问题2 如何对这个表进行检索。
庆幸的是,Jos很“友好”的给我们提供了一个检索函数:stab_binsearch。函数接受4个参数,第一是stab总表(这个说法并不是很恰当,虽然函数的含义是传入一个总表,但其实只要传入表中的一项当做起始位置函数也能正常工作),第二第三是表的下标的范围,代表在这个下标范围内寻找,若找到了,则第二个参数赋值为相应的表的下标,若没有找到,则right>left,第四个参数传入要寻找的表项的类型,也就是上面图的第二列。第五个参数代表要寻找的值,也就是上面图中的第五列,根据这个值寻找表项。而且为了比较高效,这个函数还是个二分搜索的。更加详细的说明函数注释都解释的很清楚。
问题3 我们需要做什么
需要做的是完成debuginfo_eip,该函数传入一个eip值,通过这个eip查找所需信息,并放入Eipdebuginfo这个结构体里。 JOS比较“友好”,已经替我们完成了很多功能了,我们只需要使用stab_binsearch找到函数地址和源文件行数对应的关系就行了。
代码如下:
之前的代码已经找到了函数地址,并且已经将addr减去了这个函数地址得到了函数内偏移,查找结果会放在lline里,根据这个index访问stab表即可。
之后修改backtrace函数,按要求显示相应信息即可,然后运行结果大概如下:
值得注意的是,对应源文件,发现“行数”指向的并不是很正确,有时候会多向下指几行。
这是因为在查找stab时候,我们是根据eip进行查找,而eip的内容就是指向下一条将要执行的指令的地址,所以“行数”的不准是正常的合乎逻辑的。
当然要修正这个“bug”使之更符合我们的调试习惯也并不难,只要把查找到的lline改成lline-1,即寻找上一行源代码即可。
但因为实验貌似要求就是根据eip找到相应的位置,所以我也并没有改。
至此lab1就结束了,本来想一两周就写完的,没想到断断续续拖了好几个月。
5年前刚接触编程的时候编的第一个程序就是printf("hello,world!");,然后学习断点调试,可直到今天我才大概明白了这看上去如此“简单”而不值得一提的功能实现起来需要多少的技术水平。任何看起来“理所当然”的东西实现或者证明起来都是相当难的,数学和计算机技术都佐证了这一点。
所谓返璞归真的境界,大概就是如此吧。