对应用程序进行故障排除很复杂,尤其是在处理像Go这样的高并发语言时。在特定位置使用print语句来确定程序的状态更容易,但这种方法很难随着条件的变化动态响应您的代码。调试器提供了一个非常强大的故障排除机制。添加故障排除代码会微妙地影响应用程序的行为方式。调试器可以让您更准确地了解您丢失的地方。已经有很多Go的调试器,其中一些具有通过在编译时注入代码来提供交互式终端的缺点。gdb调试器允许您调试已编译的二进制文件,只要它们与调试信息相关联,而无需修改源代码。这是一个很好的特性,因此您可以从您的部署环境中获取一个产品并灵活地对其进行调试。您可以从Golang官方文档中阅读更多关于gdb的信息,然后本指南将简要介绍使用gdb调试器调试Go应用程序的基本用法。此处将宣布对gdb的一些新更新,最显着的是将->运算符替换为.用于访问对象属性的符号。请记住,这可能在gdb和Go版本之间略有不同。本指南基于gdb7.7.1和go1.5beta2。启动gdb调试为了试验gdb我使用了一个测试程序,完整的源代码可以在gdb_sandbox_on_Github查看。让我们从一个非常简单的程序开始:packagemainimport("fmt")funcmain(){fori:=0;i<5;i++{fmt.Println("looping")}fmt.Println("Done")}我们可以运行这段代码并看到它输出了我们所期望的:$gorunmain.goloopingloopingloopingloopingloopingloopingDone让我们调试这个程序。首先,使用gobuild编译成二进制文件,然后以二进制文件的路径作为参数运行gdb。根据您的设置,您还可以使用source命令获得Go运行时支持。现在我们在gdb命令行,我们可以在运行它之前在我们的二进制文件上设置断点。$gobuild-gcflags"-N-l"-ogdb_sandboxmain.go$lsgdb_sandboxmain.goREADME.md$gdbgdb_sandbox....(gdb)source/usr/local/src/go/src/runtime/runtime-gdb.pyLoadingGoRuntimesupport.***关闭,我们在for循环内部设置一个断点(b),以查看每次执行循环时我们的代码处于什么状态。我们可以使用print(p)命令检查变量的当前内容,使用list(l)和backtrace(bt)命令查看当前步骤周围的代码。程序运行时,可以使用next(n)执行下一步,也可以使用breakpoint(c)执行到下一个断点。(gdb)bmain.go:9Breakpoint1at0x400d35:file/home/bfosberry/workspace/gdb_sandbox/main.go,line9.(gdb)runStartingprogram:/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/gdb_sandboxBreakpoint1,main.main()在/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:99fmt.Println("looping")(gdb)l4"fmt"5)67funcmain(){8fori:=0;i<5;i++{9fmt.Println("looping")10}`11fmt.Println("Done")12}(gdb)pi$1=0(gdb)nloopingBreakpoint1,main.main()at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:99fmt.Println("循环")(gdb)pi$2=1(gdb)bt#0main.main()at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:9我们的断点可以设置在关联文件中的行号、GOPATH文件中的行号或包中的函数上。下面也是一个有效的断点:(gdb)bgithub.com/bfosberry/gdb_sandbox/main.go:9(gdb)b'main.main'Structs我们可以使用稍微复杂一点的代码来演示如何调试。我们将使用f函数生成一个简单的对,x和y,其中当x相等时y=f(x),否则=x。typepairstruct{xintyint}funchandleNumber(iint)*pair{val:=iifi%2==0{val=f(i)}return&pair{x:i,y:val,}}funcf(intx)int{returnx*x+x}也可以更改循环中的代码来访问这些新函数。p:=handleNumber(i)fmt.Printf("%+v/n",p)fmt.Println("looping")因为我们需要调试变量y。我们可以在设置y的地方放置一个断点并单步执行。可以使用infoargs查看函数的参数,在bt之前可以返回当前的traceback。(gdb)b'main.f'(gdb)runStartingprogram:/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/gdb_sandboxBreakpoint1,main.f(x=0,~anon1=833492132160)at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:3333returnx*x+x(gdb)infoargsx=0(gdb)continueBreakpoint1,main.f(x=0,~anon1=833492132160)在/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:3333returnx*x+x(gdb)infoargsx=2(gdb)bt#0main.f(x=2,~anon1=1)at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:33#10x0000000000400f0einmain.handleNumber(i=2,~anon1=0x1)at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:24#20x0000000000400c47inmain.main()at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:14因为我们在变量y在函数f中设置了这样的条件后,我们可以跳转到该函数的上下文并检查堆中的代码。我们可以在更高级别设置断点并在应用程序运行时检查它们的状态。(gdb)bmain.go:26Breakpoint2at0x400f22:file/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go,line26.(gdb)continueContinuing.Breakpoint2,main.handleNumber(i=2,~anon1=0x1)at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:2828y:val,(gdb)l23ifi%2==0{24val=f(i)25}26return&pair{27x:i,28y:val,29}30}3132funcf(xint)int{(gdb)pval$1=6(gdb)pi$2=2如果我们继续住在这个断点,我们将跳过这个函数断点1in,并且HandleNumer函数中的断点会立即被触发,因为函数f只是对变量i每隔一段时间执行一次。我们可以通过暂时禁用断点2来避免这种情况。(gdb)disablebreakpoint2(gdb)continueContinuing.&{x:2y:6}looping&{x:3y:3}looping[NewLWP15200][SwitchingtoLWP15200]Breakpoint1,main.f(x=4、~anon1=1)at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:3333returnx*x+x(gdb)我们也可以使用clear和deletebreakpointNUMBER来清除和删除分别删除断点。通过动态生成和绑定断点,我们可以在应用程序流中高效地来回移动。上面的Slices和Pointers的例子程序太简单了,只用了整数和字符串,所以我们写一个稍微复杂一点的。先在main函数中添加一个slice(slice类型)指针,并将生成的pair保存起来,后面会用到。varpairs[]*pairfori:=0;i<10;i++{p:=handleNumber(i)fmt.Printf("%+v/n",p)pairs=append(pairs,p)fmt.Println("循环")}现在让我们检查生成的切片或对。首先,让我们通过将切片转换为数组来查看切片。因为handleNumber返回一个*pair类型,所以我们需要引用这个指针来访问struct(结构)的属性。(gdb)bmain.go:18Breakpoint1at0x400e14:file/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go,line18.(gdb)runStartingprogram:/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/gdb_sandbox&{x:0y:0}Breakpoint1,main.main()at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:1818fmt.Println("循环”)(gdb)ppairs$1=[]*main.pair={0xc82000a3a0}(gdb)ppairs[0]Structurehasnocomponentnamedoperator[].(gdb)ppairs.array$2=(structmain.pair**)0xc820030028(gdb)ppairs.array[0]$3=(structmain.pair*)0xc82000a3a0(gdb)p*pairs.array[0]$4={x=0,y=0}(gdb)p(*pairs.array[0])。x$5=0(gdb)p(*pairs.array[0]).y$6=0(gdb)continueContinuing.looping&{x:1y:1}Breakpoint1,main.main()at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:1818fmt.Println("looping")(gdb)p(pairs.array[1][5]).y$7=1(gdb)continueContinuing.looping&{x:2y:6}Breakpoint1,main.main()at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:1818fmt.Println("looping")(gdb)p(pairs.array[2][6]).y$8=6(gdb)你会发现gdb不确定pairs是slice类型,我们不能直接访问它的属性,为了访问它的成员我们需要使用pairs.array转换成数组,然后我们可以检查切片的长度(length)和容量(capacity):(gdb)p$len(pairs)$12=3(gdb)p$cap(pairs)$13=4这时候我们可以让它循环几次,通过这片。需要注意的是这里的Struct属性是可以通过指针来访问的,所以ppairs.array[2].y也是可行的。Goroutines现在我们可以访问struct和slice,让我们来看一个更复杂的程序。让我们在main函数中添加一些goroutines,并行处理每个数字,并将返回的结果存储在通道(chan)中:pairs:=[]*pair{}pairChan:=make(chan*pair)wg:=sync。WaitGroup{}fori:=0;i<10;i++{wg.Add(1)gofunc(valint){p:=handleNumber(val)fmt.Printf("%+v/n",p)pairChan<-pwg.Done()}(i)}gofunc(){forp:=rangepairChan{pairs=append(pairs,p)}}()wg.Wait()close(pairChan)如果我等待WaitGroup完成然后检查结果pairsslice的内容,我们可以预期内容是完全一样的,尽管它可能排序有点不同。gdb的真正强大之处在于它能够在goroutine运行时进行检查:(gdb)bmain.go:43Breakpoint1at0x400f7f:file/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go,line43。(gdb)runStartingprogram:/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/gdb_sandboxBreakpoint1,main.handleNumber(i=0,~r1=0x0)at/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:4343y:val,(gdb)l38ifi%2==0{39val=f(i)40}41return&pair{42x:i,43y:val,44}45}4647funcf(xint)int{(gdb)infoargsi=0~r1=0x0(gdb)pval$1=0你会发现我们在goroutine要执行的代码段下了一个断点,从这里我们可以查看局部变量和进程的其他goroutines:(gdb)infogoroutines1waitingruntime.gopark2waitingruntime.gopark3waitingruntime.gopark4waitingruntime.gopark*5runningmain.main.func16runnablemain.main.func17runnablemain.main.func18runnablemain.main.func19runnablemain.main.func1*10runningmain.main.func2.runnablemain1.11runfuncable1.主要.func113runnablemain.main.func114runnablemain.main.func115waitingruntime.gopark(gdb)goroutine11bt#0main.main.func1(val=6,pairChan=0xc82001a180,&wg=0xc82000a3a0)在/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:19#10x0000000000454991inruntime.goexit()at/usr/local/go/src/runtime/asm_amd64.s:1696#20x00000000000000000006in??()#30x0000000000c82001a180in??()#40x00000000000000c82000a3a3a0in??()=0xc82001a180&wg=0xc82000a3a0(gdb)goroutine11pval$2=6我们在这里做的第一件事是列出所有正在运行的goroutines并确定我们正在处理的goroutines然后我们可以看到一些回溯并将任何调试命令发送到goroutines。这个回溯和list列表不是很准确,如何让回溯更准确,goroutine上的infoargs显示我们的局部变量,以及main函数中可用的变量,在goroutine函数外使用前缀&。结论在调试应用程序方面,gdb非常强大。但这仍然是一个相当新的事物,并非所有地方都能完美运行。使用最新的稳定版gdb,go1.5beta2,有很多突破:Interfaces根据goblog上的文章,go的interfaces应该已经支持了,可以在gdb中动态投影其基类型。这应该算是一个突破。Interface{}类型目前无法将interface{}转换为其类型。列出goroutines的区别在其他goroutines中列出周边代码会导致一些行数漂移,最终导致gdb认为当前行数超出文件范围而抛出错误:(gdb)infogoroutines1waitingruntime.gopark2waitingruntime.gopark3waitingruntime.gopark4waitingruntime。gopark*5runnablemain.main.func16runnablemain.main.func17runnablemain.main.func18runnablemain.main.func19runnablemain.main.func1*10runnablemain.main.func111runnablemain.main.func112runnablemain.main.func113runnablemain.main.func114runnablerung1.main.farktgoroutine11.func1(val=6,pairChan=0xc82001a180,&wg=0xc82000a3a0)在/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:19#10x0000000000454991inruntime.goexit在/usr/local/go/src/runtime/asm_amd64.s:1696#20x00000000000000000006in?????:(:)#30x000000c82001a180in???取入?gdb.error'>Linenumber50outofrange;/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.gohas49lines.:ErroroccurredinPythoncommand:Linenumber50outofrange;/home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.gohas49lines.Goroutine调试还不稳定我遇到过简单的执行命令产生错误的情况你应该准备好在这个阶段处理类似的问题。配置gdb支持Go非常麻烦。运行gdb支持Go调试配置很麻烦。似乎正确的路径组合和构建标志以及gdb自动加载功能无法正常工作。首先,通过gdb初始化文件加载Go运行时支持会产生初始化错误。这需要通过source命令手动加载,并且调试shell需要按照指南中的描述进行初始化。什么时候应该使用调试器?那么gdb什么时候更有用呢?使用打印语言和调试代码是一种更有针对性的方法。当调试一个问题时,不宜修改代码,但不知道源头,动态断点可能更有效当涉及到很多goroutines时,最好暂停一下,然后检查程序状态“Debugging#golangwithgdb”–来自@codeship–来自推文
