概述
定位Go程序的错误,通常有两种方式:
- 打印日志
- 调试
Go是编译型语言,且IDE对调试的支持不太好,绝大多数Go的初学者调试Go程序都是通过log.Printf
等打印日志方式定位问题。通常过程如下:
- 程序
panic
或者报错 - 修改Go程序,添加打印调试日志代码
- 编译Go程序
- 重复错误出现时的操作,查看日志
- 如果定位问题原因,修复程序错误,删除打印
debug
日志代码,返回第3步的操作 - 如果未定位问题原因,返回到第2步的操作
- 如果定位问题原因,修复程序错误,删除打印
如果程序比较复杂,需要反复增加日志输出才能找到问题原因。熟练的使用调试器能够提高我们面对这样问题的灵活性。本文重点总结介绍调试相关知识,具体调试操作网上相关资料已经很全面(见最后一章参考),不作为重点。
使用GDB调试Go程序
简介
GDB不能很好的理解Go程序。Go程序和GDB的stack management、threading、runtime模型差异很大,并可能导致调试器输出不正确的结果。因此,虽然GDB在某些场景下有用,比如调试Cgo代码、调试runtime,但是对于Go程序来说,尤其是高并发程序,GDB不是一个可靠的调试器。而且,对于Go语言项目本身来说,解决这些的问题很困难,也不是一个高优先级的事情。
当你在Linux、macOS、FreeBSD、NetBSD上使用gc工具链编译和连接Go程序的时候,产生的二进制包含DWARFv4调试信息,最近版本的GDB调试器可以利用这些信息观察一个运行的进程或者core dump。
可以通过-w
标记告诉连接器去掉这些调试信息,比如:
1 | go build -ldflags "-w" . |
gc编译器生成的程序包含函数内联和变量注册。这些优化可能会让gdb调试更加困难。如果你需要禁用这些优化,使用下面的参数构建程序:
1 | go build -gcflags "all=-N -l" . |
如果你想要使用gdb检查一个core dump,你可以在程序崩溃的时候触发一个dump。在支持dump的OS上,使用GOTRACEBACK=crash
环境变量(参考runtime package documentation)。
Go 1.11版本中,由于编译器会产生更多更准确的调试信息,为了减少二进制的大小,DWARF调试信息编译时候会默认被压缩。这对于大多数ELF工具来说这是透明的,也得到Delve支持。但是macOS和Windows上一些工具不支持。如果要禁用DWARF压缩,可以在编译的时候传入参数-ldflags "-compressdwarf=false"
。
Go 1.11添加了一个实验性的功能,允许在调试器中调用函数。目前这个特性仅得到Delve(version 1.1.0及以上)的支持。
常用命令和教程
可以参考下面几篇文章,这里不做赘述:
使用LLDB调试Go程序
简介
Mac下如果你安装XCode,应该会自动安装了LLDB,LLDB是XCode的默认调试器。LLDB的安装方法可以参考这里。
GDB的命令格式非常自由,和GDB的命令不同,LLDB命令格式非常结构化(“严格”的婉转说法)。LLDB的命令格式如下:
1 | <command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]] |
解释一下:
<command>
(命令)和<subcommand>
(子命令):LLDB调试命令的名称。命令和子命令按层级结构来排列:一个命令对象为跟随其的子命令对象创建一个上下文,子命令又为其子命令创建一个上下文,依此类推。<action>
:执行命令的操作<options>
:命令选项。需要注意的是,如果aguments的第一个字母是”-“,<options>
和<arguments>
中间必须以”–”分隔开。所以如果你想启动一个程序,并给这个程序传入-program_arg value
参数,可以输入(lldb) process launch --stop-at-entry -- -program_arg value
<arguement>
:命令的参数[]
:表示命令是可选的,可以有也可以没有
LLDB也减少了gdb中一些命令的特殊写法,让用户更加容易理解命令的意图。可以阅读LLDB文档中下面一段文字了解细节:
We also tried to reduce the number of special purpose argument parsers, which sometimes forces the user to be a little more explicit about stating their intentions.
……
LLDB的命令同样给很多命令提供了缩写形式,可以通过(lldb) help
查看所有的缩写命令。
gdb和LLDB的命令之间的差别可以访问这里查看。
常用命令
使用LLDB需要熟悉的常用命令如下:
帮助
(lldb) help help
Show a list of all debugger commands, or give details about a specific command.Syntax: help [
]
使用LLDB加载一个程序
1 | $lldb /binary-path |
设置断点(breakpoints)
常见的设置断点的命令如下:
1 | (lldb) breakpoint set --file source-file.go --line 11 |
breakpoint
命令会创建一个逻辑的断点,一个逻辑的断点可以对应一个或者多个位置location
。比如,通过selector
设置的断点对应所有实现了selector
的方法。
breakpoint
命令:
1 | (lldb) help breakpoint |
设置观察点(Watchpoints)
watchpoint
命令:
1 | (lldb) help watchpoint |
运行程序或者附着程序
process
命令:
1 | (lldb) help process |
控制程序执行或者检查Thread状态
thread
命令
1 | (lldb) help thread |
检查堆栈结构(Stack Frame)状态
frame
命令
1 | (lldb) help frame |
expression
命令
1 | (lldb) help expression |
操作教程
可以参考下面几篇文章:
- Debugging Go Code with LLDB](http://ribrdb.github.io/lldb/): (中文翻译)
- 熟练使用 LLDB,让你调试事半功倍
- LLDB Tutorial
使用Delve调试Go程序
可以参考下面几篇文章:
不要使用调试器
对于调试器,一众计算机大牛都给出了明确而且强烈的建议:不要使用调试器。
- Linus Torvalds, the creator of Linux, does not use a debugger.
- Robert C. Martin, one of the inventors of agile programming, thinks that debuggers are a wasteful timesink.
- John Graham-Cumming hates debuggers.
- Brian W. Kernighan and Rob Pike wrote that stepping through a program less productive than thinking harder and adding output statements and self-checking code at critical places. Kernighan once wrote that the most effective debugging tool is still careful thought, coupled with judiciously placed print statements.
- The author of Python, Guido van Rossum has been quoted as saying that uses print statements for 90% of his debugging.
调试技术是一众纯手工的技术,诞生于计算机程序的规模还不是很大的时期。在当今软件规模不断扩展的情况下,调试无法解决软件质量问题。深入的思考、合理的架构、优美的代码、充分的单元测试才是提高软件质量的正确方向。调试应该仅作为调查问题最后一种办法。
参考
- Debugging Go Program
- Debugging Go Code with GDB
- Debugging Go Code with LLDB: (中文翻译)
- Debugging Go programs with Delve
- Post-mortem debugging
- Debugging Concurrent Programs
- Other
- Diagnostics: Profiling, Tracing, Debugging, Rutime statistics and events
- Build Go Program
- ELF format and Tools
- Methodology
- Debugging in IDE
- Tools
- GDB
- LLDB
- Delve: Delve is a debugger for the Go programming language
- Spew: Implements a deep pretty printer for Go data structures to aid in debugging
- panicparse: Crash your app in style (Golang)