偵錯工具擴充功能
Visual Studio Code 的偵錯架構讓擴充功能作者能夠輕鬆地將現有的偵錯工具整合到 VS Code 中,同時讓所有偵錯工具都擁有通用的使用者介面。
VS Code 隨附一個內建的偵錯工具擴充功能,即 Node.js 偵錯工具擴充功能,它完美地展示了 VS Code 支援的眾多偵錯功能
此螢幕截圖顯示了下列偵錯功能
- 偵錯組態管理。
- 用於啟動/停止和逐步執行的偵錯動作。
- 來源、函式、條件、行內中斷點和記錄點。
- 堆疊追蹤,包括多執行緒和多程序支援。
- 在檢視和浮動視窗中瀏覽複雜的資料結構。
- 變數值顯示在浮動視窗中或行內於原始碼中。
- 管理監看式運算式。
- 用於互動式評估和自動完成的偵錯主控台。
本文件將協助您建立偵錯工具擴充功能,使任何偵錯工具都能與 VS Code 搭配運作。
VS Code 的偵錯架構
VS Code 實作了通用的 (與語言無關的) 偵錯工具 UI,此 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 結構描述。VS Code 使用此結構描述來驗證 launch.json 編輯器中的組態,並提供 IntelliSense。請注意,JSON 結構描述建構
$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 Marketplace 安裝 預先建置的版本 並試用看看
- 切換到 [擴充功能] 檢視面板,並輸入 "mock" 以搜尋 Mock Debug 擴充功能,
- 「安裝」並「重新載入」擴充功能。
若要試用 Mock Debug
- 建立新的空白資料夾
mock test
,並在 VS Code 中開啟它。 - 建立檔案
readme.md
並輸入幾行任意文字。 - 切換到 [執行和偵錯] 檢視 (⇧⌘D (Windows、Linux Ctrl+Shift+D)),然後選取 [建立 launch.json 檔案] 連結。
- VS Code 將讓您選取「偵錯工具」以建立預設啟動組態。選取「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 模組。
src/mockRuntime.ts
是具有簡單偵錯 API 的 模擬 執行階段。- 調整 執行階段以符合偵錯工具配接器協定的程式碼位於
src/mockDebug.ts
中。您可以在這裡找到各種 DAP 要求的處理常式。 - 由於偵錯工具擴充功能的實作位於偵錯工具配接器中,因此完全不需要擴充功能程式碼 (即在擴充功能主機程序中執行的程式碼)。但是,Mock Debug 有一個小的
src/extension.ts
,因為它說明了在偵錯工具擴充功能的擴充功能程式碼中可以完成哪些工作。
現在,藉由選取 [擴充功能] 啟動組態並按下 F5
來建置和啟動 Mock Debug 擴充功能。最初,這會將 TypeScript 原始碼完整轉譯到 out
資料夾中。完整建置完成後,會啟動 監看器工作,以轉譯您所做的任何變更。
轉譯原始碼後,會出現一個標示為「[擴充功能開發主機]」的新 VS Code 視窗,其中 Mock Debug 擴充功能現在以偵錯模式執行。從該視窗中,開啟您的 mock test
專案和 readme.md
檔案,使用 'F5' 啟動偵錯工作階段,然後逐步執行它
由於您是以偵錯模式執行擴充功能,因此您現在可以在 src/extension.ts
中設定和命中中斷點,但如我先前所述,擴充功能中沒有太多有趣的程式碼正在執行。有趣的程式碼在偵錯工具配接器中執行,而偵錯工具配接器是個別的程序。
為了偵錯偵錯工具配接器本身,我們必須以偵錯模式執行它。最簡單的方法是以 伺服器模式 執行偵錯工具配接器,並將 VS Code 設定為連線到它。在您的 VS Code vscode-mock-debug 專案中,從下拉式選單中選取 [伺服器] 啟動組態,然後按下綠色的 [開始] 按鈕。
由於我們已經有擴充功能的有效偵錯工作階段,因此 VS Code 偵錯工具 UI 現在進入 多工作階段 模式,這會藉由在 [呼叫堆疊] 檢視中顯示 擴充功能 和 伺服器 這兩個偵錯工作階段的名稱來指示
現在我們可以同時偵錯擴充功能和 DA。更快速到達這裡的方式是使用 [擴充功能 + 伺服器] 啟動組態,它會自動啟動這兩個工作階段。
如需偵錯擴充功能和 DA 的替代方法 (甚至更簡單),請參閱 下方。
在檔案 src/mockDebug.ts
中方法 launchRequest(...)
的開頭設定中斷點,然後作為最後一個步驟,將 debugServer
屬性新增至您的 mock test 啟動組態,以設定 mock 偵錯工具連線到 DA 伺服器 (連接埠 4711
)
{
"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
中偵錯工具配接器的模擬實作取代為一些與「真實」偵錯工具或執行階段通訊的程式碼。這牽涉到理解和實作偵錯工具配接器協定。您可以在 這裡 找到更多關於這方面的詳細資訊。
偵錯工具擴充功能的 package.json 剖析
除了提供偵錯工具配接器的偵錯工具特定實作之外,偵錯工具擴充功能還需要一個 package.json
,它會貢獻給各種與偵錯相關的貢獻點。
因此,讓我們更仔細地看一下 Mock Debug 的 package.json
。
與每個 VS Code 擴充功能一樣,package.json
宣告了擴充功能的基礎屬性 name、publisher 和 version。使用 categories 欄位可讓擴充功能更容易在 VS Code 擴充功能 Marketplace 中找到。
{
"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
屬性的結構描述。此結構描述用於驗證 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-debugadapter
npm 模組為基礎的偵錯工具配接器實作自動支援此伺服器模式。DebugAdapterInlineImplementation
:此物件將偵錯工具配接器描述為實作vscode.DebugAdapter
介面的 JavaScript 或 Typescript 物件。以vscode-debugadapter
npm 模組 1.38-pre.4 或更新版本為基礎的偵錯工具配接器實作自動實作此介面。
Mock Debug 顯示了 三種類型的 DebugAdapterDescriptorFactory 的範例,以及它們如何 針對 'mock' 偵錯類型註冊。要使用的執行模式可以透過將全域變數 runMode
設定 為可能的值 external
、server
或 inline
之一來選取。
對於開發而言,inline
和 server
模式特別有用,因為它們允許在單一程序中偵錯擴充功能和偵錯工具配接器。