調試程序時,設置斷點的原理是什麼?

對於程序員來說,debug的時間往往比寫程序的時間還要長。尤其對我這種專寫bug為主的程序員來說,一個好的調試器意味著早點下班和休息。現在方便的調試器很多,有著名的Visual Studio(VS)等IDE,也有免費的Windbg和GDB等等。加個斷點也很簡單,就是按一下鍵而已。但你有沒有想過,調試器Debugger並不能控制程序的執行順序,為什麼它可以讓CPU在需要的地方停住呢?

今天我們就來揭開調試斷點的神秘面紗,並通過一個實例來看看調試器實際都做了些什麼。調試器能夠隨心所欲的停止程序的執行,主要通過軟體斷點和硬體斷點兩種方式。

軟體斷點

軟體斷點在X86系統中就是指令INT 3,它的二進位代碼opcode是0xCC。當程序執行到INT 3指令時,會引發軟體中斷。操作系統的INT 3中斷處理器會尋找註冊在該進程上的調試處理程序。從而像Windbg和VS等等調試器就有了上下其手的機會。

我們先通過一個例子來看看調試器都倒了什麼鬼:

#include int main (){// This loop takes some time so that we// get a chance to examine the address of// the breakpoint at the second printffor (int i = 1; i < 100000000; i++) printf("Hello World!");for (int i = 1; i < 10000000; i++) printf("Hello World!");return 0;}

這是一個比較傻的Hello World程序。我們用Windbg打開它,並設置一個斷點:

這時Windbg會將自己Attach到該程序的進程,通過程序PE文件的debug節找到調試信息。在調試信息裡面找到加斷點行所在的機器代碼,並把頭一個位元組用WriteProcessMemory()函數換成0xCC(INT 3)。

讓我們來驗證一下:

推薦點開全屏看,可能更清楚

注意左邊是Windbg窗口,右邊是用Process view打開的進程空間,左右的紅框是對應的。在我們設置斷點之前,左右的內容是完全一樣的,這裡要特別注意printf編譯出來的第一個二進位代碼0x68。接下來我們設置斷點,並開始運行,那100萬個printf讓我們有充分的時間,看看發生了什麼:

我們會發現push操作代碼0x68600e2900的第一個位元組被windbg換成了0xCC也就是INT 3。這樣windbg就可以在執行到這裡時被調度。

不一會,windbg的斷點到了:

到達斷點後,操作符又被還原為0x68,似乎什麼都沒有發生,用戶被蒙在鼓裡,是不是很有意思?

實際上,一般情況下,調試器維護了一大組調試斷點,在並把他們都換成了INT 3。在被調度回來後,會都填回去,並通過現在的地址判斷是到了那個斷點。軟體斷點沒有數目限制。

硬體斷點

X86系統提供8個調試寄存器(DR0~DR7)和2個MSR用於硬體調試。其中前四個DR0~DR3是硬體斷點寄存器,可以放入內存地址或者IO地址,還可以設置為執行、修改等條件。CPU在執行的到這裡並滿足條件會自動停下來。

硬體斷點十分強大,但缺點是只有四個,這也是為什麼所有調試器的硬體斷點只能設置4個原因。我們在調試不能修改的ROM時,只能選擇這個,所以要省著點用,在一般情況下還是盡量選擇軟體斷點。

還有個INT 1是單步調試命令,這裡略過。

其他

Visual Studio有個有趣的特性是debug編譯後,會把0xcc(INT 3)填入代碼的空隙,這樣一旦程序越界就會被VS捕捉而容易發現錯誤。而0xCCCC在中國的GBK編碼是「燙」。有中國程序員翻看內存到代碼段會發現很多"燙燙燙",不明所以,以為發生了什麼神奇的事情。

有些程序越界也會打出"燙燙燙":

有的用戶被嚇得夠嗆,以為計算機過熱了,喊燙了,趕緊關機,十分搞笑。

歡迎大家關注本專欄和用微信掃描下方二維碼加入微信公眾號"UEFIBlog",在那裡有最新的文章。同時歡迎大家給本專欄和公眾號投稿!


推薦閱讀:

請問mysql「字元查詢」和「數字查詢「的效率問題?
好好用滑鼠
如果程序語言的賦值變為左值賦予右值會更好嗎?
從集裝箱發展歷史看DevOps發展進程
c++中如何為一個程序寫擴展?

TAG:程序 | 計算機 | 編程 |