簡單總結:有軟件斷點和硬件斷點
軟件斷點:軟件斷點在X86系統中為中斷指令INT 3,其二進制代碼opcode是0xCC。當程序執行到INT 3指令時,會引發軟件中斷。操作系統的INT 3中斷處理器會尋找注冊在該進程上的調試處理程序。從而像Windbg和VS等等調試器就有了上下其手的機會。程序出錯時常看到的”燙燙燙“、”錕斤拷“、”屯屯屯“等與這個終端指令有關
硬件斷點:X86系統提供8個調試寄存器(DR0~DR7)和2個MSR用於硬件調試。
轉自:https://zhuanlan.zhihu.com/p/34003929
以下為原文:
對於程序員來說,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 printf for (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編碼是“燙”。有中國程序員翻看內存到代碼段會發現很多"燙燙燙",不明所以,以為發生了什么神奇的事情。
有些程序越界也會打出"燙燙燙":
有的用戶被嚇得夠嗆,以為計算機過熱了,喊燙了,趕緊關機,十分搞笑。