Golang Debugging with GDB
Table of Contents
1. 使用 gdb 调试 golang 程序
使用 gdb 可以调试 golang 程序。默认地,go build 命令编译出来二进制文件已经包含了 DWARFv3 调试信息。
delve 是另外一个 golang 程序的调试器,这里不介绍它。
1.1. 准备工作:禁止优化和内联(-gcflags="all=-N -l")
编译 golang 程序时,指定 go build 的选项 -gcflags="all=-N -l" 可以禁止优化(-N)和禁止内联(-l),如:
$ go build -gcflags="all=-N -l" -o=/tmp/foo .
1.2. 准备工作:启动 gdb 时自动加载 runtime-gdb.py
要使 gdb 理解 goroutine,启动 gdb 时需要加载扩展脚本 runtime-gdb.py 。不过,一般不用我们手动加载它。
使用 go build 编译程序时会自动把该脚本写入到可执行程序的 .debug_gdb_scripts 节中:
$ readelf -p .debug_gdb_scripts your_golang_app String dump of section '.debug_gdb_scripts': [ 1] /home/ec2-user/go/src/runtime/runtime-gdb.py
gdb 会自动加载 .debug_gdb_scripts 节中的脚本,参见:The .debug_gdb_scripts section 。
不过,为安全起风,gdb 默认只会加载特定目录(可通过 show auto-load safe-path 命令查看)中的脚本。 要禁止对加载脚本位置的限制,请在文件 ${HOME}/.gdbinit 中增加下面内容:
set auto-load safe-path /
如果只想临时禁止对加载脚本位置的限制,则可以这样:
$ gdb -iex "set auto-load safe-path /" ./your_golang_app ...... Loading Go Runtime support. (gdb)
启动 gdb 时,显示了“Loading Go Runtime support.”后,说明 runtime-gdb.py 已经成功加载。
2. runtime-gdb.py 增加的命令
2.1. 函数 $len() 和 $cap()
runtime-gdb.py 为 strings,slices 和 maps 增加了函数 $len() 和 $cap() ,可以在 gdb 中直接使用,如:
(gdb) p $len(utf) $23 = 4 (gdb) p $cap(utf) $24 = 4
2.2. 查看 interface 的动态类型
runtime-gdb.py 中新增了函数 $dtype() 或者命令 iface ,使用它们可以查看 interface 的动态类型,如:
(gdb) p i
$4 = {str = "cbb"}
(gdb) p $dtype(i)
$26 = (struct regexp.inputBytes *) 0xf8400b4930
(gdb) iface i
regexp.input: struct regexp.inputBytes *
2.3. 对 goroutine 的支持
对 goroutine 的支持,runtime-gdb.py 增加了下面命令:
(gdb) info goroutines # 列出所有的goroutines (gdb) goroutine n cmd # 对第n个goroutine,执行命令cmd
如:
(gdb) info goroutines # 查看所有的goroutines
1 waiting runtime.gopark
2 waiting runtime.gopark
3 waiting runtime.gopark
4 waiting runtime.gopark
* 5 running main.main.func1
6 runnable main.main.func1
7 runnable main.main.func1
8 runnable main.main.func1
9 runnable main.main.func1
* 10 running main.main.func1
11 runnable main.main.func1
12 runnable main.main.func1
13 runnable main.main.func1
14 runnable main.main.func1
15 waiting runtime.gopark
(gdb) goroutine 11 bt # 查看第11个goroutine的栈帧
#0 main.main.func1 (val=6, pairChan=0xc82001a180, &wg=0xc82000a3a0)
at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:19
#1 0x0000000000454991 in runtime.goexit () at /usr/local/go/src/runtime/asm_amd64.s:1696
#2 0x0000000000000006 in ?? ()
#3 0x000000c82001a180 in ?? ()
#4 0x000000c82000a3a0 in ?? ()
#5 0x0000000000000000 in ?? ()
(gdb) goroutine 11 l # 查看第11个goroutine的执行的源码
48 return x*x + x
49 }
(gdb) goroutine 11 info args # 查看第11个goroutine的当前变量(&开头的表示goroutine外的变量)
val = 6
pairChan = 0xc82001a180
&wg = 0xc82000a3a0
(gdb) goroutine 11 p val # 打印第11个goroutine中变量val的值
$2 = 6
3. 实例:启动 gdb
使用 gdb 启动程序:
$ gdb -q ./app Reading symbols from /home/ec2-user/go/src/xxx.com/app...done. Loading Go Runtime support.
attach 到正在运行的进程(依次指定可执行程序位置和进程号即可):
$ gdb ./app 11574 Reading symbols from /home/ec2-user/go/src/xxx.com/app...done. Attaching to program: /home/ec2-user/go/src/xxx.com/./app, process 18634 Reading symbols from /usr/lib64/libpthread.so.0...(no debugging symbols found)...done. [New LWP 18704] [New LWP 18682] [New LWP 18661] [New LWP 18658] [New LWP 18644] [New LWP 18643] [New LWP 18642] [Thread debugging using libthread_db enabled] Using host libthread_db library "/usr/lib64/libthread_db.so.1". Loaded symbols for /usr/lib64/libpthread.so.0 Reading symbols from /usr/lib64/libc.so.6...(no debugging symbols found)...done. Loaded symbols for /usr/lib64/libc.so.6 Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done. Loaded symbols for /lib64/ld-linux-x86-64.so.2 runtime.futex () at /home/ec2-user/go/src/runtime/sys_linux_amd64.s:532 532 MOVL AX, ret+40(FP) Loading Go Runtime support. Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64
不推荐使用 gdb -p pid 命令 attach 到正在运行的进程,因为这样启动时没有指定可执行程序位置,所以不能读取它的 .debug_gdb_scripts 信息,从而不会自动加载runtime-gdb.py。