通常的調試流程其實很簡單:發現問題,穩定復現,肯定臨界條件,定位問題,修復問題,覈查結果。迭代這個過程,造成一個閉環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