使用Delve进行Golang代码的调试

本文介绍了如何使用Delve这款Golang专用调试器进行代码调试,包括安装、基本和进阶调试方法。通过设置断点、查看变量值、控制程序执行流程,展示了Delve在处理Go语言并发特性时的强大功能。
摘要由CSDN通过智能技术生成

dlv  git 仓库: https://github.com/go-delve/delve/tree/master/Documentation/installation

Delve目的就是为了解决开发者在使用 GDB 调试中遇到的各种各样的问题。我们开始详细的介绍一些使用Delve 调试代码的例子。

安装

首先默认你已经安装了 Go 环境,安装命令很简单,一句话。

go install github.com/go-delve/delve/cmd/dlv

注意:如果你使用Go1.5,你必须在运行这个命令前设置GO15VENDOREXPERIMENT=1

调试代码

首先得说明一下,实诚点说,当你想用debug 工具的时候,你的代码估计已经不按照你想象的方式运行了。只是你不知道为什么会这样,因此你可能需要换一种方式启动你的程序,下面我们来演示一下如果使用dlv来启动你的程序。我们的示例代码如下:

package main

import (
    "fmt"
    "sync"
    "time"
)

func dostuff(wg *sync.WaitGroup, i int) {
    fmt.Printf("goroutine id %d\n", i)
    time.Sleep(300 * time.Second)
    fmt.Printf("goroutine id %d\n", i)
    wg.Done()
}

func main() {
    var wg sync.WaitGroup
    workers := 10

    wg.Add(workers)
    for i := 0; i < workers; i++ {
        go dostuff(&wg, i)
    }
    wg.Wait()
}

在这个示例代码中,我们创建了10个goroutine,这种代码对于 GDB 来说是几乎不可读的,因为它对于goroutine的支持很差。但是Delve作为一个专业的 Go 调试器,对于goroutine这种杀手级功能还是非常了解的。下面我们来启动程序。

dlv debug test-debug.go

运行这个命令,dlv会去编译你的代码,然后传一些参数给编译器,好让编译器编译出来更加方便调试的可执行文件,然后启动了你的程序,并且attach上去,这样你的console就会停留在了debug session,下面就可以调试程序了。

首先我们在main函数上设置一个断点。

(dlv) break main.main
Breakpoint 1 set at 0x22d3 for main.main() ./test-debug.go:16

输出信息里面告诉了我们断点的 ID和断点的位置,函数名和文件名以及所在行数。我们使用continue命令让程序运行到我们打断点的地方。

(dlv) continue
> main.main() ./test-debug.go:16 (hits goroutine(1):1 total:1) (PC: 0x22d3)
    11:        time.Sleep(300 * time.Second)
    12:        fmt.Printf("goroutine id %d\n", i)
    13:        wg.Done()
    14:    }
    15:
=>  16:    func main() {
    17:        var wg sync.WaitGroup
    18:        workers := 10
    19:
    20:        wg.Add(workers)
    21:        for i := 0; i < workers; i++ {
(dlv)

现在程序就停在了第一个断点,现在我们可以使用next命令让程序运行到下一句话,如果你想继续向下,可以直接按回车(Delve会重复上一条命令如果你按下回车键)。

(dlv) next
> main.main() ./test-debug.go:17 (PC: 0x22d7)
    12:        fmt.Printf("goroutine id %d\n", i)
    13:        wg.Done()
    14:    }
    15:
    16:    func main() {
=>  17:        var wg sync.WaitGroup
    18:        workers := 10
    19:
    20:        wg.Add(workers)
    21:        for i := 0; i < workers; i++ {
    22:            go dostuff(&wg, i)
(dlv)
> main.main() ./test-debug.go:18 (PC: 0x22f1)
    13:        wg.Done()
    14:    }
    15:
    16:    func main() {
    17:        var wg sync.WaitGroup
=>  18:        workers := 10
    19:
    20:        wg.Add(workers)
    21:        for i := 0; i < workers; i++ {
    22:            go dostuff(&wg, i)
    23:        }
(dlv)
> main.main() ./test-debug.go:20 (PC: 0x22fa)
    15:
    16:    func main() {
    17:        var wg sync.WaitGroup
    18:        workers := 10
    19:
=>  20:        wg.Add(workers)
    21:        for i := 0; i < workers; i++ {
    22:            go dostuff(&wg, i)
    23:        }
    24:        wg.Wait()
    25:    }
(dlv)    

现在我们可以尝试使用print命令去看一下变量的值。

(dlv) print wg
sync.WaitGroup {
    state1: [12]uint8 [0,0,0,0,0,0,0,0,0,0,0,0],
    sema: 0,}
(dlv) print workers
10
(dlv)

同时你也可以输出一个表达式

(dlv) print workers < 100
true

下面我们在另外一个函数dostuff 上设置一个断点

(dlv) break dostuff
Breakpoint 2 set at 0x2058 for main.dostuff() ./test-debug.go:9

我们使用continue到我们设置断点的地方,然后next

(dlv) next
goroutine id 3
> main.dostuff() ./test-debug.go:10 (PC: 0x205f)
     5:        "sync"
     6:        "time"
     7:    )
     8:
     9:    func dostuff(wg *sync.WaitGroup, i int) {
=>  10:        fmt.Printf("goroutine id %d\n", i)
    11:        time.Sleep(300 * time.Second)
    12:        fmt.Printf("goroutine id %d\n", i)
    13:        wg.Done()
    14:    }
    15:
(dlv)

可以看到Delve会告诉你目前的goroutine id,我们试试输出一下i和wg.

(dlv) print i
4
(dlv) print wg
*sync.WaitGroup {
    state1: [12]uint8 [1,0,0,0,10,0,0,0,0,0,0,0],
    sema: 0,}
    

我们创建了10个goroutine,如果你继续使用next,你会发现你还是在同一个goroutine下。这样就避免了被调试器跳转到了另外的goroutine下导致不必要的调试错误。可见还是为 Go 而生的调试器才是真爱啊。

进阶调试

其实很多时候,我们调试的代码可能是daemon程序或者需要实现编译好在不同机器运行的程序。这就需要我们attach到一个已经在运行中的程序上,下面我们就使用上面的代码来演示一下如何attach到一个程序上进行调试。首先将刚才的程序运行起来,我这里直接使用了

go build test-debug.go
./test-debug

然后使用ps查看正在运行的程序pid

  501 40994   549   0 12:08AM ttys003    0:00.00 ./test-debug

然后我们attach上去

dlv attach 40994

可以看到,熟悉的debug seesion又回来了。下面我们可以继续使用上面的命令去设置断点了

(dlv) break dostuff
Breakpoint 1 set at 0x2058 for main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:9
(dlv) break dostuff:3
Breakpoint 2 set at 0x2144 for main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12

我使用continue使程序运行到我设置断点的地方

(dlv) continue
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(18):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(19):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(26):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(23):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(24):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(20):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(21):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(25):1 total:8) (PC: 0x2144)
     7:    )
     8:
     9:    func dostuff(wg *sync.WaitGroup, i int) {
    10:        fmt.Printf("goroutine id %d\n", i)
    11:        time.Sleep(300 * time.Second)
=>  12:        fmt.Printf("goroutine id %d\n", i)
    13:        wg.Done()
    14:    }
    15:
    16:    func main() {
    17:        var wg sync.WaitGroup
(dlv)

可以看到,Delve已经打印出来了当前正在运行的goroutine,下面我们print一下我们当前的i

(dlv) print i
7
(dlv) print wg
*sync.WaitGroup {
    state1: [12]uint8 [1,0,0,0,10,0,0,0,0,0,0,0],
    sema: 0,}

和上面一样,而且attach到这个进程后,也可以把对应的源码显示出来,是不是很强大呢。更多的功能就自己参考文档摸索吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值