開發一個 Linux 調試器(七):源碼級斷點
這篇文章將會添加源碼級斷點到我們的調試器中。通過所有我們已經支持的功能,這要比起最初聽起來容易得多。我們還將添加一個命令來獲取符號的類型和地址,這對於定位代碼或數據以及理解鏈接概念非常有用。
-- Simon Brand
本文導航
系列索引
03%
斷點
08%
DWARF
08%
函數入口
10%
源碼行
26%
符號查找
39%
添加命令
84%
測試一下
92%
編譯自https://blog.tartanllama.xyz/c++/2017/06/19/writing-a-linux-debugger-source-break/
作者Simon Brand
譯者geekpi
在內存地址上設置斷點雖然不錯,但它並沒有提供最方便用戶的工具。我們希望能夠在源代碼行和函數入口地址上設置斷點,以便我們可以在與代碼相同的抽象級別中進行調試。
這篇文章將會添加源碼級斷點到我們的調試器中。通過所有我們已經支持的功能,這要比起最初聽起來容易得多。我們還將添加一個命令來獲取符號的類型和地址,這對於定位代碼或數據以及理解鏈接概念非常有用。
系列索引
隨著後面文章的發布,這些鏈接會逐漸生效。
準備環境
[1]
斷點
[2]
寄存器和內存
[3]
Elves 和 dwarves
[4]
源碼和信號
[5]
源碼級逐步執行
[6]
源碼級斷點
[7]
調用棧
[8]
讀取變數
之後步驟
斷點
DWARF
Elves 和 dwarves
[9]
這篇文章,描述了 DWARF 調試信息是如何工作的,以及如何用它來將機器碼映射到高層源碼中。回想一下,DWARF 包含了函數的地址範圍和一個允許你在抽象層之間轉換代碼位置的行表。我們將使用這些功能來實現我們的斷點。
函數入口
如果你考慮重載、成員函數等等,那麼在函數名上設置斷點可能有點複雜,但是我們將遍歷所有的編譯單元,並搜索與我們正在尋找的名稱匹配的函數。DWARF 信息如下所示:
我們想要匹配 並使用 (函數的起始地址)來設置我們的斷點。
這代碼看起來有點奇怪的唯一一點是 。 問題是函數的 不指向該函數的用戶代碼的起始地址,它指向 prologue 的開始。編譯器通常會輸出一個函數的 prologue 和 epilogue,它們用於執行保存和恢復堆棧、操作堆棧指針等。這對我們來說不是很有用,所以我們將入口行加一來獲取用戶代碼的第一行而不是 prologue。DWARF 行表實際上具有一些功能,用於將入口標記為函數 prologue 之後的第一行,但並不是所有編譯器都輸出它,因此我採用了原始的方法。
源碼行
要在高層源碼行上設置一個斷點,我們要將這個行號轉換成 DWARF 中的一個地址。我們將遍歷編譯單元,尋找一個名稱與給定文件匹配的編譯單元,然後查找與給定行對應的入口。
DWARF 看上去有點像這樣:
所以如果我們想要在 的第五行設置一個斷點,我們將查找與行 () 相關的入口並設置一個斷點。
我這裡做了 hack,這樣你可以輸入 代表 。當然你實際上應該使用大小寫敏感路徑處理庫或者其它東西,但是我比較懶。 是檢查行表入口是否被標記為一個語句的開頭,這是由編譯器根據它認為是斷點的最佳目標的地址設置的。
符號查找
當我們在對象文件層時,符號是王者。函數用符號命名,全局變數用符號命名,你得到一個符號,我們得到一個符號,每個人都得到一個符號。 在給定的對象文件中,一些符號可能引用其他對象文件或共享庫,鏈接器將從符號引用創建一個可執行程序。
可以在正確命名的符號表中查找符號,它存儲在二進位文件的 ELF 部分中。幸運的是, 有一個不錯的介面來做這件事,所以我們不需要自己處理所有的 ELF 的事情。為了讓你知道我們在處理什麼,下面是一個二進位文件的 部分的轉儲,它由 生成:
你可以在對象文件中看到用於設置環境的很多符號,最後還可以看到 符號。
我們對符號的類型、名稱和值(地址)感興趣。我們有一個該類型的 枚舉,並使用一個 作為名稱, 作為地址:
我們需要將從 獲得的符號類型映射到我們的枚舉,因為我們不希望依賴關係破環這個介面。幸運的是,我為所有的東西選了同樣的名字,所以這樣很簡單:
最後我們要查找符號。為了說明的目的,我循環查找符號表的 ELF 部分,然後收集我在其中找到的任意符號到 中。更智能的實現可以建立從名稱到符號的映射,這樣你只需要查看一次數據就行了。
添加命令
一如往常,我們需要添加一些更多的命令來向用戶暴露功能。對於斷點,我使用 GDB 風格的介面,其中斷點類型是通過你傳遞的參數推斷的,而不用要求顯式切換:
-> 斷點地址
-> 斷點行號
-> 斷點函數名
對於符號,我們將查找符號並列印出我們發現的任何匹配項:
測試一下
在一個簡單的二進位文件上啟動調試器,並設置源代碼級別的斷點。在一些 函數上設置一個斷點,看到我的調試器停在它上面是我這個項目最有價值的時刻之一。
符號查找可以通過在程序中添加一些函數或全局變數並查找它們的名稱來進行測試。請注意,如果你正在編譯 C++ 代碼,你還需要考慮名稱重整
[10]
。
本文就這些了。下一次我將展示如何向調試器添加堆棧展開支持。
你可以在這裡
[11]
找到這篇文章的代碼。
via: https://blog.tartanllama.xyz/c++/2017/06/19/writing-a-linux-debugger-source-break/
作者:Simon Brand
[12]
譯者:geekpi校對:wxy
本文由LCTT原創編譯,Linux中國榮譽推出
LCTT 譯者
geekpi
共計翻譯:544篇
貢獻時間:1219 天
※Docker 引擎的 Swarm 模式:入門教程
※創建更好的災難恢復計劃
※使用 OctoDNS 啟用 DNS 分割權威
※Kubernetes 是什麼?
※Oracle 終於幹掉了 Sun!
TAG:Linux中國 |