调试器设置

调试器会使用要以下这些配置, 在通常情况下, 你不需要更改或者修改他们中的任何一项, 但是需要看一看。

  • go.gopath. 查看GOPATH in VS Code
  • go.inferGopath, 查看GOPATH in VS Code
  • go.delveConfig
    • apiVersion 启动 headless delve 服务, 需要指定的 delve api 版本, 默认为 2.
    • dlvLoadConfig 当 apiVersion 为 1 时不适用。配置会传递给 delve, 控制 delve 的各种功能,这些功能会影响调试窗格中显示的变量。
    • maxStringLen: 从字符串读取的最大字节数
    • maxArrayValues: 从数组,切片或 map 中读取的最大元素数
    • maxStructFields: 从结构读取的最大字段数,-1将读取所有字段
    • maxVariableRecurse: 嵌套类型读取的最大层级
  • showGlobalVariables:在“调试”视图中显示全局变量(默认值:)false。

虽然大多数情况下你不需要调整配置, 但在以下情况需要调整 delve 的配置

  • 在调试视图中检查变量时,可能需要更改字符串和数组的长度, 默认上限更改为 64。
  • 在调试视图中检查嵌套变量时,请按照实际情况进行配置。

启动配置

当 delve 安装后, 运行命令Debug: Open launch.json, 如果没有 launch.json 文件, 则会使用默认配置创建一个文件,此文件用于调试当前程序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${fileDirname}",
            "env": {},
            "args": []
        }
    ]
}

以下是launch.json中的一些属性说明:

属性 说明
name 定义配置名字
type 指定语言,这里我们填go即可
request launch ,attach, 当需要对一个已经运行的的程序 debug 时才使用 attach,其他时候使用launch
mode 对于 launch 有 auto, debug, remote, test, exec, 对于 attach只有local,remote
program 指定包, 文件或者是二进制的绝对路径
env 调试程序时需要注入的环境变量, 例如:{ “ENVNAME”: “ENVVALUE” }
envFile 绝对路径,env的值会覆盖envFile的值
args 需要传给调试程序的命令行参数
showLog 布尔值,是否在调试控制台打印日志, 一般为true
logOutput 日志输出目标, 使用逗号分隔达到使用多个组件输出日志的目的 (debugger, gdbwire, lldbout, debuglineerr, rpc), 当 showLog 为 true 有效
buildFlags 构建程序时需要传递给 Go 编译器的 Flags
remotePath 如果mode为remote时, 需要指定调试文件所在服务器的绝对路径
processId 进程 id
host 目标服务器地址
port 目标端口

在调试过程中使用 VS Code 变量

  • ${workspaceFolder} 在工作区的的根目录调试程序
  • ${file} 调试当前文件
  • ${fileDirname} 调试当前文件所属的程序包

使用 build tags

如果在构建时需要构建标签, (比如:go build -tags=whatever_tag ), 则需要使用标签buildFlags 并添加内容: “-tags=whatever_tag” ,如果你需要使用多个标签时, 则需要使用单引号引起来,像这样:"-tags=‘first_tag second_tag third_tag’"

常用的launch.json 配置示例

在编辑launch.json时, 你可以将下面这些代码片段用于调试配置。

调试当前文件的配置样本

1
2
3
4
5
6
7
{
    "name": "Launch file",
    "type": "go",
    "request": "launch",
    "mode": "auto",
    "program": "${file}"
}

调试单个测试用例配置样本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
    "name": "Launch test function",
    "type": "go",
    "request": "launch",
    "mode": "test",
    "program": "${workspaceFolder}",
    "args": [
        "-test.run",
        "MyTestFunction"
    ]
}

调试包内所有测试用例配置样本

1
2
3
4
5
6
7
{
    "name": "Launch test package",
    "type": "go",
    "request": "launch",
    "mode": "test",
    "program": "${workspaceFolder}"
}

调试预构建二进制配置样本

1
2
3
4
5
6
7
{
    "name": "Launch executable",
    "type": "go",
    "request": "launch",
    "mode": "exec",
    "program": "absolute-path-to-the-executable"
}

调试本地已运行进程配置样本

1
2
3
4
5
6
7
{
    "name": "Attach to local process",
    "type": "go",
    "request": "attach",
    "mode": "local",
    "processId": 0
}

举例

这是第一个示例的源代码。 创建一个文件main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
    "encoding/json"
    "fmt"
    "log"
)

// Avenger represents a single hero
type Avenger struct {
    RealName string `json:"real_name"`
    HeroName string `json:"hero_name"`
    Planet   string `json:"planet"`
    Alive    bool   `json:"alive"`
}

func (a *Avenger) isAlive() {
    a.Alive = true
}

func main() {
    avengers := []Avenger{
        {
            RealName: "Dr. Bruce Banner",
            HeroName: "Hulk",
            Planet:   "Midgard",
        },
        {
            RealName: "Tony Stark",
            HeroName: "Iron Man",
            Planet:   "Midgard",
        },
        {
            RealName: "Thor Odinson",
            HeroName: "Thor",
            Planet:   "Midgard",
        },
    }

    avengers[1].isAlive()

    jsonBytes, err := json.Marshal(avengers)
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(string(jsonBytes))
}

在这里,我们只是定义一个struct Avenger ,然后创建一个复仇者数组,将其中一个复仇者的状态更改为还活着,然后将结果转换为JSON,最后将其打印到STDOUT。

您可以使用

1
2
go run main.go
[{"real_name":"Dr. Bruce Banner","hero_name":"Hulk","planet":"Midgard","alive":false},{"real_name":"Tony Stark","hero_name":"Iron Man","planet":"Midgard","alive":true},{"real_name":"Thor Odinson","hero_name":"Thor","planet":"Midgard","alive":false}]

要开始调试,我们需要创建一个配置。 单击Visual Studio Code左窗格上的“调试”图标。 接下来,单击齿轮图标以创建配置。

在.vscode/launch.json下创建一个配置文件,其内容如上所示。 更改配置程序以指向main.go文件。 在这种情况下,由于只有main.go文件,因此可以将其更改为工作区根目录:

1
"program": "${workspaceRoot}",

出发了 接下来,我们需要添加一个断点,因为这就是调试的全部目的。

让我们在第21行func main()上添加一个断点,方法是单击行号左侧,我们将看到一个红点。

接下来,按F5或单击左上角“调试”部分上带有绿色播放按钮的“启动”按钮。 它应该打开调试视图。

在“调试工具栏”上的“ Step Over按钮上单击两次。

一旦调试器移至第40行。

左侧的“调试”部分将为我们提供当前断点位置的状态。

我们可以在“变量”部分的特定时间查看变量的状态/值。

我们还可以看到调用堆栈,当前运行函数是main函数,以及line 40 。

您可以继续Stepping Over ,一旦我们越过线,您将看到avengers的价值发生变化。

条件断点

VSCode断点为您提供了一个选项,可以通过给它们一个表达式来编辑断点,大多数情况下,这些时间通常是布尔表达式。

例如,在第40行avengers[1].isAlive() ,我们可以在此处添加一个条件,即仅当我们要计算的表达式为true时才会引发断点。 例如avenger[1].Planet == “Earth”

为此,请在断点上单击鼠标右键,然后选择“编辑断点”。

如果没有断点,仍然可以右键单击,然后会提示您添加Conditional Breakpoint

选择以上任意一项后,让我们添加条件。 avenger[1].Planet == “Earth”

现在,如果您使用F5启动调试器,它将不会在断点处停止。 该应用程序将正常运行,我们将在调试控制台中看到结果。

但是,当我们编辑代码以符合我们的期望时。 将托尼·斯塔克的星球改变为Earth

1
2
3
4
5
6
7
// ....
{
    RealName: "Tony Stark",
    HeroName: "Iron Man",
    Planet:   "Earth",
},
//...

当我们再次使用F5启动调试器时,现在将打开“调试视图”,并且执行在断点处停止。 我们可以看到JSON没有显示在调试控制台中

调试测试

让我们向文件中添加一个新函数,以实现简单的加法运算。

1
2
3
func add(a, b int) int{
    return a+b
}

然后,我们在同一目录中创建测试文件main_test.go ,其中包含以下内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import "testing"

func Test_add(t *testing.T) {
    a, b, c := 1, 2, 3
    res := add(a, b)
    if res != c {
        t.Fail()
    }
}

该代码仅将两个数字相加,而测试仅调用该函数。

但是,如果您安装了VSCode-Go插件,您将在测试功能的顶部看到其他选项。 run test和debug test

您可以单击run test以运行测试,并在“输出”窗口中查看结果。

但是,要调试测试,也许是因为我们无法弄清楚,我们需要做的就是像以前一样添加一个断点,然后单击debug test 。

if res != c ,让我们在第10行添加一个断点,然后单击debug test。

调试工具栏

我们关注工具栏

首先是按钮的名字。从左到右按顺序如下(带有默认的 VS Code 快捷方式):

  • Continue / Pause F5
  • Step Over F10
  • Step Into F11
  • Step Out ⇧F11
  • Restart ⇧⌘F5
  • Stop ⇧F5

Continue、restart 和 stop 很简单,会分别执行你所期望的操作:继续到下一个断点,重新启动进程,以及停止进程(和调试器)。

Step 与当前行上的函数调用相关:你可以单步执行某个函数调用(Step Over),也可以进入该函数调用(Step Into 在内部查看并调试)或者离开这个函数(Step Out)。Step-over 操作还允许你逐行执行代码,即使该行不是函数调用。

Step 命令仅控制你在调试器中看到的内容。所以 “Step Out” 或 “Over” 一个函数将会 仍然照常执行所有代码。调试器不会让你感到无聊,你能够更快地完成自己的主要工作。

Continue

Continue 将会运行代码,直到下一个断点或程序结束。一种调试的方法是预先在相关行上添加多个断点,然后用 continue 在它们之间跳转.

如果你已经知道哪些函数或行与你的目的有关,那么 Continue 操作将非常方便。调试器将在预定义的位置暂停,这时你可以对变量和调用栈进行检查。

Step Over

你可以将 Step Over 看作是在函数中逐行进行,但不进入函数调用。如果你对当前行中的函数调用内部逻辑不感兴趣,而只想查看局部变量如何随时间变化,用它就对了.

Step Over 是略过说明性代码的好方法。

Step Into

当某行调用了你感兴趣的函数,并想要更深入地研究时,可以使用 Step Into。一旦进入代码块后,可以像往常一样进行调试(使用 continue、step 等命令)。

Step Out

Step Out 与 Step In 相反:如果你不再对某个函数感兴趣,可以离开它。使用 “Step out” 将一次运行完该函数的剩余代码。

通过调试检查这两个函数之间的区别,我们逐行执行第一个函数,但是早早的就退出第二个函数:

参考

[译] 使用 VSCode 调试 Golang

使用Visual Studio Code调试Go代码

VS Code 调试完全攻略(2):步进逐行调试