Debugging Go Code with GDB

以下说明适用于标准工具链( gc Go编译器和工具). Gccgo具有本地gdb支持.

请注意,在调试使用标准工具链构建的Go程序时, Delve是GDB的更好替代方案. 它比GDB更了解Go运行时,数据结构和表达式. Delve当前在amd64上支持Linux,OSX和Windows. 有关受支持平台的最新列表,请参阅Delve文档 .

GDB不太了解Go程序. 堆栈管理,线程和运行时所包含的方面与执行模型有很大不同,GDB希望它们会混淆调试器并导致错误的结果,即使使用gccgo编译程序也是如此. 因此,尽管GDB在某些情况下(例如调试Cgo代码或调试运行时本身)可能很有用,但它并不是Go程序(尤其是大量并发程序)的可靠调试器. 而且,Go项目解决这些困难的问题不是优先事项.

简而言之,下面的说明仅应作为GDB工作时如何使用GDB的指南,而不是成功的保证. 除了本概述之外,您可能还需要查阅GDB手册 .

Introduction

在Linux,macOS,FreeBSD或NetBSD上使用gc工具链编译和链接Go程序时,生成的二进制文件包含DWARFv4调试信息,GDB调试器的最新版本(≥7.5)可用于检查实时进程或核心转储. .

'-w'标志传递给链接器以忽略调试信息(例如, go build -ldflags=-w prog.go ).

gc编译器生成的代码包括内联函数调用和变量注册. 这些优化有时会使使用gdb进行调试更加困难. 如果发现需要禁用这些优化,请使用go build -gcflags=all="-N -l"build程序.

如果要使用gdb检查核心转储,则可以通过在环境中设置GOTRACEBACK=crash在允许程序崩溃的系统上触发转储(有关更多信息,请参见运行时程序包文档 ).

Common Operations

Go Extensions

GDB的最新扩展机制允许它为给定的二进制文件加载扩展脚本. 工具链使用它通过一些命令扩展GDB,以检查运行时代码的内部(例如goroutines)并漂亮地打印内置映射,切片和通道类型.

如果您想了解它的工作原理或想要扩展它,请查看Go源代码发行版中的src / runtime / runtime-gdb.py . 它取决于链接器( src / cmd / link / internal / ld / dwarf.go )确保在DWARF中描述的某些特殊魔术类型( hash<T,U> )和变量( runtime.mruntime.g ).码.

如果您对调试信息的外观感兴趣,请运行objdump -W a.out并浏览.debug_*部分.

Known Issues

  1. 漂亮的字符串打印仅触发字符串类型,而不触发从其衍生的类型.
  2. 运行时库的C部分缺少类型信息.
  3. GDB无法理解Go的名称限定,因此将"fmt.Print"视为带有"."的非结构化文字"." 需要引用. 它甚至更强烈地反对pkg.(*MyType).Meth形式的方法名称.
  4. 从Go 1.11开始,调试信息默认为压缩. 较旧的gdb版本(例如MacOS上默认可用的版本)不了解压缩. 您可以使用go build -ldflags=-compressdwarf=false来生成未压缩的调试信息. (为方便起见,可以将-ldflags选项放在GOFLAGS环境变量中,这样就不必每次都指定它.)

Tutorial

在本教程中,我们将检查regexp软件包的单元测试的二进制文件. 要构建二进制文件,请更改为$GOROOT/src/regexp并运行go test -c . 这将产生一个名为regexp.test的可执行文件.

Getting Started

启动GDB,调试regexp.test

$ gdb regexp.test
GNU gdb (GDB) 7.2-gg8
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv  3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
Type "show copying" and "show warranty" for licensing/warranty details.
This GDB was configured as "x86_64-linux".

Reading symbols from  /home/user/go/src/regexp/regexp.test...
done.
Loading Go Runtime support.
(gdb) 

消息"正在加载Go运行时支持"意味着GDB从$GOROOT/src/runtime/runtime-gdb.py加载了扩展.

为了帮助GDB找到Go运行时源和随附的支持脚本,请在$GOROOT传递'-d'标志:

$ gdb regexp.test -d $GOROOT

如果由于某种原因GDB仍然找不到该目录或脚本,则可以通过告诉gdb手动加载它(假设您在~/go/有go源代码):

(gdb) source ~/go/src/runtime/runtime-gdb.py
Loading Go Runtime support.

Inspecting the source

使用"l""list"命令检查源代码.

(gdb) l

用函数名称"list"参数化"list"的源代码的特定部分(必须用其程序包名称限定).

(gdb) l main.main

列出特定的文件和行号:

(gdb) l regexp.go:1
(gdb) # Hit enter to repeat last command. Here, this lists next 10 lines.

Naming

变量和函数名称必须使用它们所属的软件包的名称进行限定. regexp包中的Compile函数在GDB中称为'regexp.Compile' .

方法必须使用其接收器类型的名称进行限定. 例如, *Regexp类型的String方法被称为'regexp.(*Regexp).String' .

阴影其他变量的变量在调试信息中带有神奇的数字后缀. 闭包引用的变量将以神奇的前缀"&"的形式出现.

Setting breakpoints

TestFind函数上设置一个断点:

(gdb) b 'regexp.TestFind'
Breakpoint 1 at 0x424908: file /home/user/go/src/regexp/find_test.go, line 148.

运行程序:

(gdb) run
Starting program: /home/user/go/src/regexp/regexp.test

Breakpoint 1, regexp.TestFind (t=0xf8404a89c0) at /home/user/go/src/regexp/find_test.go:148
148	func TestFind(t *testing.T) {

执行已在断点处暂停. 查看正在运行的goroutine,以及它们正在做什么:

(gdb) info goroutines
  1  waiting runtime.gosched
* 13  running runtime.goexit

标有*是当前goroutine.

Inspecting the stack

查看我们在哪里暂停了程序的堆栈跟踪:

(gdb) bt  # backtrace
#0  regexp.TestFind (t=0xf8404a89c0) at /home/user/go/src/regexp/find_test.go:148
#1  0x000000000042f60b in testing.tRunner (t=0xf8404a89c0, test=0x573720) at /home/user/go/src/testing/testing.go:156
#2  0x000000000040df64 in runtime.initdone () at /home/user/go/src/runtime/proc.c:242
#3  0x000000f8404a89c0 in ?? ()
#4  0x0000000000573720 in ?? ()
#5  0x0000000000000000 in ?? ()

另一个编号为1的goroutine卡在runtime.gosched ,在通道接收上被阻塞:

(gdb) goroutine 1 bt
#0  0x000000000040facb in runtime.gosched () at /home/user/go/src/runtime/proc.c:873
#1  0x00000000004031c9 in runtime.chanrecv (c=void, ep=void, selected=void, received=void)
 at  /home/user/go/src/runtime/chan.c:342
#2  0x0000000000403299 in runtime.chanrecv1 (t=void, c=void) at/home/user/go/src/runtime/chan.c:423
#3  0x000000000043075b in testing.RunTests (matchString={void (struct string, struct string, bool *, error *)}
 0x7ffff7f9ef60, tests=  []testing.InternalTest = {...}) at /home/user/go/src/testing/testing.go:201
#4  0x00000000004302b1 in testing.Main (matchString={void (struct string, struct string, bool *, error *)} 
 0x7ffff7f9ef80, tests= []testing.InternalTest = {...}, benchmarks= []testing.InternalBenchmark = {...})
at /home/user/go/src/testing/testing.go:168
#5  0x0000000000400dc1 in main.main () at /home/user/go/src/regexp/_testmain.go:98
#6  0x00000000004022e7 in runtime.mainstart () at /home/user/go/src/runtime/amd64/asm.s:78
#7  0x000000000040ea6f in runtime.initdone () at /home/user/go/src/runtime/proc.c:243
#8  0x0000000000000000 in ?? ()

堆栈框架显示出我们正在按预期执行regexp.TestFind函数.

(gdb) info frame
Stack level 0, frame at 0x7ffff7f9ff88:
 rip = 0x425530 in regexp.TestFind (/home/user/go/src/regexp/find_test.go:148); 
    saved rip 0x430233
 called by frame at 0x7ffff7f9ffa8
 source language minimal.
 Arglist at 0x7ffff7f9ff78, args: t=0xf840688b60
 Locals at 0x7ffff7f9ff78, Previous frame's sp is 0x7ffff7f9ff88
 Saved registers:
  rip at 0x7ffff7f9ff80

该命令info locals列出了函数本地的所有变量及其值,但是使用起来有些危险,因为它还会尝试打印未初始化的变量. 未初始化的切片可能会导致gdb尝试打印任意大数组.

该函数的参数:

(gdb) info args
t = 0xf840688b60

打印参数时,请注意它是一个指向Regexp值的指针. 请注意,GDB错误地将*放在类型名称的右侧,并用传统的C样式组成了'struct'关键字.

(gdb) p re
(gdb) p t
$1 = (struct testing.T *) 0xf840688b60
(gdb) p t
$1 = (struct testing.T *) 0xf840688b60
(gdb) p *t
$2 = {errors = "", failed = false, ch = 0xf8406f5690}
(gdb) p *t->ch
$3 = struct hchan<*testing.T>

struct hchan<*testing.T>是通道的运行时内部表示. 当前为空,否则gdb会漂亮地打印其内容.

向前迈进:

(gdb) n  # execute next line
149             for _, test := range findTests {
(gdb)    # enter is repeat
150                     re := MustCompile(test.pat)
(gdb) p test.pat
$4 = ""
(gdb) p re
$5 = (struct regexp.Regexp *) 0xf84068d070
(gdb) p *re
$6 = {expr = "", prog = 0xf840688b80, prefix = "", prefixBytes =  []uint8, prefixComplete = true, 
  prefixRune = 0, cond = 0 '\000', numSubexp = 0, longest = false, mu = {state = 0, sema = 0}, 
  machine =  []*regexp.machine}
(gdb) p *re->prog
$7 = {Inst =  []regexp/syntax.Inst = {{Op = 5 '\005', Out = 0, Arg = 0, Rune =  []int}, {Op = 
    6 '\006', Out = 2, Arg = 0, Rune =  []int}, {Op = 4 '\004', Out = 0, Arg = 0, Rune =  []int}}, 
  Start = 1, NumCap = 2}

我们可以使用"s"进入String函数调用:

(gdb) s
regexp.(*Regexp).String (re=0xf84068d070, noname=void) at /home/user/go/src/regexp/regexp.go:97
97      func (re *Regexp) String() string {

获取堆栈跟踪以了解我们的位置:

(gdb) bt
#0  regexp.(*Regexp).String (re=0xf84068d070, noname=void)
    at /home/user/go/src/regexp/regexp.go:97
#1  0x0000000000425615 in regexp.TestFind (t=0xf840688b60)
    at /home/user/go/src/regexp/find_test.go:151
#2  0x0000000000430233 in testing.tRunner (t=0xf840688b60, test=0x5747b8)
    at /home/user/go/src/testing/testing.go:156
#3  0x000000000040ea6f in runtime.initdone () at /home/user/go/src/runtime/proc.c:243
....

看一下源代码:

(gdb) l
92              mu      sync.Mutex
93              machine []*machine
94      }
95
96      // String returns the source text used to compile the regular expression.
97      func (re *Regexp) String() string {
98              return re.expr
99      }
100
101     // Compile parses a regular expression and returns, if successful,

Pretty Printing

GDB漂亮的打印机制是由类型名称上的regexp匹配触发的. 切片的示例:

(gdb) p utf
$22 =  []uint8 = {0 '\000', 0 '\000', 0 '\000', 0 '\000'}

由于切片,数组和字符串不是C指针,因此GDB无法为您解释下标操作,但是您可以在运行时表示形式内进行查找(此处的制表符帮助):

(gdb) p slc
$11 =  []int = {0, 0}
(gdb) p slc-><TAB>
array  slc    len    
(gdb) p slc->array
$12 = (int *) 0xf84057af00
(gdb) p slc->array[1]
$13 = 0

扩展功能$ len和$ cap适用于字符串,数组和切片:

(gdb) p $len(utf)
$23 = 4
(gdb) p $cap(utf)
$24 = 4

Channels and maps are 'reference' types, which gdb shows as pointers to C++-like types hash<int,string>*. Dereferencing will trigger prettyprinting

接口在运行时中表示为指向类型描述符的指针和指向值的指针. Go GDB运行时扩展对此进行解码,并自动触发运行时类型的漂亮打印. 扩展功能$dtype为您解码动态类型(示例取自regexp.go 293行的断点).

(gdb) p i
$4 = {str = "cbb"}
(gdb) whatis i
type = regexp.input
(gdb) p $dtype(i)
$26 = (struct regexp.inputBytes *) 0xf8400b4930
(gdb) iface i
regexp.input: struct regexp.inputBytes *

by  ICOPY.SITE