偵錯程式到底怎樣工作
你也許用過調速器檢查過你的程式碼,但你知道它們是如何做到的嗎?
偵錯程式是大多數(即使不是每個)開發人員在軟體工程職業生涯中至少使用過一次的那些軟體之一,但是你們中有多少人知道它們到底是如何工作的?我在悉尼 linux.conf.au 2018 的演講中,將討論從頭開始編寫偵錯程式……使用 Rust!
在本文中,術語偵錯程式和跟蹤器可以互換。 “被跟蹤者”是指正在被跟蹤器跟蹤的程序。
ptrace 系統呼叫
大多數偵錯程式嚴重依賴稱為 ptrace(2)
的系統呼叫,其原型如下:
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
這是一個可以操縱程序幾乎所有方面的系統呼叫;但是,在偵錯程式可以連線到一個程序之前,“被跟蹤者”必須以請求 PTRACE_TRACEME
呼叫 ptrace
。這告訴 Linux,父程序透過 ptrace
連線到這個程序是合法的。但是……我們如何強制一個程序呼叫 ptrace
?很簡單!fork/execve
提供了在 fork
之後但在被跟蹤者真正開始使用 execve
之前呼叫 ptrace
的簡單方法。很方便地,fork
還會返回被跟蹤者的 pid
,這是後面使用 ptrace
所必需的。
現在被跟蹤者可以被偵錯程式追蹤,重要的變化發生了:
- 每當一個訊號被傳送到被跟蹤者時,它就會停止,並且一個可以被
wait
系列的系統呼叫捕獲的等待事件被傳送給跟蹤器。 - 每個
execve
系統呼叫都會導致SIGTRAP
被傳遞給被跟蹤者。(與之前的專案相結合,這意味著被跟蹤者在一個execve
完全發生之前停止。)
這意味著,一旦我們發出 PTRACE_TRACEME
請求並呼叫 execve
系統呼叫來實際在被跟蹤者(程序上下文)中啟動程式時,被跟蹤者將立即停止,因為 execve
會傳遞一個 SIGTRAP
,並且會被跟蹤器中的等待事件捕獲。我們如何繼續?正如人們所期望的那樣,ptrace
有大量的請求可以用來告訴被跟蹤者可以繼續:
PTRACE_CONT
:這是最簡單的。 被跟蹤者執行,直到它接收到一個訊號,此時等待事件被傳遞給跟蹤器。這是最常見的實現真實世界偵錯程式的“繼續直至斷點”和“永遠繼續”選項的方式。斷點將在下面介紹。PTRACE_SYSCALL
:與PTRACE_CONT
非常相似,但在進入系統呼叫之前以及在系統呼叫返回到使用者空間之前停止。它可以與其他請求(我們將在本文後面介紹)結合使用來監視和修改系統呼叫的引數或返回值。系統呼叫追蹤程式strace
很大程度上使用這個請求來獲知程序發起了哪些系統呼叫。PTRACE_SINGLESTEP
:這個很好理解。如果您之前使用過偵錯程式(你會知道),此請求會執行下一條指令,然後立即停止。
我們可以透過各種各樣的請求停止程序,但我們如何獲得被除錯者的狀態?程序的狀態大多是透過其暫存器捕獲的,所以當然 ptrace
有一個請求來獲得(或修改)暫存器:
PTRACE_GETREGS
:這個請求將給出被跟蹤者剛剛被停止時的暫存器的狀態。PTRACE_SETREGS
:如果跟蹤器之前透過呼叫PTRACE_GETREGS
得到了暫存器的值,它可以在引數結構中修改相應暫存器的值,並使用PTRACE_SETREGS
將暫存器設為新值。PTRACE_PEEKUSER
和PTRACE_POKEUSER
:這些允許從被跟蹤者的USER
區讀取資訊,這裡儲存了暫存器和其他有用的資訊。 這可以用來修改單一暫存器,而避免使用更重的PTRACE_{GET,SET}REGS
請求。
在偵錯程式僅僅修改暫存器是不夠的。偵錯程式有時需要讀取一部分記憶體,甚至對其進行修改。GDB 可以使用 print
得到一個記憶體位置或變數的值。ptrace
透過下面的方法實現這個功能:
PTRACE_PEEKTEXT
和PTRACE_POKETEXT
:這些允許讀取和寫入被跟蹤者地址空間中的一個字。當然,使用這個功能時被跟蹤者要被暫停。
真實世界的偵錯程式也有類似斷點和觀察點的功能。 在接下來的部分中,我將深入體系結構對偵錯程式支援的細節。為了清晰和簡潔,本文將只考慮 x86。
體系結構的支援
ptrace
很酷,但它是如何工作? 在前面的部分中,我們已經看到 ptrace
跟訊號有很大關係:SIGTRAP
可以在單步跟蹤、execve
之前以及系統呼叫前後被傳送。訊號可以透過一些方式產生,但我們將研究兩個具體的例子,以展示訊號可以被偵錯程式用來在給定的位置停止程式(有效地建立一個斷點!):
- 未定義的指令:當一個程序嘗試執行一個未定義的指令,CPU 將產生一個異常。此異常透過 CPU 中斷處理,核心中相應的中斷處理程式被呼叫。這將導致一個
SIGILL
訊號被髮送給程序。 這依次導致程序被停止,跟蹤器透過一個等待事件被通知,然後它可以決定後面做什麼。在 x86 上,指令ud2
被確保始終是未定義的。 - 除錯中斷:前面的方法的問題是,
ud2
指令需要佔用兩個位元組的機器碼。存在一條特殊的單位元組指令能夠觸發一箇中斷,它是int $3
,機器碼是0xCC
。 當該中斷髮出時,核心向程序傳送一個SIGTRAP
,如前所述,跟蹤器被通知。
這很好,但如何我們才能脅迫被跟蹤者執行這些指令? 這很簡單:利用 ptrace
的 PTRACE_POKETEXT
請求,它可以覆蓋記憶體中的一個字。 偵錯程式將使用 PTRACE_PEEKTEXT
讀取該位置原來的值並替換為 0xCC
,然後在其內部狀態中記錄該處原來的值,以及它是一個斷點的事實。 下次被跟蹤者執行到該位置時,它將被透過 SIGTRAP
訊號自動停止。 然後偵錯程式的終端使用者可以決定如何繼續(例如,檢查暫存器)。
好吧,我們已經講過了斷點,那觀察點呢? 當一個特定的記憶體位置被讀或寫,偵錯程式如何停止程式? 當然你不可能為了能夠讀或寫記憶體而去把每一個指令都覆蓋為 int $3
。有一組除錯暫存器為了更有效的滿足這個目的而被設計出來:
DR0
到DR3
:這些暫存器中的每個都包含一個地址(記憶體位置),偵錯程式因為某種原因希望被跟蹤者在那些地址那裡停止。 其原因以掩碼方式被設定在DR7
暫存器中。DR4
和DR5
:這些分別是DR6
和DR7
過時的別名。DR6
:除錯狀態。包含有關DR0
到DR3
中的哪個暫存器導致除錯異常被引發的資訊。這被 Linux 用來計算與SIGTRAP
訊號一起傳遞給被跟蹤者的資訊。DR7
:除錯控制。透過使用這些暫存器中的位,偵錯程式可以控制如何解釋DR0
至DR3
中指定的地址。位掩碼控制監視點的尺寸(監視1、2、4 或 8 個位元組)以及是否在執行、讀取、寫入時引發異常,或在讀取或寫入時引發異常。
由於除錯暫存器是程序的 USER
區域的一部分,偵錯程式可以使用 PTRACE_POKEUSER
將值寫入除錯暫存器。除錯暫存器只與特定程序相關,因此在程序搶佔並重新獲得 CPU 控制權之前,除錯暫存器會被恢復。
冰山一角
我們已經瀏覽了一個偵錯程式的“冰山”:我們已經介紹了 ptrace
,瞭解了它的一些功能,然後我們看到了 ptrace
是如何實現的。 ptrace
的某些部分可以用軟體實現,但其它部分必須用硬體來實現,否則實現代價會非常高甚至無法實現。
當然有很多我們沒有涉及。例如“偵錯程式如何知道變數在記憶體中的位置?”等問題由於空間和時間限制而尚未解答,但我希望你從本文中學到了一些東西;如果它激起你的興趣,網上有足夠的資源可以瞭解更多。
想要了解更多,請檢視 linux.conf.au 中 Levente Kurusa 的演講 Let's Write a Debugger!,於一月 22-26 日在悉尼舉辦。
via: https://opensource.com/article/18/1/how-debuggers-really-work
作者:Levente Kurusa 譯者:stephenxs 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出
相關文章
- Xcode偵錯程式LLDBXCodeLLDB
- Python 偵錯程式入門Python
- PsySH作為偵錯程式
- 反除錯 -- 利用ptrace阻止偵錯程式附加除錯
- Emacs 除錯祕籍之 GUD 偵錯程式Mac除錯
- 偵錯程式是個大騙子!
- GDB偵錯程式(學習筆記)筆記
- Linux gdb偵錯程式用法全面解析Linux
- 另一個Swoole偵錯程式 - Yasd
- Rails開發中使用byebug偵錯程式AI
- 在Docker內部使用gdb偵錯程式報錯-Operation not permittedDockerMIT
- tokio-rs/console:非同步 Rust 偵錯程式非同步Rust
- 如何在Docker內部使用gdb偵錯程式Docker
- 2.IDEA,Maven,偵錯程式的基本使用IdeaMaven
- 磁碟到底是怎樣工作的?一文理解硬碟結構硬碟
- 反除錯&反反除錯 -- 利用sysctl檢測偵錯程式是否存在除錯
- 為什麼在Docker裡使用gdb偵錯程式會報錯Docker
- 幽默:編寫Python程式碼你們使用什麼偵錯程式?Python
- 5 個鮮為人知 GNU 偵錯程式(GDB)技巧
- Flink CDC 3.0 耍起來到底怎麼樣?
- CPU:網路卡老哥,你到底怎麼工作的?
- 優秀開發者必備技能包:Python偵錯程式Python
- SAP技術專家的ABAP偵錯程式培訓材料
- Process Hacker一款功能強大的系統偵錯程式
- tokio-rs/console:非同步Rust執行偵錯程式控制檯非同步Rust
- 在偵錯程式下看Panic機制及oops資訊分析OOP
- 學Python能幹什麼工作?工作前景怎麼樣?Python
- 外牆清洗這件事,到底怎樣才算安全?
- SQLSERVER 的 nolock 到底是怎樣的無鎖?SQLServer
- 工作五年後的程式設計師,一般怎樣了?程式設計師
- 113 出錯程式碼502,是指什麼,怎樣解決
- Windows 11 核心新偵錯程式「GitHub 熱點速覽 v.23.01」WindowsGithub
- RenderDoc圖形偵錯程式詳細使用教程(基於DirectX11)
- UE4藍圖AI角色製作(四)之Gameplay偵錯程式AIGAM
- Linux發展現狀怎麼樣?系統運維工作怎麼樣?Linux運維
- 執行緒池是怎樣工作的?執行緒
- 怎樣做好伺服器運維工作伺服器運維
- Python怎樣忽略warning警告錯誤?Python