Golang Debugging with GDB

Table of Contents

1. 使用 gdb 调试 golang 程序

使用 gdb 可以调试 golang 程序。默认地,go build 命令编译出来二进制文件已经包含了 DWARFv3 调试信息。

delve 是另外一个 golang 程序的调试器,这里不介绍它。

参考:Debugging Go Code with GDB

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

参考:https://blog.codeship.com/using-gdb-debugger-with-go/

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。

Author: cig01

Created: <2019-05-20 Mon>

Last updated: <2019-05-22 Wed>

Creator: Emacs 27.1 (Org mode 9.4)