「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
go常用Debug工具包括GDB和Delve,两者适用的场景有所区别
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函数里的信息
使用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
总结
使用IDE调试方便和易于观察,但是使用gdb和Delve手动调试,可以让我们学到一些基本原理。Delve可以查看当前goroutine和线程的数量,并且可以切换当前所在线程,相较于GDB更适合调试GO程序,GDB是更为通用的Debug工具。
参考
- golang.org/doc/gdb (Debugging Go Code with GDB)
- sourceware.org/gdb/current… (GDB manual)
- github.com/go-delve/de…(Delve document)
- kirste.userpage.fu-berlin.de/chemnet/use… (what is stack frame)
- www.cnblogs.com/awakenedy/a… (info goroutine的使用)
- github.com/go-delve/de…(delve github)
- github.com/go-delve/de… (delve github 开始说明)