調試器是個大騙子!

我叫GDB,是一個調試器,程序員通過我可以調試他們編寫的軟件,分析其中的bug。

作爲一個調試器,調試分析是我的看家本領,像是給目標進程設置斷點,或者讓它單步執行,又或是查看進程中的變量、內存數據、CPU的寄存等等操作,我都手到擒來。

你只要輸入對應的命令,我就能幫助你調試你的程序。

我之所以有這些本事,都得歸功於一個強大的系統函數,它的名字叫ptrace。

不管是開始調試進程,還是下斷點、讀寫進程數據、讀寫寄存器,我都是通過這個函數來進行,要是沒了它,我可就廢了。

它的第一個參數是一個枚舉型的變量,表示要執行的操作,我支持的調試命令很多都是靠它來實現的:

你可以通過我來啓動一個新的進程調試,我會使用fork創建出一個新的子進程,然後在子進程中通過execv來執行你指定的程序。

不過在執行你的程序之前,我會在子進程中調用ptrace函數,然後指定第一個參數爲PTRACE_TRACEME,這樣一來,我就能監控子進程中發生的事情了,也才能對你指定的程序進行調試。

你也可以讓我attach到一個已經運行的進程分析,這樣的話,我直接調用ptrace函數,並且指定第一個參數爲PTRACE_ATTACH就可以了,然後我就會變成那個進程的父進程。

具體要選擇哪種方式來調試,這就看你的需要了。不過不管哪種方式,最終我都會“接管”被調試的進程,它裏面發生的各種信號事件我都能得到通知,方便我對它進行調試操作。

軟件斷點
作爲一個調試器,最常用的功能就是給程序下斷點了。

你可以通過break命令告訴我,你要在程序的哪個位置添加斷點。

當我收到你的命令之後,我會偷偷把被調試進程中那個位置的指令修改爲一個0xCC,這是一條特殊指令的CPU機器碼——int 3,是x86架構CPU專門用來支持調試的指令。

我的這個修改是偷偷進行的,你如果通過我來查看被調試進程的內存數據,或者在反彙編窗口查看那裏的指令,會發現跟之前一樣,這其實是我使的障眼法,讓你看起來還是原來的數據,實際上已經被我修改過了,你要是不信,你可以另外寫個程序來查看那裏的數據內容,看看我說的是不是真的。

一旦被調試的進程運行到那個位置,CPU執行這條特殊的指令時,會陷入內核態,然後取出中斷描述符表IDT中的3號表項中的處理函數來執行。

IDT中的內容,操作系統一啓動早就安排好了,所以系統內核會拿到CPU的執行權,隨後內核會發送一個SIGTRAP信號給到被調試的進程。

而因爲我的存在,這個信號會被我截獲,我收到以後會檢查一下是不是程序員之前下的斷點,如果是的話,就會顯示斷點觸發了,然後等待程序員的下一步指示。

在沒有下一步指示之前,被調試的進程都不會進入就緒隊列被調度執行。

直到你使用continue命令告訴我繼續,我再偷偷把替換成int 3的指令恢復,然後我再次調用ptrace函數告訴操作系統讓它繼續運行。

這就是我給程序下斷點的祕密。

不知道你有沒有發現一個問題,當我把替換的指令恢復後讓它繼續運行,以後就再也不會中斷在這裏了,可程序員並沒有撤銷這個斷點,而是希望每次執行到這裏都能中斷,這可怎麼辦呢?

我有一個非常巧妙的辦法,就是讓它單步執行,只執行一條指令,然後又會中斷到我這裏,但這時候我並不會通知程序員,而僅僅是把剛纔恢復的斷點又給打上(替換指令),然後就繼續運行。這一切都發生的神不知鬼不覺,程序員根本察覺不到。

單步調試
說到單步執行,應該算是程序員調試程序的時候除了下斷點之外最常見的操作了,每一次只讓被調試的進程運行一條指令,這樣方便跟蹤排查問題。

你可能很好奇我是如何讓它單步執行的呢?

單步執行的實現可比下斷點簡單多了,我不用去修改被調試進程內存中的指令,只需要調用ptrace函數,傳遞一個PTRACE_SINGLESTEP參數就行了,操作系統會自動把它設置爲單步執行的模式。

我也很好奇操作系統是怎麼辦到的,就去打聽了一下。

原來x86架構CPU有一個標誌寄存器,名叫eflags,它裏面不止包含了程序運行的一些狀態,還有一些工作模式的設定。

其中就有一個TF標記,用來告訴CPU進入單步執行模式,只要把這個標記爲設置爲1,CPU每執行一條指令,就會觸發一次調試異常,調試異常的向量號是1,所以觸發的時候,都會取出IDT中的1號表項中的處理函數來執行。

接下來的事情就跟命中斷點差不多了,我會截獲到內核發給被調試進程的SIGTRAP信號,然後等待程序員的下一步指令。

如果你繼續進行單步調試,那我便繼續重複這個過程。

如果你有程序的源代碼,你還可以進行源碼級別的單步調試,不過這裏的單步就指的是源代碼中的一行了。

這種情況下要稍微麻煩一點,我還要分析出每一行代碼對應的指令有哪些,然後用上面說的單步執行指令的方法,一條條指令快速掠過,直到這一行代碼對應的指令都執行完成。

內存斷點
有的時候,直接給程序中代碼的位置下斷點並不能包治百病。比如程序員發現某個內存地址的內容老是莫名其妙被修改,想知道到底是哪個函數乾的,這時候連地址都沒有,根本沒法下斷點。

單步執行也不行,那麼多條指令,得執行到猴年馬月去才能找到?

不用擔心,我可以幫你解決這個煩惱。

你可以通過watch命令告訴我,讓我監視被調試進程中某個內存地址的數據變化,一旦發現被修改,我都會把它給停下來報告給你。

猜猜我是如何做到的呢?

我可以用單步執行的方式,每執行一步,就檢查一下內容有沒有沒修改,一旦發現就停下來通知你們程序員。

不過這種方式實在是太麻煩了,會嚴重拖垮被調試進程的性能。

好在x86架構的CPU提供了硬件斷點的能力,幫我解決了大問題。

在x86架構CPU的內部內置了一組調試寄存器,從DR0到DR7,總共8個。通過在DR0-DR3中設置要監控的內存地址,然後在DR7中設置要監控的模式,是讀還是寫,剩下的交給CPU就好了。

CPU執行的時候,一旦發現有符合調試寄存器中設置的情況發生時,就會產生調試異常,然後取出IDT中的1號表項中的處理函數來執行,接下來的事情就跟單步調試產生的異常差不多了。

CPU內部依靠硬件電路來完成監控,可比我們軟件一條一條的檢查快多了!

現在,你不止可以使用watch命令來監控內存被修改,還可以使用rwatch、awatch命令來告訴我去監控內存被讀或者被寫。

我叫GDB,是你調試程序的好夥伴,現在你該知道我是如何工作的了吧!

【完】

這裏是編程技術宇宙,一個專注用故事分享硬核又有趣計算機知識的公衆號~

覺得不錯的話,歡迎一鍵三連哦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章