通常的调试流程其实很简单:发现问题,稳定复现,肯定临界条件,定位问题,修复问题,核查结果。迭代这个过程,造成一个闭环python
老实说,OS的实验代码,开箱体验极差,程序跳来跳去,进了Lab4后还要考虑内核态切换,很难靠肉眼完成上述闭环。debug愉悦指数为负。编程
因此在几周的探索后,我大概总结整理了一些调试经验,主要是如何在当前体系下利用或构建调试工具,改善调试体验。编程语言
咱们的口号是:没有蛀牙。函数
抛砖引玉。工具
如今咱们手里有:编码
除此以外咱们还有:debug
printf
panic
和assert
,她们是海滩上最抢眼的宝贝儿也就是所谓的“瞪眼法”。能瞪出来的Bug请直接瞪出来。调试
瞪眼法的升级版是小黄鸭调试法,进一步升级版是面向室友调试,终极形态是面向男友/女友/老妈调试code
别漏了这一步,有时候瞪是最快的,但这也要求你对OS实验到底要干什么有彻底清晰的把控。对象
记得调VIM配色,记得买珍视明。
assert
这个东西可能咱们已经在各类check
里面见过了。它的实如今include/mm.h
中(很奇怪的位置),在咱们的代码中是以宏的形式实现的:
#define assert(x) do {if(!(x)) panic("...%s", #x); } while(0)
做者十分老练,这里的trick是用拼接运算将参数x
做为一个字符串传入panic
,这样输出时咱们就能够看到断言内的具体内容。
如今assert
的功能就是这样的:若是传入的语句x
布尔值为假,就陷入panic
(顺带一提,panic
其实就是在输出信息后陷入死循环)。熟悉其余语言的断言机制,好比python,的同窗应该会感到十分亲切
这个东西有什么用呢?咱们能够在代码中合适的位置加入断言,来显示地检查某段运行逻辑是否如咱们的预期。
举个例子,page_insert
后,插入的页面所对应的物理地址应该等于虚拟地址va
所映射的物理地址(这正是这个函数的功能),所以在函数的末尾咱们能够添加一句断言:
assert(va2pa(pgdir, va) == page2pa(pp));
当程序运行到这里时,若是没有出现错误,那么这句assert
不会产生任何做用,若是出现了各类状况致使这两个物理地址不一致,那么程序就会自陷终止并输出信息。当咱们调试时,若是看到了它自陷,咱们就能知道这里的代码必定存在逻辑漏洞(或者,大灾难),在它引发更隐晦的错误前将其修复。
固然assert
也不是彻底没有反作用:它依旧会占用指令运行周期数,这个问题咱们以后再讨论。
printf
撒的满屋子都是而后由于忘了删调试语句,喜提评测0/100
debug()
printf
的问题在于,开关并不方便。很难作到只作少许的修改将全部调试信息所有关闭。所以这里开始咱们须要本身造轮子
稍微优雅一些的作法是,声明一个调试宏或一个全局变量,来控制全局的调试开关
宏的写法相似于:
# ifndef __DEBUG__ # define __DEBUG__ # endif ... # ifdef __DEBUG__ printf("SOME DEBUG INFO\n"); # endif
全局变量的写法则是
extern int is_debug_mode = 1; ... if (is_debug_mode) { printf("SOME DEBUG INFO\n"); }
宏的一个优点在于,若是咱们关掉了调试,整个调试代码块不会被编译,执行的语句更少
直接用变量则固然更可读,更美观,而且能够更轻松地实现调试信息分级
不管哪种,每次输出的时候敲三行代码太复杂了,所以终极形式是包装一个debug
函数
void _debug(char * file, int line, char *fmt, ...){ if (is_debug_mode) printf("SAY SOMETHING I'M GIVING UPON YOU.\n"); } # define debug(...) _debug(__FILE__, __LINE__, __VA__ARGS__)
这样咱们就能像使用printf
同样使用debug
(支持可变参数),同时还能输出更多调试信息,好比这一句在哪一个文件的哪一行
而后在init
中控制debug
的全局开闭就好
上面的各类方法都是针对C语言程序的,汇编则不太好办。大致上有两个思路:
output_ov_info
(我怎么可能记得它叫什么,反正差很少就这个东西)。这个东西使用场景太杂乱,不展开了MIPS为咱们提供了BREAK和一系列Trap指令,这里先只讨论Break的使用,Trap是相似的。
BREAK的做用是,抛出一个bp断点异常并使CPU切入内核态处理异常。bp的cause编码是9,为了让咱们的小系统能够处理这个异常咱们须要仿照课上的ov为其添加一个handler
lib/trap.c
,声明外连接extern handle_bp();
,而后将其绑定在9号异常上lib/genex.S
中添加handle_bp
的处理程序。推荐使用BUILD_HANDLER bp break_handler cli
将异常处理移交给一个C语言函数break_handler(struct Trapframe *)
完成而后咱们在异常处理中输出各类须要的信息,末尾死循环便可(也就是,panic
)。最重要的信息或许是pc。
若是想要更好的体验,添加一个读入字符syscall(参考lab1某次课上),让咱们能够向系统输入字符,而后把break的死循环换成等待读入。这么作的时候记得处理中断屏蔽
回过头看C程序的调试,用相似的技巧也能够实现断点调试。若是须要能够再封装一个bp()
配合debug()
使用。
再回过头看Trap(TEQ、TNE等一系列条件内陷语句),咱们能够用相似的方法为其添加异常处理程序,这些指令抛出的异常都是TRAP(13),所以咱们只须要把处理程序挂在13号异常上就能够令其运做。
Trap很像MIPS版的assert
,当知足某个条件时让内核自陷。所以在汇编代码中善用这些指令也能起到尽早发现并规避错误的做用。
或许咱们能够进一步挖掘MIPS的片上调试,但目前我尚未遇到这种粒度的需求。
更多奇技淫巧,我本身还在探索与试用,若是体验不错的话以后再更新。
stay tuned