golang调试工具
Table of Contents
1 调试工具gdb
gdb 是一个调试工具,可以调试很多种语言,当然其中就有golang。由于gdb不能显示go的数据类型,所以在go的runtime源码下有一个python代码文件,runtime-gdb.py,提供给gdb一个扩展。
1.1 gdb 调试go程序
先写一个很简单的小程序
package main import ( "fmt" ) func main() { b := make(map[string]string) b["write"] = "write" b["black"] = "black" fmt.Println(b) }
go build -gcflags "-N -l" test8.go
编译代码,-gcflags是给go编译器的参数,gc是go compile的意思。-N是不要优化代码,-l 是禁止内联代码。
> gdb test8 GNU gdb (Ubuntu 7.10-1ubuntu2) 7.10 Copyright (C) 2015 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from test8...done. warning: File "/home/47/go/src/runtime/runtime-gdb.py" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load". To enable execution of this file add add-auto-load-safe-path /home/47/go/src/runtime/runtime-gdb.py line to your configuration file "/home/47/.gdbinit". To completely disable this security protection add set auto-load safe-path / line to your configuration file "/home/47/.gdbinit". For more information about this security protection see the "Auto-loading safe path" section in the GDB manual. E.g., run from the shell: info "(gdb)Auto-loading safe path" (gdb) info files Symbols from "/home/47/code/golang/src/test3/test8". Local exec file: `/home/47/code/golang/src/test3/test8', file type elf64-x86-64. Entry point: 0x456750 0x0000000000401000 - 0x00000000004aa960 is .text 0x00000000004ab000 - 0x000000000052dc62 is .rodata 0x000000000052dc68 - 0x000000000052fd28 is .typelink 0x000000000052fd28 - 0x000000000052fd28 is .gosymtab 0x000000000052fd40 - 0x00000000005800d8 is .gopclntab 0x0000000000581000 - 0x0000000000582ce8 is .noptrdata 0x0000000000582d00 - 0x0000000000585250 is .data 0x0000000000585260 - 0x000000000059fab8 is .bss 0x000000000059fac0 - 0x00000000005a48c0 is .noptrbss 0x0000000000400fc8 - 0x0000000000401000 is .note.go.buildid (gdb) l main.main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 fmt.Println("begin===============") 9 b := make(map[string]string) 10 b["write"] = "write" 11 b["black"] = "black" (gdb) b 10 Breakpoint 1 at 0x401151: file /home/jyk-47/code/golang/src/test3/test8.go, line 10. (gdb) r Starting program: /home/jyk-47/code/golang/src/test3/test8 [New LWP 21285] [New LWP 21286] [New LWP 21287] begin=============== Breakpoint 1, main.main () at /home/jyk-47/code/golang/src/test3/test8.go:10 10 b["write"] = "write" (gdb) l 5 ) 6 7 func main() { 8 fmt.Println("begin===============") 9 b := make(map[string]string) 10 b["write"] = "write" 11 b["black"] = "black" 12 fmt.Println(b) 13 } (gdb) p b $1 = (map[string]string) 0xc8200121e0 (gdb) p *b $2 = {count = 0, flags = 0 '\000', B = 0 '\000', hash0 = 1888351057, buckets = 0x0, oldbuckets = 0x0, nevacuate = 0, overflow = 0x0} (gdb) n 11 b["black"] = "black" (gdb) bt #0 main.main () at /home/jyk-47/code/golang/src/test3/test8.go:11 (gdb) bp
可以看到上面已经加载了golang的扩展脚本runtime-gdb.py, l main.main 是查看main函数的源码,b 10 在第10行设置断点。r 运行程序。p b 是打印变量。n 是单步。gdb不能打印go中map具体的内容,只能够打印map底层的结构,用起来有点不爽。
2 golang 调试工具delve
由于gdb对golang支持比较差,只能支持一些简单的功能,比如断点,单步等,但对goroutine, 内置对象的打印等还是不怎么好。所以go有了自己的debug工具delve。安装等可以参考官方文档:https://github.com/derekparker/delve.
Delve is a source level debugger for Go programs. Delve enables you to interact with your program by controlling the execution of the process, evaluating variables, and providing information of thread / goroutine state, CPU register state and more. The goal of this tool is to provide a simple yet powerful interface for debugging Go programs. Usage: dlv [command] Available Commands: attach Attach to running process and begin debugging. connect Connect to a headless debug server. debug Compile and begin debugging main package in current directory, or the package specified. exec Execute a precompiled binary, and begin a debug session. run Deprecated command. Use 'debug' instead. test Compile test binary and begin debugging program. trace Compile and begin tracing program. version Prints version. Flags: --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate. --api-version=1: Selects API version when headless. --build-flags="": Build flags, to be passed to the compiler. --headless[=false]: Run debug server only, in headless mode. -h, --help[=false]: help for dlv --init="": Init file, executed by the terminal client. -l, --listen="localhost:0": Debugging server listen address. --log[=false]: Enable debugging server logging. Use "dlv [command] --help" for more information about a command.
delve 的帮助文档说的非常清楚。我们还是用上面的那个例子来说明.
> dlv exec ./test8 Type 'help' for list of commands. //查看主函数的代码 (dlv) ls main.main 2: 3: import ( 4: "fmt" 5: ) 6: 7: func main() { 8: fmt.Println("begin===============") 9: b := make(map[string]string) 10: b["write"] = "write" 11: b["black"] = "black" 12: fmt.Println(b) //设置断点 (dlv) b main.main Breakpoint 1 set at 0x40101b for main.main() ./test8.go:7 //运行到断点处 (dlv) c > main.main() ./test8.go:7 (hits goroutine(1):1 total:1) (PC: 0x40101b) 2: 3: import ( 4: "fmt" 5: ) 6: => 7: func main() { 8: fmt.Println("begin===============") 9: b := make(map[string]string) 10: b["write"] = "write" 11: b["black"] = "black" 12: fmt.Println(b) //单步执行 (dlv) n > main.main() ./test8.go:8 (PC: 0x401022) 3: import ( 4: "fmt" 5: ) 6: 7: func main() { => 8: fmt.Println("begin===============") 9: b := make(map[string]string) 10: b["write"] = "write" 11: b["black"] = "black" 12: fmt.Println(b) 13: } (dlv) begin=============== > main.main() ./test8.go:9 (PC: 0x40111c) 4: "fmt" 5: ) 6: 7: func main() { 8: fmt.Println("begin===============") => 9: b := make(map[string]string) 10: b["write"] = "write" 11: b["black"] = "black" 12: fmt.Println(b) 13: } (dlv) n > main.main() ./test8.go:10 (PC: 0x401151) 5: ) 6: 7: func main() { 8: fmt.Println("begin===============") 9: b := make(map[string]string) => 10: b["write"] = "write" 11: b["black"] = "black" 12: fmt.Println(b) 13: } (dlv) n > main.main() ./test8.go:11 (PC: 0x4011bb) 6: 7: func main() { 8: fmt.Println("begin===============") 9: b := make(map[string]string) 10: b["write"] = "write" => 11: b["black"] = "black" 12: fmt.Println(b) 13: } //打印b的值 (dlv) p b map[string]string [ "write": "write", ] (dlv)
dlv还提供一些命令,总的来说对于简单的调试足够了。用起来还是比较方便, 不过如果调试go汇编代码,就有点不足,-0- 单步都不能使用。