-
Notifications
You must be signed in to change notification settings - Fork 0
Lab1
通过阅读题目以及代码可以发现,格式化输出的相关代码主要位于lib/printfmt.c中的vprintfmt函数中,为了使其支持%n,只需要补全相关代码即可。实现这个功能的关键就是要统计所有输出字符,而阅读代码之后,发现putdat就是输出字符的统计,所以实现这个功能还是挺简单的。不过有两点需要注意一是要判断传入的char 指针是否为空;二是要判断putdat是否大于127,因为%n支持的是有符号的char,其范围为[-128, 127]。实现代码位于lib/printfmt.c[246:259]。
这个题目的要求就是打印出当前函数及其所有祖先函数在栈上的相关信息。要实现这个功能,实现就需要熟悉C函数的调用约定。
上图是简单的函数fa调用函数fb的反汇编结果。fa调用fb时,首先将参数按从右向左的顺序压入栈,然后执行call就行了。而实际call执行了两个动作,第一步是将当前的指令指针也就是eip压入栈,第二步则是跳转到函数fb的地址。之所以将eip压入栈,是为了保证函数调用结束后,原来的程序能够继续正常运行。 对于被调用函数fb,它的第一个动作就是将基指针ebp压入栈,同时将此时的栈顶指针esp作为该函数的基指针。当函数执行结束后,fb首先恢复了ebp(包含在leave指令中),然后调用了ret指令。ret其实也执行了两个动作,第一步是恢复eip,即pop eip;第二步就是跳转到eip所指向的地址继续执行。如果从栈的角度来看,eip和ebp的存储位置总是相邻的,而且ebp存在eip的上面。 对于这个题目,我觉得最主要还有一点,就是搞清楚ebp的作用,从上面我们知道ebp是当前函数的基指针,那么ebp指向的地址里面存的是什么呢——原先ebp的值,也就是调用者的基指针,这样就构成了一个单向链表关系。此外,在kern/entry.S中的relocated代码段将ebp初始化为0,也就是说最底层的ebp为0。 至此,这个题目就简单了,只需要读出当前函数的ebp,然后利用ebp之间的指针关系,找出所有的祖先ebp,直到ebp为0时截止。此外我们还知道eip就存在紧挨着ebp的上一个地址中,再往上则是参数。实现代码位于kern/monitor.c[130:159],最主要的就是一个while循环。 Exercise 13.Modify your stack backtrace function to display, for each eip, the function name, source file name, and line number corresponding to that eip. 这个题目其实要做的很简单,只需要找出行号就可以了,找的过程也有函数stab_binsearch可以调用,我觉得这个题目的关键就是需要知道行号存在结构体Stab的哪个域中就可以了,通过观察objdump -G的输出结果发现,表示行号的Stab类型为SLINE,此外,具体的行号存在对应的stab的n_desc中。实现代码位于 kern/kdebug.c[184:189]。
这个题目我觉得并不是太好,因为这个题目高度依赖于程序运行的内存结构,而不同版本的gcc编译出来的结果却完全不同,导致我在这个题目上浪费了挺长时间,不过现在想想,如果能搞清楚具体的内存结构还不算太难。 要实现这个功能,主要思路就是改写start_overflow的返回地址,也就是其保存的eip,使其保存在栈里的eip指向do_overflow的函数地址。此外,为了保证do_overflow执行结束后能够正常返回,还需要把start_overflow的返回地址保存在原来返回地址的下方,这样的话,当do_overflow执行ret的时候,就会跳转到start_overflow的返回地址,从而使得程序能够正常执行。在我的实现中cprintf函数主要用来改写eip。实现代码位于kern/monitor.c[93:104]。