vscode 代碼調(diào)試器

2022-08-09 10:46 更新

和語言功能一樣,VS Code 是把調(diào)試功能的最終實現(xiàn)交給插件來完成的。VS Code 提供了一套通用的圖形界面和交互方式,比如怎么創(chuàng)建斷點、如何添加條件斷點、如何查看當前調(diào)試狀態(tài)下參數(shù)的值,等等。無論你使用哪個編程語言或者調(diào)試器,這一套交互流程都是相似的。

而對于插件作者而言,他們需要完成的是如何把真正的調(diào)試工作跟 VS Code 的界面和交互結(jié)合起來,為此 VS Code 為插件作者提供了一套統(tǒng)一的接口,叫做Debug Adapter Protocol(DAP)。當用戶在界面上完成一系列調(diào)試相關(guān)的操作時,VS Code 則通過 DAP 喚起調(diào)試插件,由插件完成最終的操作。

講到這里,你可能想到了,如果你在使用的語言已經(jīng)有一個命令行的調(diào)試工具,那你也可以通過寫一個調(diào)試插件,把這個命令行的調(diào)試器通過 DAP 連接到 VS Code 中,然后就能夠借助 VS Code 這套 UI 來進行圖形化的調(diào)試了。沒錯,調(diào)試插件很大程度上就是在進行這樣的 “翻譯” 工作。下面這張 VS Code DAP 的流程圖也很好地做出了解釋:


當然,盡管我們在編輯器中提供了各種調(diào)試的界面和功能,但這并不意味著每一個調(diào)試插件把它們?nèi)紝崿F(xiàn)了。這可能是因為插件還沒有足夠成熟,也有可能是受限于底層的調(diào)試器。

首先,讓我們一起來看下 VS Code 的通用調(diào)試界面。今天我會以 Node.js 為主要的語言來介紹,對于任何編程背景的同學來說,這都沒有什么難度。

在進入調(diào)試知識之前,我們可以先做一些準備,在當前目錄下創(chuàng)建一個文件 index.js ,內(nèi)容如下:

function foo() {
    bar("Hello World");
}

foo()

function bar(str) {
    console.log(str);
}

JavaScript

調(diào)試窗口

VS Code 中有一個專門的用于管理調(diào)試功能的視圖。我們可以點擊界面左側(cè)“昆蟲”(也就是 bug 啦)形狀的按鈕,或者按下 “Cmd + Shift + D” (Windows 上是 Ctrl + Shift + D)來喚出調(diào)試視圖。


在視圖的最上側(cè),有個綠色的箭頭按鈕。這個按鈕是用于啟動調(diào)試器的。但是在上面的截圖里,你可以看到在綠色箭頭的右側(cè)寫著 “沒有配置”。這說明現(xiàn)在 VS Code 還不知道該使用什么調(diào)試器來調(diào)試當前的代碼。此時點擊這個按鈕或者按下 F5,我們能夠看到一個列表。


這個列表有兩個選項,一個是 Chrome,另一個則是 Node.js。其中,Node.js 的調(diào)試器是 VS Code 默認就支持的,而 Chrome 這個選項則是因為我安裝了 Chrome 調(diào)試相關(guān)的插件 Debugger for Chrome – Visual Studio Marketplace 。 為了便于理解,這里我會選擇 Node.js 。

選擇完 Node.js 后,我們可以看到 VS Code 的 UI 發(fā)生了變化,但是一閃而過,然后又恢復了正常。這是因為 VS Code 的 Node.js 調(diào)試器發(fā)現(xiàn)本地有 index.js 文件,于是直接執(zhí)行了這個文件;index.js 文件內(nèi)容非常簡單,很快結(jié)束了。

下面,讓我們放慢速度再來一次。

首先,我們將鼠標移動到第五行代碼的行號前面,點擊鼠標左鍵,我們能夠看到一個紅色的圓點被創(chuàng)建了出來,這就是斷點。當然,我們也可以把光標移動到第五行,然后按下 F9,同樣可以在第五行創(chuàng)建斷點。


此時,當我們再次點擊調(diào)試視圖上面的綠色箭頭按鈕,或者按下 F5,啟動調(diào)試器,并且選擇 Node.js ,VS Code 就會進入調(diào)試模式。


我們能夠看到界面中間出現(xiàn)了一個工具欄,用于控制代碼的執(zhí)行;左側(cè)的調(diào)試視圖,現(xiàn)在也展示了當前上下文里的變量、調(diào)用堆棧和所有創(chuàng)建的斷點等。相信這些你早就已經(jīng)非常熟悉了,這里我就不多加贅述。


但是,如果工作區(qū)并沒有任何打開的文件,那當我們再次按下 F5 進行調(diào)試,然后選擇 Node.js 時,VS Code 會告訴我們 “找不到要調(diào)試的程序”。這句話什么意思呢?


在沒有任何配置的情況下,VS Code 的 Node.js 調(diào)試器,會嘗試著去調(diào)試當前打開的文件,而如果當前沒有任何的文件被打開的話,Node.js 調(diào)試器就不知道該調(diào)試哪個代碼文件了。而且很多時候,我們的項目相對比較復雜,單個文件的調(diào)試還是相對太理想化了。為了解決這個問題,我們需要給 VS Code 提供一個配置文件,告訴調(diào)試器如何加載和調(diào)試代碼。

相信你對 VS Code 的各種配置文件早就不陌生了,VS Code 用于配置代碼調(diào)試的文件跟其他的很類似,是一個 JSON 文件,叫做 launch.json 。我們可以把它放在 .vscode 文件夾下,也能夠?qū)⑺膬?nèi)容放在個人或者工作區(qū)的配置文件里。

配置launch.json

VSCode 代碼調(diào)試器配置launch.json介紹,在調(diào)試視圖的最上方,我們能夠看到一個齒輪形狀的按鈕,它可以用于創(chuàng)建和修改 launch.json 文件。由于當前文件夾下沒有 launch.json 文件,所以這個按鈕的右上角有個紅色的點,它告訴我們當前的調(diào)試配置有一點問題,讓我們點擊這個按鈕。

當我們按下按鈕后,VS Code 詢問我們想要創(chuàng)建什么項目的調(diào)試配置,這里我們再次選擇 Node.js。然后我們就能夠看到 .vscode 文件夾下 launch.json 文件被創(chuàng)建出來了,它的內(nèi)容如下:

{
 // 使用 IntelliSense 了解相關(guān)屬性。 
 // 懸停以查看現(xiàn)有屬性的描述。
 // 欲了解更多信息,請訪問: https://go.microsoft.com/fwlink/?linkid=830387
 "version": "0.2.0",
 "configurations": [
  {
   "type": "node",
   "request": "launch",
   "name": "啟動程序",
   "program": "${file}"
  }
 ]
}

JSON

這個 JSON 文件里的 configurations 的值就是當前文件夾下所有的配置了?,F(xiàn)在我們只有一個調(diào)試配置,它有四個屬性:

第一個是 type,代表著調(diào)試器的類型。它決定了 VS Code 會使用哪個調(diào)試插件來調(diào)試代碼。

第二個是 request,代表著該如何啟動調(diào)試器。如果我們的代碼已經(jīng)運行起來了,則可以將它的值設(shè)為 attach,那么我們則是使用調(diào)試器來調(diào)試這個已有的代碼進程;而如果它的值是 launch,則意味著我們會使用調(diào)試器直接啟動代碼并且調(diào)試。

第三個屬性 name,就是這個配置的名字了。

第四個屬性 program,就是告訴 Node.js 調(diào)試器,我們想要調(diào)試哪個文件。這個值支持預定義參數(shù),比如在上面的例子里,我們使用了${file},也就是當前編輯器里打開的文件。

不過使用這個配置,并沒有解決剛才上面我提的問題,如果所有文件都被關(guān)閉了,那么${file} 就是空的了,這個調(diào)試配置并不能正確運行。

下面我們把 program 的值改為 ${workspaceFolder}/index.js,其中${workspaceFolder} 是代表當前工作區(qū)文件夾地址的預定義參數(shù),使用它就能夠準確地定位當前工作區(qū)里 index.js 文件了。(關(guān)于在配置文件里可以使用的預定義參數(shù),請參考Visual Studio Code Variables Reference。 )

到這一步,即使我們關(guān)閉掉編輯器里全部的文件,當我們按下 F5 ,也能夠?qū)?nbsp;Node.js 代碼調(diào)試起來了。

開發(fā)launch.json

VSCode 代碼調(diào)試器配置launch.json開發(fā),launch.json 該怎么寫,看到這里,你可能會說,“上面講的我都會,快趕緊告訴我該怎么掌握 launch.json 吧,它實在是太難寫了?!睕]錯,VS Code 的調(diào)試交互方式,跟其他工具并沒有太大的出入,但是配置起來可就麻煩了,我也深受其擾。究其原因,不同調(diào)試器在調(diào)試代碼或者工程時,需要的信息各不相同,VS Code 并沒有辦法為它們統(tǒng)一所有的配置項。

舉個例子,在上面的 Node.js 調(diào)試配置里,有個屬性叫做 request,它控制著我們運行調(diào)試器時是 launch 還是該 attach 。對于絕大部分調(diào)試器,這個屬性都是有用的,所以 VS Code 預先定義好了 request 這個屬性,然后要求每個調(diào)試配置都必須包含 request 這個屬性,而且它的值必須是 launch 或者 attach 之一。


我們在編輯器里打開 launch.json 這個文件時,能夠看到request 這個屬性的顏色是灰色的,這說明它是 VS Code 預先定義好的屬性,每個調(diào)試器插件都會按照一樣的方式去閱讀和理解它的值。

而 name 和 program 這兩個屬性,它們的顏色是藍色的,這意味著它們的定義和最終解釋,都是由調(diào)試插件控制的,而 VS Code 并不會對它們做任何的約束和處理。

相信到這里,你已經(jīng)明白了 launch.json 的本質(zhì),以及它的書寫難度來自于哪里。VS Code 提供了調(diào)試界面,但是并沒有將調(diào)試配置統(tǒng)一起來,而是將它的自由度完全交給調(diào)試器本身,我們在 launch.json 里書寫的調(diào)試配置,其實就是調(diào)試器的配置或者參數(shù),只不過它的格式是 JSON。

但是別怕,我們有辦法降低書寫它的難度。我們可以借助VS Code的調(diào)試器插件提供的模板,以及自動補全功能。

模板

下面我們打開 launch.json ,在第十二行最后按下Ctrl + Space或者執(zhí)行 “觸發(fā)建議”這個命令,VS Code 立刻就會為我們喚出了建議列表。


建議列表里的,就是調(diào)試插件們給我們提供的調(diào)試配置的模板了。模板的前綴就是它所屬的語言或者插件名稱,后面則是這個模板的類型。它們一般都有一段說明,介紹它大概是完成什么調(diào)試工作的。

比如我已經(jīng)安裝了 Java 的插件,當我選擇了 “Java Attach to Remote Program” 這一項時,我們能夠看到它的描述是:


“Add a new configuration for attaching to a running java program” ,它的意思是這個配置是用于將調(diào)試器 attach 到正在運行的 Java 程序上的。

再或者我選擇了 “Node.js: Gulp 任務“,它的作用是調(diào)試 Gulp 任務(Gulp 是 Node.js 的一個任務腳本工具),同時它還提示了我要確保項目里已安裝本地 Gulp 腳本。


就是這樣,很多時候,模板可以幫助我們完成大部分的工作,然后我們只需要稍作修改就可以了。

參數(shù)智能提示

另一個能夠幫助到我們的,就是在書寫配置屬性的時候使用自動補全功能。當我們在書寫新的屬性時,按下 Ctrl + Space,就能夠喚出建議列表,建議列表里提供了當前調(diào)試配置里可以使用的所有屬性,然后我們就可以按需選用了。


通用屬性

雖然每個調(diào)試器各自控制著用戶可以使用哪些屬性,但是調(diào)試器之間還是有很多相同的地方,調(diào)試插件在很多時候都會使用相同的屬性名來代表同樣的功能。比如,我自己就是 Ruby 插件的作者,我在實現(xiàn) Ruby 調(diào)試插件的時候,參考了很多 Node.js 和 PHP 調(diào)試插件對于屬性的命名和使用。我在書寫不同語言的調(diào)試配置時,經(jīng)常使用的有下面這些:

  • program 一般用于指定將要調(diào)試的文件。
  • stopOnEntry,當調(diào)試器啟動后,是否在第一行代碼處暫停代碼的執(zhí)行。這個屬性非常方便,如果沒有設(shè)置斷點而代碼執(zhí)行非??斓脑?,我們就會像文章的最開頭那樣,代碼調(diào)試一閃而過,而沒有辦法在代碼執(zhí)行的過程中暫停了。而設(shè)置了 stopOnEntry 后,代碼會自動在第一行停下來,然后我們就可以繼續(xù)我們的代碼調(diào)試了。
  • args 參數(shù)。相信你應該記得在前面任務系統(tǒng)配置的文章里,我已經(jīng)說明了可以使用 args 來控制傳入任務腳本的參數(shù),同樣的,我們也可以通過 args 來把參數(shù)傳給將要被調(diào)試的代碼。
  • env 環(huán)境變量。大部分調(diào)試器都使用它來控制調(diào)試進程的特殊環(huán)境變量。
  • cwd 控制調(diào)試程序的工作目錄。
  • port 是調(diào)試時使用的端口。

多系統(tǒng)支持

在任務系統(tǒng)配置里,我們介紹過如何為不同的操作系統(tǒng)指定不同的配置,調(diào)試配置也支持這樣的語法。比如 program 這個屬性,我們可以默認使用 macOS 或者 Linux 的書寫方式 :

"program": "path/app.js"

JSON

然后我們也可以為 Windows 平臺指定特定的書寫方式:

"windows": {
  "program": "path\\app.js"
}

JSON

試著理解調(diào)試器

上面我建議的書寫調(diào)試配置 launch.json 的方法,總結(jié)來說就是:第一,借助模板和智能提示,盡可能利用調(diào)試插件給我們的提示和文檔;第二,試著記住和學習通用的配置屬性和技巧,有些知識我們在學習任務系統(tǒng)時也已經(jīng)涉獵過了。

但在我自己學習配置 launch.json 的過程中,我自己體悟的最重要的一條方法,就是去試著理解調(diào)試器本身,或者講的更寬泛一些,理解自己正在執(zhí)行的操作究竟干了什么。

尤其是當我們已經(jīng)熟悉了通用的那些屬性的配置,如果配置完它們都還不能工作,那么很有可能是我們使用的這個調(diào)試器有一些特殊的要求。這里我舉兩個例子。

第一個是我在調(diào)試 Gulp 時遇到的。Gulp 是一個自動化腳本工具,基于 Node.js ,可以通過 NPM 進行安裝使用 (npm install gulp)。它的配置文件也是一個 JavaScript 文件,我可以在文件夾下創(chuàng)建 gulpfile.js ,然后在這個文件里創(chuàng)建多個不同的任務,之后就可以在命令行里使用 gulp task直接運行任務了。

我最開始只知道如何為普通的 JavaScript 文件創(chuàng)建調(diào)試配置,也就是我們在文章開頭提到的方法,為 Node.js 這個調(diào)試器指定一個文件,比如 ${workspaceFolder}/index.js,Node.js 調(diào)試器就回去調(diào)試這個文件了。但是如果我要調(diào)試我寫在 gulpfile.js 文件里的某個任務,該怎么做呢?直接調(diào)試 ${workspaceFolder}/gulpfile.js 代碼并不奏效,因為我希望調(diào)試的其實是 gulp task這個命令。

但是我退過來想一下,Gulp 是使用 Node.js 實現(xiàn)的工具,它的執(zhí)行文件其實也是一個 JavaScript 代碼(也就是 node_modules/gulp/bin/gulp.js)。gulp task運行的時候,其實是在 Node.js 下運行 node_modules/gulp/bin/gulp.js這個文件,然后傳入了 task這個參數(shù)。

理解了這層關(guān)系,那么調(diào)試配置就好寫了。我要使用的調(diào)試器是 node,調(diào)試的是 gulp.js 這個文件,它存在當前目錄下的 node_modules 文件夾下,那么我需要使用 ${workspaceFolder}這個預定義的變量,同時給 args傳入 task這個參數(shù)。最終的調(diào)試配置如下:

{
 "type": "node",
 "request": "launch",
 "name": "Gulp task",
 "program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
 "args": [
  "task"
 ]
}

JSON

第二個例子則是我在調(diào)試 JavaScript 代碼遇到的。前端項目為了保證加載和運行速度,很多都會對 JavaScript 代碼進行打包和壓縮。經(jīng)壓縮的代碼幾乎是不可讀的,為了便于調(diào)試,打包工具都還會生成一個特殊的 sourcemap 文件,這個文件里記錄的是原始代碼和壓縮代碼之間的對應關(guān)系。有了這個文件,我就能夠像調(diào)試原始代碼一樣調(diào)試經(jīng)壓縮的代碼了。在自動補全的提示下,我可以添加一個屬性 “sourceMaps” ,這樣 JavaScript 的調(diào)試器就知道去閱讀 sourcemap 文件了。

但問題來了,當我使用 webpack 打包代碼然后進行調(diào)試時,斷點停下的位置總是錯得離譜。很顯然,調(diào)試器沒有能夠正確地通過 sourcemap 文件定位斷點。不過尋找解決方案的方式還算很簡單,我到對應的調(diào)試器的 GitHub 代碼倉庫 issues 里搜索 “webpack sourcemap” 就找到一摸一樣的問題了,原因是 webpack 自己的特殊的 sourcemap 生成方式跟 VS Code 不兼容。要解決這個問題,我們既可以通過配置 webpack,也可以通過 “sourceMapPathOverrides”這個屬性來修復。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號