查看更多
去年deepin社區發佈了自己的IDE:deepin-IDE,當時得到了眾多開源社區用户的廣泛關注,本文試着將 deepin-IDE “調試”部分的一些實現方法與大家進行分享。
deepin-IDE 的調試功能是選用 DAP(Debug Adapter Protocol )調試適配協議實現的,整體架構是圍繞該協議搭建。
什麼是 DAP 協議
DAP 即調試適配協議( Debug Adapter Protocol ),顧名思義,它是用來對多種調試器進行抽象統一的適配層,將原有 IDE 和調試工具直接交互的模式更改為和 DAP 進行交互。該模式可以讓 IDE 集成多種調試器變得更簡單,且靈活性更好。
在 IDE 中的調試功能有許多小功能組成,包括單步執行、斷點、查看變量值等,常規的實現方式是在每個 IDE 中去實現這些邏輯,且因為調試工具的接口不同,還需要為每個調試工具做一些適配工作,這將導致大量且重複的工作,如下圖所示:
調試適配器協議背後的想法是標準化一個抽象協議,用於開發工具如何與具體調試器通信。這個思想和 LSP(Language Server Protocol)和 BSP(Build Server Protocol)類似,都是通過協議去統一相同功能在不同工具之間的差異性。其所處位置如下圖所示,其中左邊為不同的開發工具,右邊為不能同的調試器,不同於開發工具和調試器直接交互的方式,DAP 將這些交互統一了起來,讓開發工具和調試工具都面向 DAP 編程。
上圖中的交互是通過協議進行,所以不會像通過 API 的方式存在語言限制,可以更好的適應調試器的集成。
DAP 如何工作
以下部分解釋了開發工具(例如 IDE 或編輯器)和調試適配器之間的交互,包括具體的協議格式説明、交互流程等。
調試會話
開發工具有兩種基礎的方式和調試器進行交互,分別是:
【單會話模式】
在這種模式下,開發工具啓動一個調試適配器作為一個單獨的進程並且通過標準的std接口進行通信。在調試會話的結束時調試適配器就終止,對於當前的調試會話,開發工具往往需要實現多個調試適配。
【多會話模式】
在這種模式下,開發工具不會啓動調試適配器,而是假定它已經在運行並且會在特定端口上偵聽連接嘗試,對於每個調試會話,開發工具在特定端口上啓動一個新的通信會話並在會話結束時斷開連接。
在與調試適配器建立連接後,開發工具和調試適配器之間通過基礎協議進行通信。
基礎協議
基礎協議由兩部分組成,包括頭和內容(類似於 HTTP),頭部和內容部分通過“\r\n”進行分割:
【協議頭】
協議頭部分由字段組成, 每個頭字段由一個鍵和一個值組成,用‘:’(一個冒號和一個空格)分隔, 每個頭字段都以“\r\n“結尾。由於最後一個協議頭字段和整個協議頭本身都以 \r\n 終止,並且由於協議頭是強制性的,所以消息的內容部分總是在(並唯一標識)兩個 \r\n 序列之前。當前只支持一個協議頭字段:
| 頭字段名 | 值類型 | 描述 |
|---|---|---|
| Content-Length | 數字 | 這個字段是必須的,用來記錄內容字段的長度,單位是字節。 |
協議頭部分使用的是“ASCII”編碼。
【內容部分】
內容部分包含了實際要傳輸的數據,這些數據用 JSON 格式來描述請求、響應和事件。內容部分用的是 utf-8 編碼
為了有個具體的認識,這裏舉個簡單的例子。在調試過程中,開發人員經常會使用到下一步操作,在 DAP 中其協議為:
Content-Length: 119\r\n
\r\n
{
"seq": 153,
"type": "request",
"command": "next",
"arguments": {
"threadId": 3
}
}
類型是“請求”,命令是下一步,參數部分可以攜帶多個,這裏是用的線程Id。 這個協議看着挺簡單的,是吧?接下來就講講如何使用它。
使用方法
詳細的使用方法這裏就不涉及,因為用一個時序圖就可以説明:
可以看到,初始化、請求、響應等必要的步驟都在圖中。其中調試適配器可以理解為調試器的抽象,調試功能的最終執行者是由對應語言的調試工具實現的。
在 deepin-IDE 中的實現
在 deepin-IDE 中,調試功能的實現是結合 cppdap + debugmanager 實現的。
cppdap 是一款基於 C++ 開發的 SDK,基本實現了 DAP 的全量協議。 deepin-IDE 的客户端和服務端都是應用的該 SDK 進行開發,據此可以實現以下功能:
- 通信功能,包括服務端的 TCP 監聽,客户端的 TCP 連接等;
- DAP 協議的封裝,並實現協議的串行化和解串行化;
- 提供註冊回調功能,從而可以在回調內處理各種事件、請求等;
它的層級結構如下:
用 cppdap 可以減少客户端和服務端不少工作量,也統一了兩邊的協議數據。而 debugmanager 可以理解為調試器的抽象,包含所有必要的調試要素。整體結構如下:
左邊是客户端,右邊是服務端,內部實現如下:
客户端實現
客户端包含了兩個個主要功能,一個是和 DAP 服務端進行交互,發送調試命令或處理返回的數據;另一個是將DAP 數據轉換後顯示到用户界面,並響應界面發送的事件。概括起來就包含業務模塊、事件模塊、DAP 模塊和界面4個部分。
- 業務模塊包含了插件類、調試參數、調試管理類等,其中插件類負責插件加載、初始化、獲取上下文等,調試管理類用來組合事件、DAP、界面幾個模塊。 事件模塊
- 事件模塊包含兩個子模塊,分別是事件發送和事件接收,比如頁面跳轉事件、添加\移除斷點事件等。
- DAP 模塊基於 cppdap 開發,採用層級結構,底層是原始 DAP 協議封裝,中間層是針對業務做的進一步封裝,簡化了向外提供的接口,最上層是對整個調試功能的整合,包括數據緩存、界面元素、命令收發。
- 界面模塊包含堆棧界面、變量界面、斷點列表、異步對話框等,用於 DAP 的數據展示。
如上圖所示,灰色部分為 DAP 客户端的界面呈現。
服務端實現
服務端的功能分為兩個部分,一個是基於 cppdap 實現命令的收發,另一個是與 gdb 交互,實現調試程序的啓動、暫停、退出等一系列動作。
- 和客户端一樣,服務端也是基於cppdap實現的通信和協議封裝和解析。 調試工具
- 和調試工具的交互是通過進程調用的方式實現,接收進程輸出得到返回信息。如果調試工具本身支持 DAP 協議,則可以直接交互。
附錄/參考文檔:
(1)DAP:https://microsoft.github.io/debug-adapter-protocol/overview
(2)deepin IDE 使用參考:https://wiki.deepin.org/zh/05_HOW-TO/02_%E5%BC%80%E5%8F%91%E7%9B%B8%E5%85%B3/deepin-unioncode
(3)https://distrowatch.com/table.php?distribution=deepin