Go的Debug工具

338 阅读3分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
go常用Debug工具包括GDBDelve,两者适用的场景有所区别

  • GDB:适合调试CGO代码、runtime等
  • Delve:大多数场景下建议使用

编译

go build -ldflags="-w -s"

设置flags为ldflags="-w -s"可以获得最小的二进制文件。 编译和链接时会包含一些DWARFv4调试信息,传递-w参数可以省略这些调试信息,无法使用gdb查看特定的函数、设置断点、获取堆栈跟踪,gdb需要的元数据都不包含在内。 -s参数关闭Go符号表的生成,将无法使用go工具nm列出二进制中的符号。

go build -gcflags=all="-N -l"

编译器编译时会进行一些优化操作,包括内联函数调用、变量注册、优化会使得调试过程困难,编译代码时最好取消优化,-N取消编译器优化,-l取消内联优化

使用GDB调试go

代码显示、断点、反汇编

(gdb) list 
(gdb) list line 
(gdb) list file.go:line 
(gdb) break line 
(gdb) break file.go:line 
(gdb) disas
  • list:展示代码
  • list line:展示指定行代码
  • list file.go:line:展示指定文件、指定行的代码
  • break line:指定行设置断点
  • break file.go:line:指定文件、指定行设置断点
  • disas:反汇编

栈帧信息

栈帧(stack frame):每次执行函数调用时,会生成该函数的调用信息。包括程序调用位置、调用参数、被调用函数的参数。这些信息被存储在栈帧中

(gdb) bt (gdb) frame n

n=0表示当前栈帧,n=1表示调用者栈帧,层层向上。

实例

package main 
import "fmt" 
func main() { 
    x := 1 
    fmt.Println(x) 
    print("1111") 
} 
func print(str interface{}) 
{ 
    fmt.Println(str) 
}

上述功能实际操作举例:以下面这段代码为例,设置断点在print("1111"),键入s,进入print函数,则当前栈帧保存print函数的参数、局部变量,当前frame 0为正在执行的print函数的信息,上一级调用者是main函数,保存的信息在frame1,可以看到下面截图中信息均为print函数里的信息 Untitled.png

使用Delve调试go

安装Delve

go get -u github.com/go-delve/delve/cmd/dlv

使用

当前目录是main.go所在目录直接键入dlv debug。如果当前目录不是main.go所在目录则键入dlv debug github.com/me/xxx (xxx表示main.go所在目录) 传递命令行参数可以键入 dlv debug github.com/me/xxx - - -arg1 value

  • 设置断点(break point)和持续执行(continue execution)程序
(dlv) break main.main 
(dlv) continue

Untitled1.png

总结

使用IDE调试方便和易于观察,但是使用gdb和Delve手动调试,可以让我们学到一些基本原理。Delve可以查看当前goroutine和线程的数量,并且可以切换当前所在线程,相较于GDB更适合调试GO程序,GDB是更为通用的Debug工具。

参考

  1. golang.org/doc/gdb (Debugging Go Code with GDB)
  2. sourceware.org/gdb/current… (GDB manual)
  3. github.com/go-delve/de…(Delve document)
  4. kirste.userpage.fu-berlin.de/chemnet/use… (what is stack frame)
  5. www.cnblogs.com/awakenedy/a… (info goroutine的使用)
  6. github.com/go-delve/de…(delve github)
  7. github.com/go-delve/de… (delve github 开始说明)