...

使用GDB调试Go代码

This applies to the gc toolchain. Gccgo has native gdb support. Besides this overview you might want to consult the GDB manual.

本文档适用于 gc 工具链。Gccgo 拥有 gdb 的原生支持。 除此概览外,你或许还想查阅GDB 手册

GDB does not understand Go programs well. The stack management, threading, and runtime contain aspects that differ enough from the execution model GDB expects that they can confuse the debugger, even when the program is compiled with gccgo. As a consequence, although GDB can be useful in some situations, it is not a reliable debugger for Go programs, particularly heavily concurrent ones. Moreover, it is not a priority for the Go project to address these issues, which are difficult. In short, the instructions below should be taken only as a guide to how to use GDB when it works, not as a guarantee of success.

GDB 无法很好地理解 Go 程序。堆栈管理、线程和运行时包含的方面与 GDB 预期的执行模型非常不同, 即便程序是由 gccgo 编译的,它们也会把调试器搞乱。作为一个推论,即便 GDB 在某些情况下很有用, 但对于 Go 程序来说它并不是个可靠的调试器,对大并发来说更是如此。此外,它并不是 Go 项目需要优先解决的问题,这是很难的。简言之,就是以下指令仅在 GDB 可用时才能作为指导被采纳, 而并不保证成功。

In time, a more Go-centric debugging architecture may be required.

到时候,我们会需要一个更加以 Go 为中心的调试架构。

Introduction

引言

When you compile and link your Go programs with the gc toolchain on Linux, Mac OS X, FreeBSD or NetBSD, the resulting binaries contain DWARFv3 debugging information that recent versions (>7.1) of the GDB debugger can use to inspect a live process or a core dump.

当你使用 gc 工具链在 Linux、Mac OS X、FreeBSD 或 NetBSD 上编译或链接 Go 程序时, 所产生的二进制文件会包含 DWARFv3 调试信息,最近版本(>7.1)的 GDB 调试器可用于检查实时进程或核心转储。

Pass the '-w' flag to the linker to omit the debug information (for example, go build -ldflags "-w" prog.go).

可通过向连接器传递 '-w' 标记来省略调试信息 (例如,go build -ldflags "-w" prog.go)。

The code generated by the gc compiler includes inlining of function invocations and registerization of variables. These optimizations can sometimes make debugging with gdb harder. To disable them when debugging, pass the flags -gcflags "-N -l" to the go command used to build the code being debugged.

gc 编译器生成的代码包含内联函数调用和注册变量。这些优化有时会让 GDB 调试变得更难。要在调试时关闭它们,请向用于构建代码进行调试的 go 命令传递 -gcflags "-N -l" 标记。

Common Operations

一般操作

  • Show file and line number for code, set breakpoints and disassemble:
    (gdb) list
    (gdb) list line
    (gdb) list file.go:line
    (gdb) break line
    (gdb) break file.go:line
    (gdb) disas
  • Show backtraces and unwind stack frames:
    (gdb) bt
    (gdb) frame n
  • Show the name, type and location on the stack frame of local variables, arguments and return values:
    (gdb) info locals
    (gdb) info args
    (gdb) p variable
    (gdb) whatis variable
  • Show the name, type and location of global variables:
    (gdb) info variables regexp

Go Extensions

Go 扩展

A recent extension mechanism to GDB allows it to load extension scripts for a given binary. The tool chain uses this to extend GDB with a handful of commands to inspect internals of the runtime code (such as goroutines) and to pretty print the built-in map, slice and channel types.

GDB 最近的一个扩展机制允许它为给定的二进制文件加载扩展脚本。工具链以此用少数命令来扩展 GDB, 检查运行时代码的内部构件(例如 Go 程),并美观打印出内建映射、切片与信道类型。

  • Pretty printing a string, slice, map, channel or interface:
    (gdb) p var
  • A $len() and $cap() function for strings, slices and maps:
    (gdb) p $len(var)
  • A function to cast interfaces to their dynamic types:
    (gdb) p $dtype(var)
    (gdb) iface var

    Known issue: GDB can’t automatically find the dynamic type of an interface value if its long name differs from its short name (annoying when printing stacktraces, the pretty printer falls back to printing the short type name and a pointer).

  • Inspecting goroutines:
    (gdb) info goroutines
    (gdb) goroutine n cmd
    (gdb) help goroutine
    For example:
    (gdb) goroutine 12 bt

If you'd like to see how this works, or want to extend it, take a look at src/runtime/runtime-gdb.py in the Go source distribution. It depends on some special magic types (hash<T,U>) and variables (runtime.m and runtime.g) that the linker (src/cmd/ld/dwarf.c) ensures are described in the DWARF code.

如果你想看看它如何工作,或者想要扩展它,那就看看Go源码发行版中的 src/pkg/runtime/runtime-gdb.py吧。 连接器依赖于一些特别的魔法类型(hash<T,U>)与变量(runtime.mruntime.g)来确保以DWARF码描述(src/cmd/ld/dwarf.c)。

If you're interested in what the debugging information looks like, run 'objdump -W 6.out' and browse through the .debug_* sections.

如果你对调试信息看起来如何感兴趣,运行 'objdump -W 6.out' 并翻阅 .debug_* 的小节。

Known Issues

已知问题

  1. String pretty printing only triggers for type string, not for types derived from it.
  2. Type information is missing for the C parts of the runtime library.
  3. GDB does not understand Go’s name qualifications and treats "fmt.Print" as an unstructured literal with a "." that needs to be quoted. It objects even more strongly to method names of the form pkg.(*MyType).Meth.
  4. All global variables are lumped into package "main".
  1. 字符串的美观打印只会为类型string触发,而不会为从它衍生出的类型触发。
  2. 运行时库C部分的类型信息缺失。
  3. GDB不理解Go的名称限定且将 "fmt.Print" 作为需要引用的带 "." 的无结构字面来对待。该对象甚至比 pkg.(*MyType).Meth 形式的方法名更强。
  4. 所有全局变量都集中在包 "main" 中。

Tutorial

教程

In this tutorial we will inspect the binary of the regexp package's unit tests. To build the binary, change to $GOROOT/src/regexp and run go test -c. This should produce an executable file named regexp.test.

在此教程中,我们将检查 regexp 包单元测试的二进制文件。 要构建此二进制文件,切换到 $GOROOT/src/pkg/regexp 目录并运行 go test -c。这将产生一个名为 regexp.test 的可执行文件。

Getting Started

起步

Launch GDB, debugging regexp.test:

启动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)

The message "Loading Go Runtime support" means that GDB loaded the extension from $GOROOT/src/runtime/runtime-gdb.py.

信息 "Loading Go Runtime support" 意为 GDB 从 $GOROOT/src/pkg/runtime/runtime-gdb.py 加载扩展。

To help GDB find the Go runtime sources and the accompanying support script, pass your $GOROOT with the '-d' flag:

要帮助 GDB 找到 Go 运行时源及其伴随的支持脚本,请将你的 $GOROOT 通过 '-d' 标记传入:

$ gdb regexp.test -d $GOROOT

If for some reason GDB still can't find that directory or that script, you can load it by hand by telling gdb (assuming you have the go sources in ~/go/):

若因为一些原因 GDB 仍然不能找到该目录或该脚本,你可以通过手动告诉 gdb 来加载它 (假定你在 ~/go/ 中拥有 go 源码)。

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

Inspecting the source

检查源码

Use the "l" or "list" command to inspect source code.

使用 "l""list" 命令来检查源码。

(gdb) l

List a specific part of the source parametrizing "list" with a function name (it must be qualified with its package name).

通过函数名(它必须由其包名所限定)来列出由参数 "list" 确定的源的指定部分。

(gdb) l main.main

List a specific file and line number:

列出指定的文件与行号:

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

Naming

命名

Variable and function names must be qualified with the name of the packages they belong to. The Compile function from the regexp package is known to GDB as 'regexp.Compile'.

变量与函数名必须由它们所属的包名限定。来自 regexp 包的 Compile 函数被 GDB 视作 'regexp.Compile'

Methods must be qualified with the name of their receiver types. For example, the *Regexp type’s String method is known as 'regexp.(*Regexp).String'.

方法必须由其接收器类型的名字限定。例如,*Regexp 类型的 String 方法应视作 'regexp.(*Regexp).String'

Variables that shadow other variables are magically suffixed with a number in the debug info. Variables referenced by closures will appear as pointers magically prefixed with '&'.

尾随其他变量的变量会魔法般地后缀一些调试信息。通过闭包引用的变量将作为指针魔法般地前缀 '&'。

Setting breakpoints

设置断点

Set a breakpoint at the TestFind function:

TestFind 函数处设置断点:

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

Run the program:

运行此程序

(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) {

Execution has paused at the breakpoint. See which goroutines are running, and what they're doing:

执行会在断点处暂停。看看哪个 Go 程正在运行,以及它们在做什么:

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

the one marked with the * is the current goroutine.

* 标记的为当前 Go 程。

Inspecting the stack

检查栈

Look at the stack trace for where we’ve paused the program:

看一下我们所暂停程序的栈跟踪:

(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 ?? ()

The other goroutine, number 1, is stuck in runtime.gosched, blocked on a channel receive:

另一个Go程#1卡在 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 ?? ()

The stack frame shows we’re currently executing the regexp.TestFind function, as expected.

果然,栈帧显示当前我们正在执行 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

The command info locals lists all variables local to the function and their values, but is a bit dangerous to use, since it will also try to print uninitialized variables. Uninitialized slices may cause gdb to try to print arbitrary large arrays.

命令 info locals 列出所有该函数及其值的本地变量,但用它会有点儿危险,因为它还会试图打印未初始化变量。 未初始化切片可能导致gdb试图打印任意大的数组。

The function’s arguments:

函数实参:

(gdb) info args
t = 0xf840688b60

When printing the argument, notice that it’s a pointer to a Regexp value. Note that GDB has incorrectly put the * on the right-hand side of the type name and made up a 'struct' keyword, in traditional C style.

在打印该实参时,注意它是指向 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>

That struct hchan<*testing.T> is the runtime-internal representation of a channel. It is currently empty, or gdb would have pretty-printed it's contents.

其中 struct hchan<*testing.T> 为信道的运行时内部表示。它一般不是为空就是gdb会美观打印其内容。

Stepping forward:

下一步:

(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}

We can step into the Stringfunction call with "s":

我们可以步进至 String 函数调用 "s"

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

Get a stack trace to see where we are:

看看栈跟踪我们在哪儿:

(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
....

Look at the source code:

查看源码:

(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's pretty printing mechanism is triggered by regexp matches on type names. An example for slices:

GDB 的美观打印机制由类型名的正则匹配触发。一个切片的例子:

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

Since slices, arrays and strings are not C pointers, GDB can't interpret the subscripting operation for you, but you can look inside the runtime representation to do that (tab completion helps here):

由于切片、数组及字符串并非 C 指针,GDB 无法为你解释下标操作,不过你可以查看运行时中的表示来达到目的(tab 补全可以帮你):

(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

The extension functions $len and $cap work on strings, arrays and slices:

扩展函数 $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

信道与映射为“引用”类型,gdb 会将它作为指向类 C++ 类型 hash<int,string>* 的指针来显示。 解引用将触发美观打印。

Interfaces are represented in the runtime as a pointer to a type descriptor and a pointer to a value. The Go GDB runtime extension decodes this and automatically triggers pretty printing for the runtime type. The extension function $dtype decodes the dynamic type for you (examples are taken from a breakpoint at regexp.go line 293.)

接口在运行时会表示为一个指向类型描述符的指针与一个指向值的指针。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 *