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。