偵錯程式擴充套件
Visual Studio Code 的除錯體系結構允許擴充套件作者輕鬆地將現有偵錯程式整合到 VS Code 中,同時保持與所有偵錯程式一致的使用者介面。
VS Code 內建了一個偵錯程式擴充套件,即 Node.js 偵錯程式擴充套件,它是 VS Code 所支援的許多偵錯程式功能的絕佳展示。

此螢幕截圖顯示了以下除錯功能
- 除錯配置管理。
- 用於啟動/停止和單步執行的除錯操作。
- 原始碼、函式、條件、行內斷點和日誌點。
- 堆疊跟蹤,包括多執行緒和多程序支援。
- 在檢視和懸停提示中導航複雜資料結構。
- 在懸停提示中或在原始碼中內聯顯示的變數值。
- 監視表示式管理。
- 具有自動完成功能的互動式求值的除錯控制檯。
本文件將幫助您建立一個偵錯程式擴充套件,使任何偵錯程式都能與 VS Code 一起工作。
VS Code 的除錯架構
VS Code 基於我們引入的用於與偵錯程式後端通訊的抽象協議,實現了通用的(與語言無關)偵錯程式 UI。由於偵錯程式通常不實現此協議,因此需要一箇中介來“適配”偵錯程式到協議。這個中介通常是一個與偵錯程式通訊的獨立程序。

我們將此中介稱為 **除錯介面卡** (或簡稱 **DA**),DA 和 VS Code 之間使用的抽象協議稱為 **除錯介面卡協議** (或簡稱 **DAP**)。由於除錯介面卡協議獨立於 VS Code,因此它有自己的 網站,您可以在那裡找到 介紹和概述、詳細的 規範,以及一些 已知實現和支援工具 的列表。DAP 的歷史和動機在此 部落格文章 中有解釋。
由於除錯介面卡獨立於 VS Code,並且可以用於 其他開發工具,因此它們不符合 VS Code 基於擴充套件和貢獻點的可擴充套件性架構。
因此,VS Code 提供了一個名為 debuggers 的貢獻點,您可以在此為特定的除錯型別(例如 Node.js 偵錯程式的 node)貢獻一個除錯介面卡。當用戶啟動該型別的除錯會話時,VS Code 會啟動註冊的 DA。
因此,在最簡化的形式下,偵錯程式擴充套件僅僅是除錯介面卡實現的宣告式貢獻,而擴充套件本身就是一個用於打包除錯介面卡的容器,沒有任何額外的程式碼。

更現實的偵錯程式擴充套件會向 VS Code 貢獻許多或全部以下宣告式項
- 偵錯程式支援的語言列表。VS Code 會為這些語言啟用設定斷點的 UI。
- 偵錯程式引入的除錯配置屬性的 JSON schema。VS Code 使用此 schema 來驗證 launch.json 編輯器中的配置並提供 IntelliSense。請注意,不支援 JSON schema 的
$ref和definition構造。 - VS Code 建立的初始 launch.json 的預設除錯配置。
- 使用者可以新增到 launch.json 檔案中的除錯配置程式碼片段。
- 可用於除錯配置的變數宣告。
您可以在 contributes.breakpoints 和 contributes.debuggers 參考中找到更多資訊。
除了上述純粹的宣告式貢獻外,除錯擴充套件 API 還支援以下基於程式碼的功能
- VS Code 建立的初始 launch.json 的動態生成的預設除錯配置。
- 動態確定要使用的除錯介面卡。
- 在將除錯配置傳遞給除錯介面卡之前對其進行驗證或修改。
- 與除錯介面卡通訊。
- 向除錯控制檯傳送訊息。
在本文件的其餘部分,我們將展示如何開發偵錯程式擴充套件。
Mock Debug 擴充套件
由於從頭開始建立除錯介面卡對於本教程來說有點繁重,我們將從一個簡單的 DA 開始,我們已經將其建立為一個教育性的“除錯介面卡入門工具包”。它被稱為Mock Debug,因為它不與真實的偵錯程式通訊,而是模擬一個。Mock Debug 模擬了一個偵錯程式,並支援步進、繼續、斷點、異常和變數訪問,但它沒有連線到任何真實的偵錯程式。
在深入研究 mock-debug 的開發設定之前,讓我們先從 VS Code 市場安裝一個預構建版本並玩一下。
- 切換到擴充套件檢視,鍵入“mock”以搜尋 Mock Debug 擴充套件,
- “安裝”並“重新載入”該擴充套件。
嘗試 Mock Debug
- 建立一個新的空資料夾
mock test,然後在 VS Code 中開啟它。 - 建立一個檔案
readme.md,並輸入幾行任意文字。 - 切換到“執行和除錯”檢視(⇧⌘D (Windows、Linux Ctrl+Shift+D)),然後選擇 **建立 launch.json 檔案** 連結。
- VS Code 會讓你選擇一個“偵錯程式”來建立一個預設的 launch 配置。選擇“Mock Debug”。
- 按下綠色的 **開始** 按鈕,然後按 Enter 確認建議的檔案
readme.md。
除錯會話啟動,你可以“步進”瀏覽 readme 檔案,設定和命中斷點,並遇到異常(如果 exception 這個詞出現在某一行)。

在使用 Mock Debug 作為你自己的開發起點之前,我們建議先解除安裝預構建版本。
- 切換到擴充套件檢視,然後點選 Mock Debug 擴充套件的齒輪圖示。
- 執行“解除安裝”操作,然後“重新載入”視窗。
Mock Debug 的開發設定
現在讓我們獲取 Mock Debug 的原始碼,並在 VS Code 中開始對其進行開發。
git clone https://github.com/microsoft/vscode-mock-debug.git
cd vscode-mock-debug
yarn
在 VS Code 中開啟專案資料夾 vscode-mock-debug。
包裡有什麼?
package.json是 mock-debug 擴充套件的清單。- 它列出了 mock-debug 擴充套件的貢獻。
compile和watch指令碼用於將 TypeScript 原始碼轉譯到out資料夾,並監視後續的原始碼修改。- 依賴項
vscode-debugprotocol、vscode-debugadapter和vscode-debugadapter-testsupport是 NPM 模組,它們簡化了基於 Node 的除錯介面卡的開發。
src/mockRuntime.ts是一個具有簡單除錯 API 的 *mock* 執行時。- 將執行時適配到除錯介面卡協議的程式碼位於
src/mockDebug.ts。在這裡你可以找到 DAP 各項請求的處理程式。 - 由於偵錯程式擴充套件的實現位於除錯介面卡中,因此不需要任何擴充套件程式碼(即在擴充套件主機程序中執行的程式碼)。然而,Mock Debug 有一個小的
src/extension.ts,因為它演示了偵錯程式擴充套件的擴充套件程式碼中可以做什麼。
現在,透過選擇 **Extension** 啟動配置並按 F5 來構建並啟動 Mock Debug 擴充套件。最初,這會進行一次完整的 TypeScript 原始碼到 out 資料夾的轉譯。在完整構建之後,會啟動一個*監視任務*,該任務會轉譯你所做的任何更改。
轉譯原始碼後,會出現一個新的標記為“[Extension Development Host]”的 VS Code 視窗,其中 Mock Debug 擴充套件正在除錯模式下執行。從該視窗開啟你的 mock test 專案以及 readme.md 檔案,使用 'F5' 啟動除錯會話,然後步進執行。

由於你正在除錯擴充套件,所以現在你可以在 src/extension.ts 中設定和命中斷點,但正如我上面提到的,擴充套件中沒有太多有趣的 [程式碼] 在執行。有趣的 [程式碼] 執行在除錯介面卡中,這是一個單獨的程序。
為了除錯除錯介面卡本身,我們必須在除錯模式下執行它。最簡單的方法是以*伺服器模式*執行除錯介面卡,並配置 VS Code 連線到它。在你的 VS Code vscode-mock-debug 專案中,從下拉選單中選擇 **Server** 啟動配置,然後按綠色啟動按鈕。
由於我們已經有一個活動的擴充套件除錯會話,VS Code 偵錯程式 UI 現在進入*多會話*模式,這可以透過在“呼叫堆疊”檢視中看到兩個除錯會話 **Extension** 和 **Server** 的名稱來指示。

現在我們可以同時除錯擴充套件和 DA。更快地到達這裡的方法是使用 **Extension + Server** 啟動配置,它會自動啟動兩個會話。
在 下面 可以找到一種替代的、更簡單的除錯擴充套件和 DA 的方法。
在檔案 src/mockDebug.ts 的 launchRequest(...) 方法的開頭設定一個斷點,最後一步是透過為埠 4711 新增一個 debugServer 屬性到你的 mock test 啟動配置中,來配置 mock 偵錯程式連線到 DA 伺服器。
{
"version": "0.2.0",
"configurations": [
{
"type": "mock",
"request": "launch",
"name": "mock test",
"program": "${workspaceFolder}/readme.md",
"stopOnEntry": true,
"debugServer": 4711
}
]
}
如果你現在啟動這個除錯配置,VS Code 不會將 mock 除錯介面卡作為一個單獨的程序啟動,而是直接連線到已執行伺服器的本地埠 4711,你應該會命中 launchRequest 中的斷點。
有了這個設定,你現在可以輕鬆地編輯、轉譯和除錯 Mock Debug。
但是現在真正的 [工作] 開始了:你將不得不替換 src/mockDebug.ts 和 src/mockRuntime.ts 中除錯介面卡的 mock 實現,用一些可以與“真實”偵錯程式或執行時通訊的程式碼來代替。這涉及到理解和實現除錯介面卡協議。更多關於這方面的資訊可以在 這裡 找到。
偵錯程式擴充套件的 package.json 解剖
除了提供偵錯程式特定的除錯介面卡實現之外,偵錯程式擴充套件還需要一個 package.json,該檔案向各種與除錯相關的貢獻點進行貢獻。
因此,讓我們仔細看看 Mock Debug 的 package.json。
像每個 VS Code 擴充套件一樣,package.json 聲明瞭擴充套件的基本屬性 **name**、**publisher** 和 **version**。使用 **categories** 欄位可以使擴充套件在 VS Code 擴充套件市場中更容易被發現。
{
"name": "mock-debug",
"displayName": "Mock Debug",
"version": "0.24.0",
"publisher": "...",
"description": "Starter extension for developing debug adapters for VS Code.",
"author": {
"name": "...",
"email": "..."
},
"engines": {
"vscode": "^1.17.0",
"node": "^7.9.0"
},
"icon": "images/mock-debug-icon.png",
"categories": ["Debuggers"],
"contributes": {
"breakpoints": [{ "language": "markdown" }],
"debuggers": [
{
"type": "mock",
"label": "Mock Debug",
"program": "./out/mockDebug.js",
"runtime": "node",
"configurationAttributes": {
"launch": {
"required": ["program"],
"properties": {
"program": {
"type": "string",
"description": "Absolute path to a text file.",
"default": "${workspaceFolder}/${command:AskForProgramName}"
},
"stopOnEntry": {
"type": "boolean",
"description": "Automatically stop after launch.",
"default": true
}
}
}
},
"initialConfigurations": [
{
"type": "mock",
"request": "launch",
"name": "Ask for file name",
"program": "${workspaceFolder}/${command:AskForProgramName}",
"stopOnEntry": true
}
],
"configurationSnippets": [
{
"label": "Mock Debug: Launch",
"description": "A new configuration for launching a mock debug program",
"body": {
"type": "mock",
"request": "launch",
"name": "${2:Launch Program}",
"program": "^\"\\${workspaceFolder}/${1:Program}\""
}
}
],
"variables": {
"AskForProgramName": "extension.mock-debug.getProgramName"
}
}
]
},
"activationEvents": ["onDebug", "onCommand:extension.mock-debug.getProgramName"]
}
現在看看 **contributes** 部分,其中包含特定於除錯擴充套件的貢獻。
首先,我們使用 **breakpoints** 貢獻點來列出啟用斷點設定的語言。沒有這個,就無法在 Markdown 檔案中設定斷點。
接下來是 **debuggers** 部分。在這裡,一個偵錯程式在除錯型別 mock 下被引入。使用者可以在啟動配置中引用此型別。可選屬性 **label** 可用於在 UI 中顯示除錯型別時給它一個好聽的名字。
由於除錯擴充套件使用除錯介面卡,因此為其程式碼提供了一個相對路徑作為 **program** 屬性。為了使擴充套件獨立,應用程式必須位於擴充套件資料夾內。按照慣例,我們將此應用程式保留在名為 out 或 bin 的資料夾中,但您可以自由使用其他名稱。
由於 VS Code 在不同的平臺上執行,我們必須確保 DA 程式也能支援不同的平臺。為此,我們有以下選項
-
如果程式是以平臺無關的方式實現的,例如作為在所有支援的平臺上都可用的執行時上執行的程式,您可以透過 **runtime** 屬性指定該執行時。目前,VS Code 支援
node和mono執行時。我們上面的 Mock 除錯介面卡就使用了這種方法。 -
如果你的 DA 實現需要在不同平臺上使用不同的可執行檔案,**program** 屬性可以針對特定平臺進行限定,如下所示
"debuggers": [{ "type": "gdb", "windows": { "program": "./bin/gdbDebug.exe", }, "osx": { "program": "./bin/gdbDebug.sh", }, "linux": { "program": "./bin/gdbDebug.sh", } }] -
也可以結合使用這兩種方法。下面的例子來自 Mono DA,它實現為一個 mono 應用程式,在 macOS 和 Linux 上需要執行時,但在 Windows 上不需要。
"debuggers": [{ "type": "mono", "program": "./bin/monoDebug.exe", "osx": { "runtime": "mono" }, "linux": { "runtime": "mono" } }]
**configurationAttributes** 聲明瞭此偵錯程式可用的 launch.json 屬性的 schema。此 schema 用於驗證 launch.json,並在編輯啟動配置時支援 IntelliSense 和懸停幫助。
**initialConfigurations** 定義了此偵錯程式的預設 launch.json 的初始內容。當專案沒有 launch.json 並且使用者啟動除錯會話或在“執行和除錯”檢視中選擇 **建立 launch.json 檔案** 連結時,會使用此資訊。在這種情況下,VS Code 會讓使用者選擇一個除錯環境,然後建立相應的 launch.json。

與在 package.json 中靜態定義 launch.json 的初始內容不同,可以透過實現 DebugConfigurationProvider 來動態計算初始配置(有關詳細資訊,請參閱下面的“使用 DebugConfigurationProvider”部分)。
**configurationSnippets** 定義了在編輯 launch.json 時在 IntelliSense 中顯示的啟動配置程式碼片段。按照慣例,在程式碼片段的 label 屬性前加上除錯環境名稱的字首,以便在它出現在許多程式碼片段建議列表中時能夠清晰地識別。
**variables** 貢獻會將“變數”繫結到“命令”。這些變數可以在啟動配置中使用 **${command:xyz}** 語法來使用,並且在啟動除錯會話時,變數會被繫結命令返回的值替換。
命令的實現位於擴充套件中,它可以從一個簡單的無 UI 的表示式,到基於擴充套件 API 中可用的 UI 功能的複雜功能。Mock Debug 將變數 AskForProgramName 繫結到命令 extension.mock-debug.getProgramName。該命令在 src/extension.ts 中的 實現 使用 showInputBox 讓使用者輸入程式名稱。
vscode.commands.registerCommand('extension.mock-debug.getProgramName', config => {
return vscode.window.showInputBox({
placeHolder: 'Please enter the name of a markdown file in the workspace folder',
value: 'readme.md'
});
});
現在該變數可以在啟動配置的任何字串型別的值中使用,形式為 **${command:AskForProgramName}**。
使用 DebugConfigurationProvider
如果 package.json 中除錯貢獻的靜態性質不夠,可以使用 DebugConfigurationProvider 來動態控制除錯擴充套件的以下方面
- 新建立的 launch.json 的初始除錯配置可以動態生成,例如基於工作區中可用的某些上下文資訊。
- 可以在啟動新的除錯會話之前“解析”(或修改)啟動配置。這允許根據工作區中可用的資訊填充預設值。存在兩個*解析*方法:
resolveDebugConfiguration在變數被替換到啟動配置之前呼叫,resolveDebugConfigurationWithSubstitutedVariables在所有變數都被替換之後呼叫。前者必須在驗證邏輯將其他變數插入除錯配置時使用。後者必須在驗證邏輯需要訪問所有除錯配置屬性的最終值時使用。
src/extension.ts 中的 MockConfigurationProvider 實現 resolveDebugConfiguration 來檢測這種情況:當沒有 launch.json 時啟動除錯會話,但活動編輯器中打開了 Markdown 檔案。這是一種典型場景,使用者在編輯器中打開了一個檔案,只想除錯它而不建立 launch.json。
除錯配置提供程式透過 vscode.debug.registerDebugConfigurationProvider 為特定的除錯型別註冊,通常在擴充套件的 activate 函式中。為了確保 DebugConfigurationProvider 足夠早地註冊,必須在除錯功能使用時立即啟用擴充套件。這可以透過在 package.json 中為 onDebug 事件配置擴充套件啟用來實現。
"activationEvents": [
"onDebug",
// ...
],
這個通用的 onDebug 事件會在任何除錯功能被使用時觸發。只要擴充套件的啟動成本很低(即啟動過程不花費太多時間),這就能正常工作。如果除錯擴充套件的啟動成本很高(例如,因為它啟動了一個語言伺服器),onDebug 啟用事件可能會對其他除錯擴充套件產生負面影響,因為它被觸發得相當早,並且不考慮特定的除錯型別。
對於啟動成本高的除錯擴充套件,更好的方法是使用更細粒度的啟用事件
onDebugInitialConfigurations在呼叫DebugConfigurationProvider的provideDebugConfigurations方法之前觸發。onDebugResolve:type在呼叫指定型別的DebugConfigurationProvider的resolveDebugConfiguration或resolveDebugConfigurationWithSubstitutedVariables方法之前觸發。
**經驗法則:** 如果除錯擴充套件的啟用成本很低,則使用 onDebug。如果成本很高,則使用 onDebugInitialConfigurations 和/或 onDebugResolve,具體取決於 DebugConfigurationProvider 是否實現了相應的 provideDebugConfigurations 和/或 resolveDebugConfiguration 方法。
釋出你的偵錯程式擴充套件
建立偵錯程式擴充套件後,您可以將其釋出到 Marketplace。
- 更新
package.json中的屬性,以反映偵錯程式擴充套件的名稱和用途。 - 按照 釋出擴充套件 中的說明上傳到 Marketplace。
開發偵錯程式擴充套件的替代方法
正如我們所見,開發偵錯程式擴充套件通常涉及在兩個並行會話中除錯擴充套件和除錯介面卡。如上所述,VS Code 對此提供了很好的支援,但如果擴充套件和除錯介面卡是同一個程式,可以在一個除錯會話中進行除錯,開發可能會更容易。
只要你的除錯介面卡是用 TypeScript/JavaScript 實現的,這種方法實際上很容易實現。基本思想是直接在擴充套件內部執行除錯介面卡,並讓 VS Code 連線到它,而不是為每個會話啟動一個新的外部除錯介面卡。
為此,VS Code 提供了擴充套件 API 來控制除錯介面卡的建立和執行方式。當除錯會話啟動並且需要除錯介面卡時,DebugAdapterDescriptorFactory 有一個 createDebugAdapterDescriptor 方法會被 VS Code 呼叫。此方法必須返回一個描述除錯介面卡如何執行的描述符物件 (DebugAdapterDescriptor)。
目前 VS Code 支援三種執行除錯介面卡的方式,因此提供三種不同的描述符型別
DebugAdapterExecutable:此物件將除錯介面卡描述為一個外部可執行檔案,並帶有路徑和可選的引數和執行時。可執行檔案必須實現除錯介面卡協議,並透過 stdin/stdout 進行通訊。這是 VS Code 的預設操作模式,如果未顯式註冊DebugAdapterDescriptorFactory,VS Code 會自動使用 package.json 中的相應值使用此描述符。DebugAdapterServer:此物件將除錯介面卡描述為一個執行的伺服器,透過特定的本地或遠端埠進行通訊。基於vscode-debugadapternpm 模組的除錯介面卡實現會自動支援此伺服器模式。DebugAdapterInlineImplementation:此物件將除錯介面卡描述為一個實現vscode.DebugAdapter介面的 JavaScript 或 TypeScript 物件。基於vscode-debugadapternpm 模組版本 1.38-pre.4 或更高版本的除錯介面卡實現會自動實現此介面。
Mock Debug 展示了 三種 DebugAdapterDescriptorFactories 的示例,以及它們如何為“mock”除錯型別註冊。可以透過將全域性變數 runMode 設定為可能值 external、server 或 inline 之一來選擇要使用的執行模式。
對於開發而言,inline 和 server 模式特別有用,因為它們允許在一個程序中同時除錯擴充套件和除錯介面卡。