Vim 最強調試插件:vimspector


Vimspector是一個基於DAP(debug adapter protocol)的Vim多語言調試插件,理論上能夠支持所有支持語言(只要有對應的 DAP)。這個插件仍在實驗階段,可能會有各種bug,但是對C/C++、Python 等流行的語言已經進行了充分的測試。

這篇文章以調試 C/C++ 程序為例,介紹 vimspector 的配置與使用。

依賴

  • 帶 Python3.6+ 支持的 Vim 8.2 或更高版本
  • 帶 Python3.6+ 支持的 Neovim-0.4.3 或更高版本(最好是 Nightly 版本)

由於 vimspector 的作者主要在 GNU/Linux 上使用 Vim 開發,因此 Vimspector 作者 puremourning 明確表示在 vimspector 的充分測試並穩定后才會提供對 Neovim 的完整支持(見 issue coc.nvim: Debug Adapter Protocol Support #322),因此目前對於 Neovim 和 Windows 的支持都處於實驗階段。我個人建議在 Vim 中使用本插件。

安裝

vimspector

使用vim-plug安裝:

Plug 'puremourning/vimspector'

使用 dein.vim 安裝:

call dein#add('puremourning/vimspector')

調試適配器

最新版的 vimspector 可以在 Vim 中通過命令:VimspectorInstall安裝調試適配器,按Tab鍵可以補全。

也可以使用安裝腳本安裝,進入vimspector的安裝目錄,執行:

./install_gadget.py <language-name>

install_gadget.py會自動下載<language-name>所需的調試適配器並進行相應配置,--help可以查看vimspector所支持的全部語言。

以在Linux環境上打開C/C++支持為例:

./install_gadget.py --enable-c

vimspector會自動下載微軟開發的調試適配器cpptools-linux.vsixyour-vimspector-path/gadgets/linux/download/vscode-cpptools/0.27.0/中。如果是在mac上,linux會被改成mac

如果下載速度過慢,可以自己下載好放置在上面提到的目錄中,然后再執行以上命令。

配置

Vimspector 使用 json 作為配置文件的格式,每個配置都是一個 json 對象。

Vimpector 有兩類配置:

  • 調試適配器的配置
    • 如何啟動或連接到調試適配器
    • 如何 attach 到某進程
    • 如何設置遠程調試
  • 調試會話的配置
    • 使用哪個調試適配器
    • launch 或 attach 到進程
    • 是否預先設置斷點,在何處設置斷點

這兩類配置可以對應多個配置文件,vimspector 會將多個配置文件中的信息合並成一個配置。

調試適配器配置

調試適配器的這個配置在打開 vimspector 對某語言的支持時就已經自動設置好了,存放在 your-path-to-vimspector/gadgets/linux/.gadgets.json 中。

比如在我的設備上,.gadgets.json 內容如下:

{
  "adapters": {
    "vscode-cpptools": {
      "attach": {
        "pidProperty": "processId",
        "pidSelect": "ask"
      },
      "command": [
        "${gadgetDir}/vscode-cpptools/debugAdapters/OpenDebugAD7"
      ],
      "name": "cppdbg"
    }
  }
}

其中變量${gadgetDir}代表着存放 .gadgets.json 的目錄。除此之外,vimspector 還定義了其他預定義變量,並提供了自定義和用戶輸入變量內容的功能,以便我們編寫比較通用的配置文件。

調試適配器的配置還可以存在於其他配置文件中,vimspector 讀取一系列配置文件,生成adapters對象。

調試適配器的配置可以存在於以下文件中:

  1. our-path-to-vimspector/gadgets/<os>/.gadgets.json:這個文件由 install_gadget.py 自動生成,用戶不應該修改它。

  2. your-path-to-vimspector/gadgets/<os>/.gadgets.d/*.json :這些文件是用戶自定義的。

  3. 在 Vim 工作目錄向父目錄遞歸搜索到的第一個 .gadgets.json。

  4. .vimspector.json 中定義的adapters

編號代表配置文件的優先級,編號越大優先級越高,高優先級的配置文件將覆蓋低優先級的配置文件中的的adapters

在我的機器上沒有 your-path-to-vimspector/gadgets/<os>/.gadgets.d目錄,可能是需要自己創建。

不進行遠程調試的情況下不太需要修改默認的調試適配器配置。我一般沒有進行遠程調試的需求,沒有實際使用過 vimspector 的遠程調試功能(雖然這個功能是 vimspector 重點支持的),因此不介紹調試適配器的配置。

調試會話配置

項目的調試會話的文件位於以下兩個位置:

  1. <your-path-to-vimspector>/configurations/<os>/<filetype>/*.json
  2. 項目根目錄中的 .vimspector.json

每當打開一個新的調試會話時,vimspector 都會在當前目錄向父目錄遞歸搜索,如果查找到了 .vimspector.json,則使用其中的配置,並將其所在的目錄設定為項目根目錄;如果未查找到,則使用 vimspector 安裝目錄中的配置文件,將打開的文件的目錄設置為項目根目錄。

修改了.vimspector.json 后不需要重啟 Vim 就可以使用最新配置。

配置選項

vimspector.json 中只能包含一個對象,其中包含以下子對象:

  • adapters:調試適配器配置,如果不是進行遠程調試,一般不需要設置
  • configurations:調試程序時的配置

configurations主要包含以下以下字段:

  • adapter:使用的調試配置器名稱,該名稱必須出現在adapters塊或其他調試適配器配置中。
  • variables:用戶定義的變量
  • configuration:配置名,如configuration1
  • remote-request,remote-cmdLine:遠程調試使用

其中adapterconfiguration是必須的。

configuration需要包含的字段和使用的 DAP 有關,我使用vscode-cpptoolsconfiguration必須包含以下字段:

  • request:調試的類型,lauchattach
  • typecppdgb(GDB/LLDB)或cppvsdbg(Visutal Studio Windows debugger)

除了以上的選項,還可以設置程序路徑、參數、環境變量、調試器路徑等,更詳細的信息可以查看 vscode-cpptools 文檔launch-json-reference

上面的選項構成了 vimspector 配置文件的主體框架,完整的選項參考vimspector.schema.json

變量

Vimspector 提供了比較靈活的變量定義功能,可以方便的自定義配置。

預定義變量

predefined variables

自定義變量

自定義變量要在起作用的配置中定義,並置於variables塊中,如下面的GDBServerVersionSomeOtherVariable

{
  "configurations": {
    "some-configuration": {
      "variables": {
        "GDBServerVersion": {
          "shell": [ "/path/to/my/scripts/get-gdbserver-version" ],
          "env": {
            "SOME_ENV_VAR": "Value used when running above command"
          }
        },
        "SomeOtherVariable": "some value"
      }
    }
  }
}

可以通過 shell 設置變量的值,代碼塊中的GDBServerVersion的值就是 shell 腳本/path/to/my/scripts/get-gdbserver-version的輸出。

經我實驗,自定義變量似乎不能夠依賴自定義變量,也不可以在shell中使用預定義變量,可能之后的版本會實現這些功能。

也可以從用於輸入中獲取變量的值。只要 vimspector 發現了未定義的變量,就會在運行時提示用戶輸入變量值。Vimspector 支持 splat 運算符(不清楚中文叫什么),語法為*${Variable},可以將一個變量拓展開。

以上兩個特性結合在一起可以實現很靈活的配置,最典型的運例子是程序參數的傳遞。Vimspector 調試的程序的參數以數組的形式傳遞,在配置文件中將args設置為一個在運行時用戶輸入的變量,就可以模擬命令行的效果。

  "args": [ "*${CommandLineArgs}" ]

在運行時 vimspector 會要求用戶輸入值,如果用戶輸入123,args就會被拓展成["1", "2", "3"]

默認值

可以為變量提供默認值,${variableName:default value}。在${}中引用變量時,}要通過\轉義,即將}寫為\\}

  {
    "configuration": {
      "program": "${script:${file\\}}"
    }
  }

program默認設置為變量file的值。

類型轉換

vimspector 介紹的的用戶輸入都字符串,有時會出現類型和用戶期望的不同的情況。比如,用戶為布爾類型的變量StopOnEntry輸入true,vimspector 接收到字符串"true",並將它賦給變量,這樣出現了類型不一致的情況。

  {
    "configuration": {
      "stopAtEntry": "${StopOnEntry}"
    }
  }

在字段后添加#json可以將接收到的字符串轉換成 json 里的類型。如果變量以#json結尾,需要在字段尾部添加#s以告知 Vimspector 這個變量以#json結尾,而不是進行類型轉換。

 {
    "configuration": {
      "stopAtEntry#json": "${StopOnEntry}"
    }
  }

這樣,用戶輸入true,vimspector 接收到字符串"true",然后再將它解析為布爾類型的true

多配置共存

可以在 .vimspector.vim 中寫多個配置,在啟動 vimspector 時再選擇使用的配置。這樣的話,可以將所有自己需要的配置寫入到一個文件,在創建項目時復制到項目中。

和配置選擇有關的字段有兩個:

autoselect: 布爾類型。在只有一個配置可用時,是否自動選擇它。

default: 布爾類型。當啟動時用戶沒有選擇配置時,使用本配置。

斷點

可以在配置中提前打好斷點,比如在程序入口點暫停(通常是main()),在拋出異常時暫停,在某函數暫停等等。

stopAtEntry: 布爾類型。是否在程序入口點暫停。

cpp_throw: 在拋出異常時暫停

cpp_catch: 在捕獲異常時暫停

{
    "example":{
        "stopAtEntry": true,
        "MIMode": "gdb",            // 使用 GDB
        "breakpointers": {
        "exception": {
             "cpp_throw": "Y",
             "cpp_catch": "N"
         }
        }
    }
}

Vimspector 暫時不支持在配置中在函數、代碼行上打斷點,但是可以通過底層的調試適配器,直接在調試器中執行命令,繞開這個限制。

示例

Vimspector 的配置其實很簡單,但是紙上談兵有些難以理解。這里將給出幾個可以實際使用的配置,如調試 Vim、調試 qemu 模擬器中的 OS 內核。

調試 Vim

vimspector 文檔中給出的調試vim的配置:

{
  "configurations": {
    "Vim - run a test": {                           // 配置名
      "adapter": "vscode-cpptools",                 // 使用的調試適配器
      "configuration": {                            // 具體的配置
        "type":    "cppdbg",                        // 調試器類型:cppdbg(GDB/LLDB) 或 cppvsdbg(VISUAL STUDIO)
        "request": "launch",                        // 調試類型:launch(啟動程序) 或 attach(連接進程)
        "program": "${workspaceRoot}/src/vim",      // 帶有調試信息的可執行文件目錄
        "args": [                                   // 程序的參數,一個 json 數組
          "-f",
          "-u", "unix.vim",
          "-U", "NONE",
          "--noplugin",
          "--not-a-term",
          "-S", "runtest.vim",
          "${Test}.vim"                             // 未定義的變量,用戶輸入
        ],
        "cwd": "${workspaceRoot}/src/testdir",      // 當前工作目錄
        "environment": [                            // 環境變量
          { "name": "VIMRUNTIME", "value": "${workspaceRoot}/runtime" }
        ],
        "externalConsole": true,                    // 是否使用外部終端
        "stopAtEntry": true,                        // 是否在程序入口點暫停
        "MIMode": "lldb",                           // 使用 LLDB 作為調試器
        "logging": {                                // 調試適配器的輸出
          "engineLogging": false                    // 是否打印調試適配器產生的信息,默認不打印
        }
      }
    }
  }
}

調試 qemu-riscv64 中的 OS 內核

用以下命令啟動 qemu 模擬器,讓它監聽localhost:1234等待 GDB 連接:

 qemu-system-riscv64 \
       -machine virt \
       -s -S         \
       -nographic    \
       -bios default \
       -device loader,file=kernel.img,addr=0x80200000

因此,我們要配置 vimspector 讓 RISCV 架構的 GDB 連接到localhost:1234

{
    "configurations": {
        "qemu-riscv64-oslab": {
            "adapter": "vscode-cpptools",
            "variables": {
                "kernelBin": "kernel.bin,                                // 帶有調試信息的內核可執行文件
                "riscvGDB": "/usr/local/bin/riscv64-unknown-elf-gdb"     // GDB 路徑
            },
            "configuration": {
                "type":    "cppdbg",
                "request": "launch",
                "program": "${kernelBin}",
                "cwd": "${workspaceRoot}",
                "environment": [],
                "externalConsole": true,
                "stopAtEntry": true,
                "MIMode": "gdb",                                         // 使用 GDB
                "miDebuggerPath": "${riscvGDB}",                         // GDB 路徑為 ${riscvGDB}
                "setupCommands": [                                       // 設置 GDB 初始化命令,相當於 gdbinit
                    {
                        "description": "Enable pretty-printing for gdb", // 描述,不會被 GDB 使用
                        "text": "set architecture riscv",                // 命令
                        "ignoreFailures": false                          // 是否忽略錯誤
                    },
                    {
                        "description": "Connect gdbserver within qemu",
                        "text": "target remote localhost:1234",
                        "ignoreFailures": false
                    }
                ]
            }
        }
    }
}

上面的配置依賴 vscode-cpptools,不支持其他的調試適配器。

我自己的配置

我將我可能使用的配置寫入到一個文件,這樣不需要重復編寫,在啟動 vimspector 選擇即可。

{
    "configurations": {
                                                                   // 上面介紹過了,不再贅述
        "qemu-riscv64-oslab": {
           // ...
        },
        "launch-current-file": {
            "adapter": "vscode-cpptools",
            "configuration": {
                "default": true,
                "type":    "cppdbg",
                "request": "launch",
                "program": "${fileDirname}/${fileBasenameNoExtension}",
                "args": ["*${ProgramArgs}"],                      // 用戶輸入
                "cwd": "${workspaceRoot}",
                "environment": [],
                "externalConsole": true,
                "stopAtEntry": true,
                "MIMode": "gdb",
                "breakpointers": {
                    "exception": {
                        "cpp_throw": "Y",                          // 拋出異常時暫停
                        "cpp_catch": "N"                           // 捕獲時不暫停
                    }
                }
            }
        },
        "launch-current-project": {
            "adapter": "vscode-cpptools",
            "configuration": {
                "variables": {
                    "ProgramName": {
                        "shell": ["basename ", "${workspaceRoot}"] // 無法正確執行,需要用戶輸入
                    },
                    "ProgramPath": "${workspaceRoot}/_builds/${ProgramName}"
                },
                "type":    "cppdbg",
                "request": "launch",
                "program": "${workspaceRoot}/_builds/${ProgramName}",
                "args": ["*${ProgramArgs}"],
                "cwd": "${workspaceRoot}",
                "environment": [],
                "externalConsole": true,
                "stopAtEntry": true,
                "MIMode": "gdb",
                "breakpointers": {
                    "exception": {
                        "cpp_throw": "Y",
                        "cpp_catch": "N"
                    }
                }
            }
        },
        "attach-current-file": {
            "adapter": "vscode-cpptools",
            "configuration": {
                "type": "cppdbg",
                "request": "attach",
                "program": "${fileDirname}/${fileBasenameNoExtension}",
                "MIMode": "gdb",
                "breakpointers": {
                    "exception": {
                        "cpp_throw": "Y",
                        "cpp_catch": "N"
                    }
                }
            }
        },
        "attach-current-project": {
            "adapter": "vscode-cpptools",
            "configuration": {
                "variables": {
                    "ProgramName": {
                        "shell": ["basename", "${workspaceRoot}"]
                    },
                    "ProgramPath": "${workspaceRoot}/_builds/${ProgramName}"
                },
                "type": "cppdbg",
                "request": "attach",
                "program": "${ProgramPath}",
                "MIMode": "gdb",
                "breakpointers": {
                    "exception": {
                        "cpp_throw": "Y",
                        "cpp_catch": "N"
                    }
                }
            }
        }
    }
}

vimspector-screenshot

快捷鍵

Vimspector 預設了vscode mode 和 human mode 兩套鍵盤映射(快捷鍵)。

開啟vscode mode:

let g:vimspector_enable_mappings = 'VISUAL_STUDIO'

開啟 human mode:

let g:vimspector_enable_mappings = 'HUMAN'

這兩個套快捷鍵都要用到 F11 和 F12,這兩個按鍵往往會和終端快捷鍵沖突,如 F11 是最大化終端,F12 是彈出 guake 之類的下拉框終端,建議終端用戶重新定義快捷鍵。

GDB 前端推薦(題外話)

我從韋易笑大佬(他寫了不少有深度的文章和插件,對我產生了很大影響,推薦關注)的知乎回答如何在vim中可視化的調試c++程序?中了解到了 vimspector。當時對 json 等還很不了解,在配置 vimspector 時遇到很大困難,在網上沒有查找到比較詳細的中文文章,因此寫了這篇博客,希望能夠幫助到有需要的朋友。這篇文章發布半年多以來,vimspector 處於活躍的開發中,原文的許多內容已經過時,為了不誤導讀者,更新了文章。

最近幾天,參考韋易笑的文章終端調試哪家強,嘗試了幾種 C/C++ 調試方案,得到以下結論:

裸 GDB ==> cgdb ==> Vimspector/VSCode ==> gdbgui

其中 Vimspector 和 VSCode 均使用 vscode-cpptools,個人認為在能力上沒有太大區別。gdbgui是一個基於瀏覽器的 GDB 前端,能力應該是上述幾種方案中最強的。gdbgui 有以下幾個突出特性:

  • 不需要配置
    gdbgui 不需要配置,只需要像直接使用 gdb 一樣輸入命令即可,如gdbgui -g 'gdb program -x gdbinit'
  • 兼顧 GUI 和命令行
    在 gdbgui 中可以直接在 gdb 命令行中輸入命令,並且 GUI 會響應 gdb 命令。比如在命令行中打了斷點,GUI 會立刻顯示出來。
  • 圖形化顯示數據結構
    GDB 可以圖形化顯示鏈表和樹

Vimspector 仍在實驗階段,部分重要特性還沒有穩定,如果需要更加強大的調試功能,可以考慮 gdbgui。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM