Bootstrap

Go语言Debug调试

通过log库输出日志,我们可以对程序进行异常分析和问题追踪。但有时候,我也希望能有更直接的程序跟踪及定

位工具能够帮助我们更方便快捷的追踪、定位问题,最直观的感觉还是使用调试器。Linux平台下,原生的

C/C++程序,我们往往使用gdb进行程序调试,切换到Golang,我们同样还是可以使用gdb进行调试。同时我们还

可以使用golang实现的调试器dlv进行调试。

1、使用 GDB 调试 Go 程序

调试使用的是 GDB (要求版本 7.1 + ),使用前,请先确保你的机器上已经安装 GDB:

[root@zsx ~]# which gdb
/usr/bin/gdb
[root@zsx ~]# gdb -v
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.

如果没有安装则进行下面的操作进行安装:

因为gdb对Golang的支持也是在不断完善中,为使用gdb调试Golang程序,建议将gdb升级到相对较新版本。

# 1、通过包管理器进行安装
$ yum -y install gdb
# 2、源码安装GDB
yum install ncures-devel
wget http://ftp.gnu.org/gnu/gdb/gdb-8.2.tar.gz
tar zxf gdb-8.2.tar.gz
cd gdb-8.2
make && make install

准备就绪后,先在目录下写一个测试文件:

[root@zsx demo]# pwd
/home/zhangshixing/go_work_space/src/demo
package main

import "fmt"

func main(){
  msg := "hello, world"
  fmt.Println(msg)
}

然后执行如下命令进行编译,里面有好多个参数,有疑问的可以自行搜索引擎:

# 关闭内联优化,方便调试
[root@zsx demo]# go build -gcflags "-N -l" hello.go

[root@zsx demo]# go mod init hello
[root@zsx demo]# go mod tidy

# 发布版本删除调试符号
# 这里无须执行
[root@zsx demo]# go build -ldflags "-s -w"
[root@zsx demo]# ll
total 1164
-rw-r--r--. 1 root root      22 Feb  7 18:36 go.mod
-rwxr-xr-x. 1 root root 1183744 Feb  7 18:37 hello
-rw-r--r--. 1 root root      87 Feb  7 18:35 hello.go

最后使用 GDB 命令进入调试界面:

# 如果你喜欢这种界面的话,用这条命令
[root@zsx demo]# gdb -tui hello

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNqXOdSc-1687443931558)(…/…/images/Go/006.png)]

# 如果你跟我一样不喜欢不习惯用界面,就使用这个命令
[root@zsx demo]# gdb hello

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qP0FxmsP-1687443931560)(…/…/images/Go/007.png)]

进入 GDB 调试界面后,并不是立即可用,你先需要回车,然后再你敲入几行命令,调试窗口就会出现代码。

(gdb) b main.main # 在main包里的main函数加断点
Breakpoint 1 at 0x47e060: file /home/zhangshixing/go_work_space/src/demo/hello.go, line 5.
(gdb) run # 执行进程
Starting program: /home/zhangshixing/go_work_space/src/demo/hello

Breakpoint 1, main.main () at /home/zhangshixing/go_work_space/src/demo/hello.go:5
5       func main(){
(gdb) c # 进入到下一个断点
Continuing.
hello, world
[Inferior 1 (process 6295) exited normally]
(gdb) q # 退出

进入断点:

(gdb) b hello.go:5 # 在hello.go的第5行设置断点
Breakpoint 1 at 0x47e060: file /home/zhangshixing/go_work_space/src/demo/hello.go, line 5. 
(gdb) b hello.go:7 # 在hello.go的第7行设置断点
Breakpoint 2 at 0x47e08d: file /home/zhangshixing/go_work_space/src/demo/hello.go, line 7.
(gdb) run # 执行进程,会进入第一个断点
Starting program: /home/zhangshixing/go_work_space/src/demo/hello

Breakpoint 1, main.main () at /home/zhangshixing/go_work_space/src/demo/hello.go:5
5       func main(){
(gdb) c # 进入到下一个断点
Continuing.

Breakpoint 2, main.main () at /home/zhangshixing/go_work_space/src/demo/hello.go:7
7         fmt.Println(msg)
(gdb) n # 执行下一步
hello, world
8       }
(gdb) q # 退出

下面将详解介绍调试指令:

  • r:run,执行程序

  • n:next,下一步,不进入函数

  • s:step,下一步,会进入函数

  • b:breakponit,设置断点

  • l:list,查看源码

  • c:continue,继续执行到下一断点

  • bt:backtrace,查看当前调用栈

  • p:print,打印查看变量

  • q:quit,退出 GDB

  • whatis:查看对象类型

  • info breakpoints:查看所有的断点

  • info locals:查看局部变量

  • info args:查看函数的参数值及要返回的变量值

  • info frame:堆栈帧信息

  • info goroutines:查看 goroutines 信息。在使用前 ,需要注意先执行 source

    /usr/local/go/src/runtime/runtime-gdb.py

  • goroutine 1 bt:查看指定序号的 goroutine 调用堆栈

  • 回车:重复执行上一次操作

其中有几个指令的使用比较灵活:

  • 比如l(list),查看代码

    # 查看指定行数上下5行
    # 显示十行代码,其中第8行在显示的十行里面的中间
    (gdb) l 8
    
    # 查看指定范围的行数
    (gdb) l 5:8
    
    # 查看指定文件的行数上下5行
    (gdb) l hello.go:8
    
    # 可以查看函数,记得加包名
    (gdb) l main.main
    
  • 把上面的 l 换成 b ,大多数也同样适用

    # 在指定行打断点
    (gdb) b 8
    
    # 在指定指定文件的行打断点
    (gdb) b hello.go:8
    
    # 在指定函数打断点,记得加包名
    (gdb) b main.main
    
  • 还有p(print),打印变量

    # 查看变量
    (gdb) p var
    
    # 查看对象长度或容量
    (gdb) p $len(var)
    (gdb) p $cap(var)
    
    # 查看对象的动态类型
    (gdb) p $dtype(var)
    (gdb) iface var
    

新增的命令会出现在help info里。

新增的函数会出现在help function里。

官方文档:https://golang.org/doc/gdb

参考手册:

https://www.bookstack.cn/books/cgdb-manual-in-chinese

https://www.kancloud.cn/wizardforcel/gdb-tips-100

1.1 GDB调试简单使用

1、编写go程序

[root@zsx src]# tree demo/
demo/
├── go.mod
├── main.go
└── mylib
    └── dbgTest.go
package main

import (
	"demo/mylib"
	"fmt"
	"os"
)

func main() {
	fmt.Println("Golang dbg test...")
	var argc = len(os.Args)
	var argv = append([]string{}, os.Args...)
	fmt.Printf("argc:%d\n", argc)
	fmt.Printf("argv:%v\n", argv)
	var var1 = 1
	var var2 = "golang dbg test"
	var var3 = []int{1, 2, 3}
	var var4 mylib.MyStruct
	var4.A = 1
	var4.B = "golang dbg my struct field B"
	var4.C = map[int]string{1: "value1", 2: "value2", 3: "value3"}
	var4.D = []string{"D1", "D2", "D3"}
	mylib.DBGTestRun(var1, var2, var3, var4)
	fmt.Println("Golang dbg test over")
}
package mylib

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

type MyStruct struct {
	A int
	B string
	C map[int]string
	D []string
}

func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) {
	fmt.Println("DBGTestRun Begin!\n")
	waiter := &sync.WaitGroup{}
	waiter.Add(1)
	go RunFunc1(var1, waiter)
	waiter.Add(1)
	go RunFunc2(var2, waiter)
	waiter.Add(1)
	go RunFunc3(&var3, waiter)
	waiter.Add(1)
	go RunFunc4(&var4, waiter)
	waiter.Wait()
	fmt.Println("DBGTestRun Finished!\n")
}
func RunFunc1(variable int, waiter *sync.WaitGroup) {
	fmt.Printf("var1:%v\n", variable)
	for {
		if variable != 123456 {
			continue
		} else {
			break
		}
	}
	time.Sleep(10 * time.Second)
	waiter.Done()
}
func RunFunc2(variable string, waiter *sync.WaitGroup) {
	fmt.Printf("var2:%v\n", variable)
	time.Sleep(10 * time.Second)
	waiter.Done()
}
func RunFunc3(pVariable *[]int, waiter *sync.WaitGroup) {
	fmt.Printf("*pVar3:%v\n", *pVariable)
	time.Sleep(10 * time.Second)
	waiter.Done()
}
func RunFunc4(pVariable *MyStruct, waiter *sync.WaitGroup) {
	fmt.Printf("*pVar4:%v\n", *pVariable)
	time.Sleep(10 * time.Second)
	waiter.Done()
}

其中,main.go为主函数入口,而dbgTest.go启动多个goroutine,用于演示调试操作。

2、编译程序

在对程序进行调试前,我们需要对目标程序进行调试版本程序的编译。

C/C++程序,我们会通过gcc/g++进行编译、链接时加入-g3等参数,使得程序编译时带入调试信息,进而让调试

器能够最终并解释相关的程序代码。

同样的,在我们对Golang程序进行调试时,我们也需要加入相应的编译、链接选项:-gcflags="-N -l",生成

程序调试信息(-N -l用于关闭编译器的内联优化)。编译GoDbg项目指令:

[root@zsx demo]# go build -gcflags="-N -l" ../demo
[root@zsx demo]# ll
total 1764
-rwxr-xr-x. 1 root root 1796373 Feb  8 09:17 demo # 新生成的文件
-rw-r--r--. 1 root root      21 Feb  8 09:12 go.mod
-rw-r--r--. 1 root root     588 Feb  8 09:11 main.go
drwxr-xr-x. 2 root root      24 Feb  8 09:12 mylib
# 也可以单独编译文件
[root@zsx demo]# go build -gcflags '-N -l' main.go

3、启动调试程序(gdb

[root@zsx demo]# gdb ./demo
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
......
(gdb)

4、在main函数上设置断点(b

(gdb) b main.main
Breakpoint 1 at 0x480a80: file /home/zhangshixing/go_work_space/src/demo/main.go, line 9.

5、带参数启动程序(r

(gdb) r arg1 arg2
Starting program: /home/zhangshixing/go_work_space/src/demo/./demo arg1 arg2

Breakpoint 1, main.main () at /home/zhangshixing/go_work_space/src/demo/main.go:9
9       func main() {

6、在文件dbgTest.go上通过行号设置断点(b

(gdb) b dbgTest.go:16
Breakpoint 2 at 0x480240: file /home/zhangshixing/go_work_space/src/demo/mylib/dbgTest.go, line 16.

7、查看断点设置情况(info b

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000480a80 in main.main
                                                   at /home/zhangshixing/go_work_space/src/demo/main.go:9
        breakpoint already hit 1 time
2       breakpoint     keep y   0x0000000000480240 in demo/mylib.DBGTestRun
                                                   at /home/zhangshixing/go_work_space/src/demo/mylib/dbgTest.go:16

8、禁用断点(dis n

# 参数n为断点的Num
(gdb) dis 1
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x0000000000480a80 in main.main
                                                   at /home/zhangshixing/go_work_space/src/demo/main.go:9
        breakpoint already hit 1 time
2       breakpoint     keep y   0x0000000000480240 in demo/mylib.DBGTestRun
                                                   at /home/zhangshixing/go_work_space/src/demo/mylib/dbgTest.go:16

Num为1的断点的Enb由y编程了n。

9、删除断点(del n

(gdb) del 1
(gdb) info b
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000480240 in demo/mylib.DBGTestRun
                                                   at /home/zhangshixing/go_work_space/src/demo/mylib/dbgTest.go:16

10、断点后继续执行(c

(gdb) c
Continuing.
Golang dbg test...
argc:3
argv:[/home/zhangshixing/go_work_space/src/demo/./demo arg1 arg2]

Breakpoint 2, demo/mylib.DBGTestRun (var1=1, var2=..., var3=..., var4=...)
    at /home/zhangshixing/go_work_space/src/demo/mylib/dbgTest.go:16
16      func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) {

11、显示代码(l

(gdb) l
11              B string
12              C map[int]string
13              D []string
14      }
15
16      func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) {
17              fmt.Println("DBGTestRun Begin!\n")
18              waiter := &sync.WaitGroup{}
19              waiter.Add(1)
20              go RunFunc1(var1, waiter)

12、单步执行(n

(gdb) n
17              fmt.Println("DBGTestRun Begin!\n")
(gdb) n
DBGTestRun Begin!

18              waiter := &sync.WaitGroup{}
(gdb) n
19              waiter.Add(1)

13、打印变量信息(print/p

在进入DBGTestRun的地方设置断点(b dbgTest.go:16),进入该函数后,通过p命令显示对应变量:

(gdb) p var1
$1 = 1
(gdb) p var2
$2 = 0x499f30 "golang dbg test"
(gdb) p var3
$3 = <optimized out>
(gdb) p var4
$4 = {A = 1, B = 0x49c948 "golang dbg my struct field B", C = 0xc0000101e0, D = {
    array = 0xc000010210, len = 3, cap = 3}}

14、查看调用栈(bt),切换调用栈(f n),显示当前栈变量信息

(gdb) bt
#0  demo/mylib.DBGTestRun (var1=1, var2=..., var3=..., var4=...)
    at /home/zhangshixing/go_work_space/src/demo/mylib/dbgTest.go:17
#1  0x0000000000481185 in main.main ()
    at /home/zhangshixing/go_work_space/src/demo/main.go:23
(gdb) f 1
#1  0x0000000000481185 in main.main ()
    at /home/zhangshixing/go_work_space/src/demo/main.go:23
23              mylib.DBGTestRun(var1, var2, var3, var4)
(gdb) l
18              var var4 mylib.MyStruct
19              var4.A = 1
20              var4.B = "golang dbg my struct field B"
21              var4.C = map[int]string{1: "value1", 2: "value2", 3: "value3"}
22              var4.D = []string{"D1", "D2", "D3"}
23              mylib.DBGTestRun(var1, var2, var3, var4)
24              fmt.Println("Golang dbg test over")
25      }
(gdb) p var1
$5 = 1
(gdb) p var2
$6 = 0x499f30 "golang dbg test"
(gdb) p var3
$7 = {array = 0xc0000160c0, len = 3, cap = 3}
(gdb) p var4
$8 = {A = 1, B = 0x49c948 "golang dbg my struct field B", C = 0xc0000101e0, D = {
    array = 0xc000010210, len = 3, cap = 3}}

2、devel工具

  • 追踪程序中的异常代码
  • 通过打印日志的方式,追查问题效率比较低
  • devel是一种工具,直接分析程序执行的情况

GitHub:https://github.com/derekparker/delve

2.1 简单使用

1、下载编译

# 这种方式安装可能版本太低,使用下面的方式安装
cd $GOPATH/src/
git clone https://github.com/derekparker/delve.git
cd delve/cmd/dlv/
go build
go install
$ go install github.com/go-delve/delve/cmd/dlv@latest

2、查看安装情况

[root@zsx dlv]# which dlv
/home/zhangshixing/go/bin/dlv
[root@zsx dlv]# dlv version
Delve Debugger
Version: 1.20.1
Build: $Id: 96e65b6c615845d42e0e31d903f6475b0e4ece6e

3、编写go文件

// delvetest/main.go
package main

import "fmt"

func Add(x, y int) int {
	return x + y
}
func main()  {
	a := 100
	b:=200
	c := Add(a, b)
	fmt.Println(c)
}

4、设置断点

[root@zsx delvetest]# dlv debug main.go
Type 'help' for list of commands.
(dlv) b main.main // break 包名:包里面的mian函数名
Breakpoint 1 set at 0x49674a for main.main() ./main.go:8
(dlv)

5、执行程序

(dlv) c
> main.main() ./main.go:8 (hits goroutine(1):1 total:1) (PC: 0x49674a)
     3: import "fmt"
     4:
     5: func Add(x, y int) int {
     6:         return x + y
     7: }
=>   8: func main()  {
     9:         a := 100
    10:         b:=200
    11:         c := Add(a, b)
    12:         fmt.Println(c)
    13: }
(dlv)

6、下一步

(dlv) next
> main.main() ./main.go:9 (PC: 0x496758)
     4:
     5: func Add(x, y int) int {
     6:         return x + y
     7: }
     8: func main()  {
=>   9:         a := 100
    10:         b:=200
    11:         c := Add(a, b)
    12:         fmt.Println(c)
    13: }
(dlv)
(dlv) next
> main.main() ./main.go:10 (PC: 0x496761)
     5: func Add(x, y int) int {
     6:         return x + y
     7: }
     8: func main()  {
     9:         a := 100
=>  10:         b:=200
    11:         c := Add(a, b)
    12:         fmt.Println(c)
    13: }
(dlv) next
> main.main() ./main.go:11 (PC: 0x49676a)
     6:         return x + y
     7: }
     8: func main()  {
     9:         a := 100
    10:         b:=200
=>  11:         c := Add(a, b)
    12:         fmt.Println(c)
    13: }
(dlv) 

7、查看变量值

(dlv) p a
100
(dlv) p b
200
(dlv)

8、重新执行

(dlv) r
Process restarted with PID 99641
(dlv)
(dlv) next
> main.main() ./main.go:8 (hits goroutine(1):1 total:1) (PC: 0x49674a)
     3: import "fmt"
     4:
     5: func Add(x, y int) int {
     6:         return x + y
     7: }
=>   8: func main()  {
     9:         a := 100
    10:         b:=200
    11:         c := Add(a, b)
    12:         fmt.Println(c)
    13: }
(dlv)

9、单步进入(遇到函数调用)

(dlv) s
> main.main() ./main.go:9 (PC: 0x496758)
     4:
     5: func Add(x, y int) int {
     6:         return x + y
     7: }
     8: func main()  {
=>   9:         a := 100
    10:         b:=200
    11:         c := Add(a, b)
    12:         fmt.Println(c)
    13: }
(dlv) s
> main.main() ./main.go:10 (PC: 0x496761)
     5: func Add(x, y int) int {
     6:         return x + y
     7: }
     8: func main()  {
     9:         a := 100
=>  10:         b:=200
    11:         c := Add(a, b)
    12:         fmt.Println(c)
    13: }
(dlv) s
> main.main() ./main.go:11 (PC: 0x49676a)
     6:         return x + y
     7: }
     8: func main()  {
     9:         a := 100
    10:         b:=200
=>  11:         c := Add(a, b)
    12:         fmt.Println(c)
    13: }
(dlv) s
> main.Add() ./main.go:5 (PC: 0x496700)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5: func Add(x, y int) int {
     6:         return x + y
     7: }
     8: func main()  {
     9:         a := 100
    10:         b:=200
(dlv)

10、退出调试

(dlv) q

2.2 调试正在运行的程序

1、go程序

// delvetest/main.go
package main

import (
    "fmt"
    "time"
)

func main()  {
    var i = 0
    for {
	var curTime time.Time
	time.Sleep(5 * time.Second)
	i++
	curTime = time.Now()
	fmt.Printf("run %d count, cur time: %v\n", i, curTime)
    }
}
[root@zsx delvetest]# go build main.go
[root@zsx delvetest]# ./main
run 1 count, cur time: 2023-02-07 21:49:53.993267564 +0800 CST m=+5.000796406
run 2 count, cur time: 2023-02-07 21:49:58.994410425 +0800 CST m=+10.001939218
run 3 count, cur time: 2023-02-07 21:50:03.994891606 +0800 CST m=+15.002420304
[root@zsx delvetest]# ps aux | grep main
[root@zsx delvetest]# ps -ef | grep main
root        741      1  0 Jan30 ?        00:00:00 /usr/sbin/alsactl -s -n 19 -c -E ALSA_CONFIG_PATH=/etc/alsa/alsactl.conf --initfile=/lib/alsa/init/00main rdaemon
root       1076 103356  0 21:49 pts/1    00:00:00 ./main
root       1752 110709  0 21:51 pts/2    00:00:00 grep --color=auto main

2、开启调试

[root@zsx delvetest]# dlv attach 1076
Type 'help' for list of commands.
(dlv) b main.go:13
Breakpoint 1 set at 0x48ae4e for main.main() ./main.go:13

3、执行

(dlv) c
> main.main() ./main.go:13 (hits goroutine(1):1 total:1) (PC: 0x48ae4e)
Warning: debugging optimized function
     8: func main()  {
     9:     var i = 0
    10:     for {
    11:         var curTime time.Time
    12:         time.Sleep(5 * time.Second)
=>  13:         i++
    14:         curTime = time.Now()
    15:         fmt.Printf("run %d count, cur time: %v\n", i, curTime)
    16:     }
    17: }

4、下一步

(dlv) next
> main.main() ./main.go:12 (PC: 0x48aeca)
Warning: debugging optimized function
     7:
     8: func main()  {
     9:     var i = 0
    10:     for {
    11:         var curTime time.Time
=>  12:         time.Sleep(5 * time.Second)
    13:         i++
    14:         curTime = time.Now()
    15:         fmt.Printf("run %d count, cur time: %v\n", i, curTime)
    16:     }
    17: }

5、查看变量i的值

(dlv) p i
4

6、退出

(dlv) q
Would you like to kill the process? [Y/n] y

2.3 多线程调试

1、go程序

// delvetest/main.go
package main

import (
    "fmt"
    "time"
)

func isPrime(n int) bool {
    if n <= 1 {
	return false
    }
    for i := 2; i < n; i++ {
	if n % i == 0 {
    	    return false
        }
    }
    return true
}

// 生产素数
func produceSushu(c chan int)  {
    i := 1
    for {
	result := isPrime(i)
	if result {
	    c <- i
	}
	time.Sleep(time.Second)
	i++
    }
}

func consumeSushu(c chan int)  {
    for v := range c {
	fmt.Printf("%d is prime\n", v)
    }
}

func main()  {
    // 声明一个容量为1000的整形队列
    var intChan chan int = make(chan int, 1000)
    go produceSushu(intChan)  // go 开启一个线程
    go consumeSushu(intChan)

    time.Sleep(time.Hour)
}

2、开启调试

[root@zsx delvetest]# dlv debug main.go
Type 'help' for list of commands.
(dlv)

3、设置断点

(dlv) b produceSushu
Breakpoint 1 set at 0x498426 for main.produceSushu() ./main.go:21

4、执行

(dlv) c
> main.produceSushu() ./main.go:21 (hits goroutine(6):1 total:1) (PC: 0x498426)
    16:     }
    17:     return true
    18: }
    19:
    20: // 生产素数
=>  21: func produceSushu(c chan int)  {
    22:     i := 1
    23:     for {
    24:         result := isPrime(i)
    25:         if result {
    26:             c <- i
(dlv)

5、下一步

(dlv) next
> main.produceSushu() ./main.go:22 (PC: 0x498439)
    17:     return true
    18: }
    19:
    20: // 生产素数
    21: func produceSushu(c chan int)  {
=>  22:     i := 1
    23:     for {
    24:         result := isPrime(i)
    25:         if result {
    26:             c <- i
    27:         }
(dlv)

6、查看当前程序有几个线程

(dlv) goroutines
  Goroutine 1 - User: /home/zhangshixing/go/src/runtime/time.go:194 time.Sleep (0x45e77b) [sleep]
  Goroutine 2 - User: /home/zhangshixing/go/src/runtime/proc.go:362 runtime.gopark (0x437812) [force gc (idle)]
  Goroutine 3 - User: /home/zhangshixing/go/src/runtime/proc.go:362 runtime.gopark (0x437812) [GC sweep wait]
  Goroutine 4 - User: /home/zhangshixing/go/src/runtime/proc.go:362 runtime.gopark (0x437812) [GC scavenge wait]
  Goroutine 5 - User: /home/zhangshixing/go/src/runtime/proc.go:362 runtime.gopark (0x437812) [finalizer wait]
* Goroutine 6 - User: ./main.go:22 main.produceSushu (0x498439) (thread 127301)
  Goroutine 7 - User: ./main.go:34 main.consumeSushu (0x498505) [chan receive]
[7 goroutines]

7、切换线程

(dlv) goroutine 1
Switched from 6 to 1 (thread 127301)

8、查看当前线程堆栈信息

(dlv) bt
0  0x0000000000437812 in runtime.gopark
   at /home/zhangshixing/go/src/runtime/proc.go:362
1  0x000000000045e77b in time.Sleep
   at /home/zhangshixing/go/src/runtime/time.go:194
2  0x00000000004986cb in main.main
   at ./main.go:45
3  0x00000000004373f8 in runtime.main
   at /home/zhangshixing/go/src/runtime/proc.go:250
4  0x0000000000461781 in runtime.goexit
   at /home/zhangshixing/go/src/runtime/asm_amd64.s:1571

2.4 dlv命令

args ------------------------ Print function arguments. # 打印函数参数.
break (alias: b) ------------ Sets a breakpoint. # 设置断点.
breakpoints (alias: bp) ----- Print out info for active breakpoints. # 输出活动断点的信息.
call ------------------------ Resumes process, injecting a function call (EXPERIMENTAL!!!) # 恢复进程,注入一个函数调用(还在实验阶段!!)
clear ----------------------- Deletes breakpoint. # 删除断点.
clearall -------------------- Deletes multiple breakpoints. # 删除多个断点.
condition (alias: cond) ----- Set breakpoint condition. # 设置断点条件.
config ---------------------- Changes configuration parameters. # 修改配置参数.
continue (alias: c) --------- Run until breakpoint or program termination. # 运行到断点或程序终止.
deferred -------------------- Executes command in the context of a deferred call. # 在延迟调用的上下文中执行命令.
disassemble (alias: disass) - Disassembler. # 反汇编程序.
down ------------------------ Move the current frame down. # 将当前帧向下移动.
edit (alias: ed) ------------ Open where you are in $DELVE_EDITOR or $EDITOR # 在$DELVE_EDITOR或$EDITOR中打开你所在的位置
exit (alias: quit | q) ------ Exit the debugger. # 退出调试器.
frame ----------------------- Set the current frame, or execute command on a different frame. # 设置当前帧,或在不同的帧上执行命令.
funcs ----------------------- Print list of functions. # 打印函数列表.
goroutine ------------------- Shows or changes current goroutine # 显示或更改当前goroutine
goroutines ------------------ List program goroutines. # 列举程序goroutines.
help (alias: h) ------------- Prints the help message. # 打印帮助信息.
libraries ------------------- List loaded dynamic libraries # 展示加载的动态库
list (alias: ls | l) -------- Show source code. # 显示源代码.
locals ---------------------- Print local variables. # 打印局部变量.
next (alias: n) ------------- Step over to next source line. # 转到下一个源行.
on -------------------------- Executes a command when a breakpoint is hit. # 在命中断点时执行命令.
print (alias: p) ------------ Evaluate an expression. # 计算一个表达式.
regs ------------------------ Print contents of CPU registers. # 打印CPU寄存器的内容.
restart (alias: r) ---------- Restart process. # 重启进程.
set ------------------------- Changes the value of a variable. # 更改变量的值.
source ---------------------- Executes a file containing a list of delve commands # 执行包含delve命令列表的文件
sources --------------------- Print list of source files. # 打印源文件列表.
stack (alias: bt) ----------- Print stack trace. # 打印堆栈跟踪信息.
step (alias: s) ------------- Single step through program. # 单步执行程序.
step-instruction (alias: si)  Single step a single cpu instruction. # 单步执行一条cpu指令.
stepout --------------------- Step out of the current function. # 跳出当前函数.
thread (alias: tr) ---------- Switch to the specified thread. # 切换到指定的线程.
threads --------------------- Print out info for every traced thread. # 打印每个跟踪线程的信息.
trace (alias: t) ------------ Set tracepoint. # 设置跟踪点.
types ----------------------- Print list of types # 打印类型列表
up -------------------------- Move the current frame up. # 向上移动当前帧.
vars ------------------------ Print package variables. # 打印包变量.
whatis ---------------------- Prints type of an expression. # 打印表达式的类型.

2.5 调试流程示例

// 1.找到项目运行的进程id,attach上去进入dlv
$ ps aux | grep 项目名称
$ dlv attach 进程id

// 2.b(break) 断点,文件+行号形式
(dlv) b controller/search.go:20

// 3.c(continue) 继续运行调转到断点处
(dlv) c

// 4.bp(breakpoints) 查看活动断点信息,可以看到会同时为panic也自动打上断点

// Breakpoint runtime-fatal-throw at 0x4402c0 for runtime.fatalthrow() /usr/local/go/src/runtime/panic.go:1163 (0)
// Breakpoint unrecovered-panic at 0x440340 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1190 (0)
//         print runtime.curg._panic.arg
// Breakpoint 1 at 0x10455f3 for example/controller/search.Ctrl() /workspace/git-resource/controller/search.go:20 (19)
(dlv) bp

// 5.n(next) 跳转到下一行,然后想要继续向下可以直接回车,默认重复执行上一条命令

// example/controller/search.Ctrl() /workspace/git-resource/controller/search.go:21 (PC: 0x1045615)
(dlv) n

// 6.p(print) 打印变量的值

// example/service/global/globaltype.Data [
//      "59": ["104958","104957"],
//  ]
(dlv) p slotAdIds

// 7.locals 打印局部变量
(dlv) locals

// 8.s(step) 单步执行
(dlv) s

// 9.whatis 打印表达式类型
(dlv) whatis slotAdIds

// 10.stepout 跳出当前函数
(dlv) stepout

// 11.goroutine 查看当前处于的goroutine   goroutines查看所有的goroutine   goroutine 1 从当前goroutine调到编号为1的goroutine

// Thread 1033702 at ./service/search.go:23
// Goroutine 36020:
//      Runtime: ./service/search.go:23 example/search.DoSearch (0x1b42c2b)
//      User: ./service/search.go:23 example/search.DoSearch (0x1b42c2b)
//      Go: /usr/local/Cellar/go/1.16.2/libexec/src/net/http/server.go:3013 net/http.(*Server).Serve (0x15766ae)
//      Start: /usr/local/Cellar/go/1.16.2/libexec/src/net/http/server.go:1817 net/http.(*conn).serve (0x156df40)
(dlv) goroutine

// 12.args 打印函数参数和返回值

// adCtx = ("*example/util/contextx.Context")(0xc00b9505b0)
// ~r1 = example/service/global/globaltype.Datas nil
// ~r2 = error nil
(dlv) args

// 13. clear 1 删除第一个断点 clearall 删除所有断点
(dlv) clear 1
(dlv) clearall

// 14. set 改变变量的值  只能更改数值变量和指针的值
(dlv) set tmp = 2

// 15. exit(q) 退出
(dlv) q

3 、比较

综合比较两个Golang程序调试器gdb和dlv,我认为dlv的功能更为完善,更能满足实际调试时的功能需求。两者的

优缺点比较大致如下:

调试器优势不足
dlv对goroutine环境调试支持比较完善暂时没找到
gdb符合现有的调试习惯,类似C/C++调试指令都有对goroutine场景支持不足,不能很好的应对goroutine的调试

(1)、dlv对goroutine的支持更好,使用gdb没有找到goroutine的调试方法。

(2)、gdb对于局部引用变量无法调试,dlv不会。

package main

import "fmt"

func main() {
	i := 10
	j := &i
	fmt.Println(j)
}
$ gdb test
(gdb) b test.go:8
Breakpoint 1 at 0x47e09b: file /home/zhangshixing/go_work_space/src/delvetest/test.go, line 8.
(gdb) run
Starting program: /home/zhangshixing/go_work_space/src/delvetest/test

Breakpoint 1, main.main () at /home/zhangshixing/go_work_space/src/delvetest/test.go:8
8               fmt.Println(j)
(gdb) p i
No symbol "i" in current context.
(gdb) p j
$1 = (int *) 0xc0000180d0
(gdb) q
$ dlv debug test.go
Type 'help' for list of commands.
(dlv) b test.go:8
Breakpoint 1 set at 0x49673b for main.main() ./test.go:8
(dlv) c
> main.main() ./test.go:8 (hits goroutine(1):1 total:1) (PC: 0x49673b)
     3: import "fmt"
     4:
     5: func main() {
     6:         i := 10
     7:         j := &i
=>   8:         fmt.Println(j)
     9: }
    10:
(dlv) p i
10
(dlv) p j
*10
(dlv) q

gdb 使用 p i 打印变量i的时候报错,dlv却可以。

(3)、dlv无法调试interface等Go内部实现的一些结构,gdb是可以的。

;