设为首页 加入收藏

TOP

使用 GDB 工具调试 Go
2015-08-31 21:24:04 来源: 作者: 【 】 浏览:34
Tags:使用 GDB 工具 调试

排除应用程序故障是比较复杂的,特别是处理像 Go 这样的高并发语言。它更容易在具体位置使用 print 打印语句来确定程序状态,但是这个方法很难根据条件发展去动态响应你的代码


调试器提供了一个强大得令人难以置信的故障排除机制。添加排除故障的代码可以巧妙地影响到应用程序该如何运行。调试器可以给正在迷茫的你更精确的看法。


已经有许多 Go 的调试器存在了,其中一些调试器的不好之处是通过在编译时注入代码来提供一个交互终端。gdb 调试器则允许你调试已经编译好的二进制文件,只要他们已经与 debug 信息连接,并不用修改源代码。这是个相当不错的特性,因此你可以从你的部署环境中取一个产品然后灵活地调试它。你可以从Golang 官方文档中阅读更多关于 gdb 的信息,那么这篇指南将简单讲解使用 gdb 调试器来调试 Go 应用程序的基本用法。


这儿会宣布一些 gdb 的最新更新,最特别的是替换 -> 操作为 . 符号来访问对象属性。记住这儿可能在gdb 和 Go 版本中有细微改变。本篇指南基于 gdb 7.7.1和go 1.5beta2。


为了实验 gdb 我使用了一个测试程序,完整的源代码可以在gdb_sandbox_on_Github上查看。让我们从一个非常简单的程序开始吧:


我们可以运行这段代码并看到它输出内容的和我们想象的一样:


我们来调试这个程序吧。首先,使用 go build 编译成二进制文件,接着使用这个二进制文件的路径做为参数运行 gdb。根据你的设定,你也可以使用 source 命令来获取 Go 运行时(Go runtime)的支持。现在我们已经在 gdb 的命令行中了,我们可以在运行我们的二进制文件前为它设置断点。


第一关,我们在 for 循环里面设置一个断点(b)来查看执行每次循环时我们的代码会各有什么状态。我们可以使用print(p)命令来检查当前内容的一个变量,还有 list(l)和 backtrace(bt)命令查看当前步骤周围的代码。程序运行时可以使用 next(n)执行下一步或者使用 breakpoint(c)执行到下一个断点。


我们的断点可以设置在关联文件的行号中、GOPATH里的文件的行号或一个包里的函数。如下也是一个有效的断点:


Structs


我们可以用稍微复杂一点的代码来实例演示如何调试。我们将使用f函数生成一个简单的pair,x和y,当x相等时y=f(x),否则=x。


也可以在循环中改变代码来访问这些新函数。


因为我们需要调试的是变量 y。我们可以在y被设置的地方放置断点然后单步执行。可以使用 info args 查看函数的参数,在 bt 之前可以返回当前回溯。


因为我们在变量 y 是在函数 f 中被设定的这样一个条件下,我们可以跳到这个函数的上下文并检查堆区的代码。应用运行时我们可以在一个更高的层次上设置断点并检查其状态。


如果我们在这个断点处继续住下走我们将越过在这个函数中的断点1,而且将立即触发在 HandleNumer 函数中的断点,因为函数 f 只是对变量 i 每隔一次才执行。我们可以通过暂时使断点 2不工作来避免这种情况的发生。


我们也可以分别使用 clear 和 delete breakpoint NUMBER 来清除和删除断点。动态产生和系住断点,我们可以有效地在应用流中来回移动。


Slices and Pointers


上例程序太简单了,只用到了整数型和字符串,所以我们将写一个稍微复杂一点的。首先添加一个slice(切片类型)的指针到 main 函数,并保存生成的 pair,我们后面将用到它。


现在我们来检查生成出来的 slice 或 pairs,首先我们用转换成数组来看一下这个 slice。因为 handleNumber 返回的是一个 *pair 类型,我们需要引用这个指针来访问 struct(结构)的属性。


你会发现这里 gdb 并不确定 pairs 是一个 slice 类型,我们不能直接访问它的属性,为了访问它的成员我们需要使用 pairs.array 来转换成数组,然后我们就可以检查 slice 的 length(长度)和 capacity(容量):


这时我们可以让它循环几次,并透过这个 slice 不用的成员方法监听增加的 xy 的值,要注意的是,这里的 struct 属性可以通过指针访问,所以 p pairs.array[2].y 一样可行。


现在我们已经可以访问 struct 和 slice 了,下面再来更加复杂一点的程序吧。让我们添加一些goroutines 到 mian 函数,并行处理每一个数字,返回的结果存入信道(chan)中:


如果我等待 WaitGroup 执行完毕再检查 pairs slice 的结果,我们可以预期到内容是完全相同的,虽然它的排序可能有些出入。gdb 真正的威力来自于它可以在 goroutines 正在运行时进行检查:


你会发现我们在 goroutine 要执行的代码段中放置了一个断点,从这里我们可以检查到局部变量,和进程中的其它 goroutines:


在这里我们做的第一件事就是列出所有正在运行的 goroutine,并确定我们正在处理的那一个。然后我们可以看到一些回溯,并发送任何调试命令到 goroutine。这个回溯和列表清单并不太准确,如何让回溯更准确,goroutine 上的 info args 显示了我们的局部变量,以及主函数中的可用变量,goroutine 函数之外的使用前缀&


结论


当调试应用时,gdb 的强大令人难以置信。但它仍然是一个相当新的事物,并不是所有的地方工作地都很完美。使用最新的稳定版 gdb,go 1.5 beta2,有不少地方有突破:


根据 go 博客上的文章, go 的 interfaces 应该已经支持了,这允许在 gdb 中动态的投影其基类型。这应该算一个突破。


目前没有办法转换 interface{} 为它的类型。


在其他 goroutine 中列出周边代码会导致一些行数的漂移,最终导致 gdb 认为当前的行数超出文件范围并抛出一个错误:


Goroutine 调试还不稳定


处理 goroutines 往往不稳定;我遇到过执行简单命令产生错误的情况。现阶段你应该做好处理类似问题的准备。


gdb 支持 Go 的配置非常麻烦


运行 gdb 支持 Go 调试的配置非常麻烦,获取正确的路径结合与构建 flags,还有 gdb 自动加载功能好像都不能正常的工作。首先,通过一个 gdb 初始化文件加载 Go 运行时支持就会产生初始化错误。这就需要手动通过一个源命令去加载,调试 shell 需要像指南里面描述的那样去进行初始化。


所以什么情况下使用 gdb 更有用?使用 print 语言和调试代码是更有针对性的方法。


英文原文:Using the gdb debugger with Go


】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇再说apue.h头文件(UNIX环境高级.. 下一篇Android 关机(reboot)流程

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: