Golang程序调试工具介绍(gdb vs dlv)

时间:2022-07-23
本文章向大家介绍Golang程序调试工具介绍(gdb vs dlv),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

通过log库输出日志,我们可以对程序进行异常分析和问题追踪。但有时候,我也希望能有更直接的程序跟踪及定位工具能够帮助我们更方便快捷的追踪、定位问题,最直观的感觉还是使用调试器。Linux平台下,原生的C/C++程序,我们往往使用gdb进行程序调试,切换到Golang,我们同样还是可以使用gdb进行调试。同时我们还可以使用golang实现的调试器dlv进行调试。以下内容是我对gdb以及dlv使用及对比总结

安装

go get github.com/go-delve/delve/cmd/dlv

准备工作

为展示整个调试过程,准备了一个演示项目GoDbg,整个目录结构如下所示

[lday@alex GoDbg]$ tree
.
├── main.go
└── mylib
    └── dbgTest.go

其中,main.go为主函数入口,而dbgTest.go启动多个goroutine,用于演示调试操作。 main.go:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

package main import ( "GoWorks/GoDbg/mylib" "fmt" "os" ) func main() { fmt.Println("Golang dbg test...") var argc = len(os.Args) var argv = append([]string{}, os.Args...) fmt.Printf("argc:%dn", argc) fmt.Printf("argv:%vn", argv) var var1 = 1 var var2 = "golang dbg test" var var3 = []int{1, 2, 3} var var4 mylib.MyStruct var4.A = 1 var4.B = "golang dbg my struct field B" var4.C = map[int]string{1: "value1", 2: "value2", 3: "value3"} var4.D = []string{"D1", "D2", "D3"} mylib.DBGTestRun(var1, var2, var3, var4) fmt.Println("Golang dbg test over") }

dbgTest.go:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

package mylib import ( "fmt" "sync" "time" ) type MyStruct struct { A int B string C map[int]string D []string } func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) { fmt.Println("DBGTestRun Begin!n") waiter := &sync.WaitGroup{} waiter.Add(1) go RunFunc1(var1, waiter) waiter.Add(1) go RunFunc2(var2, waiter) waiter.Add(1) go RunFunc3(&var3, waiter) waiter.Add(1) go RunFunc4(&var4, waiter) waiter.Wait() fmt.Println("DBGTestRun Finished!n") } func RunFunc1(variable int, waiter *sync.WaitGroup) { fmt.Printf("var1:%vn", variable) for { if variable != 123456 { continue } else { break } } time.Sleep(10 * time.Second) waiter.Done() } func RunFunc2(variable string, waiter *sync.WaitGroup) { fmt.Printf("var2:%vn", variable) time.Sleep(10 * time.Second) waiter.Done() } func RunFunc3(pVariable *[]int, waiter *sync.WaitGroup) { fmt.Printf("*pVar3:%vn", *pVariable) time.Sleep(10 * time.Second) waiter.Done() } func RunFunc4(pVariable *MyStruct, waiter *sync.WaitGroup) { fmt.Printf("*pVar4:%vn", *pVariable) time.Sleep(10 * time.Second) waiter.Done() }

在对程序进行调试前,我们需要对目标程序进行调试版本程序的编译。C/C++程序,我们会通过gcc/g++进行编译、链接时加入-g3等参数,使得程序编译时带入调试信息,进而让调试器能够最终并解释相关的程序代码。同样的,在我们对Golang程序进行调试时,我们也需要加入相应的编译、链接选项:-gcflags="-N -l",生成程序调试信息(-N -l用于关闭编译器的内联优化)。编译GoDbg项目指令:go build -gcflags="-N -l" GoWorks/GoDbg

gdb调试程序

因为gdb对Golang的支持也是在不断完善中,为使用gdb调试Golang程序,建议将gdb升级到相对较新版本,目前,我使用的版本是gdb7.10。 大多数命令在使用gdb调试C/C++时都会用到,详细说明可参考:Debugging Go Code with GDB,具体操作如下:

  1. 启动调试程序(gdb) [lday@alex GoDbg]$ gdb ./GoDbg
  2. 在main函数上设置断点(b) (gdb) b main.main Breakpoint 1 at 0x401000: file /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go, line 9.
  3. 带参数启动程序(r) (gdb) r arg1 arg2 Starting program: /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/GoDbg arg1 arg2 [New LWP 8412] [New LWP 8413] [New LWP 8414] [New LWP 8415] Breakpoint 1, main.main () at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9 9 func main() {
  4. 在文件dbgTest.go上通过行号设置断点(b) (gdb) b dbgTest.go:16 Breakpoint 3 at 0x457960: file /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go, line 16.
  5. 查看断点设置情况(info b) (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000401000 in main.main at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9 breakpoint already hit 1 time 2 breakpoint keep y 0x0000000000401000 in main.main at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9 breakpoint already hit 1 time 3 breakpoint keep y 0x0000000000457960 in GoWorks/GoDbg/mylib.DBGTestRun at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:16
  6. 禁用断点(dis n) (gdb) dis 1 (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep n 0x0000000000401000 in main.main at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9 breakpoint already hit 1 time 2 breakpoint keep y 0x0000000000401000 in main.main at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9 breakpoint already hit 1 time 3 breakpoint keep y 0x0000000000457960 in GoWorks/GoDbg/mylib.DBGTestRun at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:16
  7. 删除断点(del n) (gdb) del 1 (gdb) info b Num Type Disp Enb Address What 2 breakpoint keep y 0x0000000000401000 in main.main at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9 breakpoint already hit 1 time 3 breakpoint keep y 0x0000000000457960 in GoWorks/GoDbg/mylib.DBGTestRun at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:16
  8. 断点后继续执行(c) (gdb) c Continuing. Golang dbg test... argc:3 argv:[/home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/GoDbg arg1 arg2] Breakpoint 3, GoWorks/GoDbg/mylib.DBGTestRun (var1=1, var2="golang dbg test") at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:16 16 func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) { (gdb)
  9. 显示代码(l) (gdb) l 11 B string 12 C map[int]string 13 D []string 14 } 15 16 func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) { 17 fmt.Println("DBGTestRun Begin!n") 18 waiter := &sync.WaitGroup{} 19 20 waiter.Add(1)
  10. 单步执行(n) (gdb) n DBGTestRun Begin! 18 waiter := &sync.WaitGroup{}
  11. 打印变量信息(print/p) 在进入DBGTestRun的地方设置断点(b dbgTest.go:16),进入该函数后,通过p命令显示对应变量: (gdb) l 17 12 C map[int]string 13 D []string 14 } 15 16 func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) { 17 fmt.Println("DBGTestRun Begin!n") 18 waiter := &sync.WaitGroup{} 19 20 waiter.Add(1) 21 go RunFunc1(var1, waiter) (gdb) p var1 $3 = 1 (gdb) p var2 $4 = "golang dbg test" (gdb) p var3 No symbol "var3" in current context. 从上面的输出我们可以看到一个很奇怪的事情,虽然DBGTestRun有4个参数传入,但是,似乎var3和var4 gdb无法识别,在后续对dlv的实验操作中,我们发现,dlv能够识别var3, var4.
  12. 查看调用栈(bt),切换调用栈(f n),显示当前栈变量信息 (gdb) bt #0 GoWorks/GoDbg/mylib.DBGTestRun (var1=1, var2="golang dbg test") at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:17 #1 0x00000000004018c2 in main.main () at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:27 (gdb) f 1 #1 0x00000000004018c2 in main.main () at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:27 27 mylib.DBGTestRun(var1, var2, var3, var4) (gdb) l 22 var4.A = 1 23 var4.B = "golang dbg my struct field B" 24 var4.C = map[int]string{1: "value1", 2: "value2", 3: "value3"} 25 var4.D = []string{"D1", "D2", "D3"} 26 27 mylib.DBGTestRun(var1, var2, var3, var4) 28 fmt.Println("Golang dbg test over") 29 } (gdb) print var1 $5 = 1 (gdb) print var2 $6 = "golang dbg test" (gdb) print var3 $7 = []int = {1, 2, 3} (gdb) print var4 $8 = {A = 1, B = "golang dbg my struct field B", C = map[int]string = {[1] = "value1", [2] = "value2", [3] = "value3"}, D = []string = {"D1", "D2", "D3"}}
  13. 显示goroutine列表(info goroutines) 当程序执行到dbgTest.go:23时,程序通过go启动了第一个goroutine,并执行RunFunc1(),我们可以通过上述命令查看goroutine列表 (gdb) n 23 waiter.Add(1) (gdb) info goroutines * 1 running runtime.systemstack_switch 2 waiting runtime.gopark 17 waiting runtime.gopark 18 waiting runtime.gopark 19 runnable GoWorks/GoDbg/mylib.RunFunc1
  14. 查看goroutine的具体情况(goroutine n cmd) (gdb) goroutine 19 bt #0 GoWorks/GoDbg/mylib.RunFunc1 (variable=1, waiter=0xc8200721f0) at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:36 #1 0x0000000000456df1 in runtime.goexit () at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:1998 #2 0x0000000000000001 in ?? () #3 0x000000c8200721f0 in ?? () #4 0x0000000000000000 in ?? () 我们可以通过上述指令查看goroutine 9的调用栈,显然,该goroutine正在执行dbgTest.go:36行的函数:RunFunc1的goroutine。我们通过goroutine 19 info args等命令来查看该goroutine最顶层调用栈的变量信息,但是,如果我们需要查看的信息不再最顶层调用栈上,则很遗憾,gdb没法输出 (gdb) goroutine 19 info args variable = 1 waiter = 0xc8200721f0 (gdb) goroutine 19 p waiter $1 = (struct sync.WaitGroup *) 0xc8200721f0 (gdb) goroutine 19 p *waiter $2 = {state1 = "0000000001000000000000", sema = 0} 当我们执行到第26行,第2个goroutine被我们启动时,再次查看goroutine列表: (gdb) n 26 waiter.Add(1) (gdb) info goroutines * 1 running runtime.systemstack_switch 2 waiting runtime.gopark 17 waiting runtime.gopark 18 waiting runtime.gopark * 19 running syscall.Syscall 20 runnable GoWorks/GoDbg/mylib.RunFunc2 此时我们再次查看goroutine 19的状态 (gdb) goroutine 19 bt #0 syscall.Syscall () at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/syscall/asm_linux_amd64.s:19 #1 0x00000000004ab95f in syscall.write (fd=1, p= []uint8 = {...}, n=859530587568, err=...) at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/syscall/zsyscall_linux_amd64.go:1064 #2 0x00000000004ab40d in syscall.Write (fd=5131648, p= []uint8, n=0, err=...) at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/syscall/syscall_unix.go:180 #3 0x000000000046c928 in os.(*File).write (f=0xc820084008, b= []uint8, n=4571929, err=...) at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/os/file_unix.go:255 #4 0x000000000046aa24 in os.(*File).Write (f=0xc82008a000, b= []uint8 = {...}, n=7, err=...) at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/os/file.go:144 #5 0x000000000045c707 in fmt.Fprintf (w=..., format="var1:%vn", a= []interface {} = {...}, n=7, err=...) at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:190 #6 0x000000000045c7b4 in fmt.Printf (format="var1:%vn", a= []interface {} = {...}, n=7, err=...) at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:197 #7 0x00000000004583eb in GoWorks/GoDbg/mylib.RunFunc1 (variable=1, waiter=0xc8200721f0) at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:37 #8 0x0000000000456df1 in runtime.goexit () at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:1998 #9 0x0000000000000001 in ?? () #10 0x000000c8200721f0 in ?? () #11 0x0000000000000000 in ?? () 从第7,8层调用栈我们可以看到,此时goroutine 19已经进入到RunFunc1fmt.Printf函数中,当我们尝试在goroutine 19上切换栈时,gdb报错: (gdb) goroutine 19 f 7 #7 0x00000000004583eb in GoWorks/GoDbg/mylib.RunFunc1 (variable=1, waiter=0xc8200721f0) at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:37 37 fmt.Printf("var1:%vn", variable) Python Exception <class 'gdb.error'> Frame is invalid.: Error occurred in Python command: Frame is invalid. 似乎gdb不允许我们在goroutine上做调用栈的切换,因此我们没法在这种状态下查看某层调用栈的变量信息。缺少在goroutine上不同frame的变量查看,个人感觉gdb调试Golang程序功能大打折扣,在后面对dlv的实验操作中我们可以看到,dlv可以!

dlv调试程序

尝试了”老牌”调试器gdb,我们再来试试新进的Golang原生调试器delve(dlv)

Delve is a debugger for the Go programming language. The goal of the project is to provide a simple, full featured debugging tool for Go

dlv是Golang实现的Golang调试器,目前dlv对windows平台的支持似乎不是很好,我在windows平台调试,dlv无法找到目标程序的源代码,因此建议在Linux平台下调试Golang程序时使用。使用dlv前,需在本地通过go get github.com/derekparker/delve/cmd/dlv进行安装。dlv的详细介绍可参见github上的delve介绍。以下是具体操作说明

  1. 带参数启动程序(dlv exec ./GoDbg -- arg1 arg2) [lday@alex GoDbg]$ dlv exec ./GoDbg -- arg1 arg2 Type 'help' for list of commands. (dlv)
  2. 在main函数上设置断点(b) (dlv) b main.main Breakpoint 1 set at 0x40101b for main.main() ./main.go:9
  3. 启动调试,断点后继续执行(c) (dlv) c > main.main() ./main.go:9 (hits goroutine(1):1 total:1) (PC: 0x40101b) 4: "GoWorks/GoDbg/mylib" 5: "fmt" 6: "os" 7: ) 8: => 9: func main() { 10: fmt.Println("Golang dbg test...") 11: 12: var argc = len(os.Args) 13: var argv = append([]string{}, os.Args...) 14:
  4. 在文件dbgTest.go上通过行号设置断点(b) (dlv) b dbgTest.go:17 Breakpoint 2 set at 0x457f51 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:17 (dlv) b dbgTest.go:23 Breakpoint 3 set at 0x4580d0 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:23 (dlv) b dbgTest.go:26 Breakpoint 4 set at 0x458123 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:26 (dlv) b dbgTest.go:29 Breakpoint 5 set at 0x458166 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:29
  5. 显示所有断点列表(bp) (dlv) bp Breakpoint unrecovered-panic at 0x429690 for runtime.startpanic() /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/panic.go:524 (0) Breakpoint 1 at 0x40101b for main.main() ./main.go:9 (1) Breakpoint 2 at 0x457f51 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:17 (0) Breakpoint 3 at 0x4580d0 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:23 (0) Breakpoint 4 at 0x458123 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:26 (0) Breakpoint 5 at 0x458166 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:29 (0) dlv似乎没有提供类似gdbdis x,禁止某个断点的功能,在文档中暂时没有查到。不过这个功能用处不大。
  6. 删除某个断点(clear x) (dlv) clear 5 Breakpoint 5 cleared at 0x458166 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:29 (dlv) bp Breakpoint unrecovered-panic at 0x429690 for runtime.startpanic() /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/panic.go:524 (0) Breakpoint 1 at 0x40101b for main.main() ./main.go:9 (1) Breakpoint 2 at 0x457f51 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:17 (0) Breakpoint 3 at 0x4580d0 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:23 (0) Breakpoint 4 at 0x458123 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:26 (0)
  7. 显示当前运行的代码位置(ls) (dlv) ls > GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:17 (hits goroutine(1):1 total:1) (PC: 0x457f51) 12: C map[int]string 13: D []string 14: } 15: 16: func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) { => 17: fmt.Println("DBGTestRun Begin!n") 18: waiter := &sync.WaitGroup{} 19: 20: waiter.Add(1) 21: go RunFunc1(var1, waiter) 22:
  8. 查看当前调用栈信息(bt) (dlv) bt 0 0x0000000000457f51 in GoWorks/GoDbg/mylib.DBGTestRun at ./mylib/dbgTest.go:17 1 0x0000000000401818 in main.main at ./main.go:27 2 0x000000000042aefb in runtime.main at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/proc.go:188 3 0x0000000000456df0 in runtime.goexit at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:1998
  9. 输出变量信息(print/p) (dlv) print var1 1 (dlv) print var2 "golang dbg test" (dlv) print var3 []int len: 3, cap: 3, [1,2,3] (dlv) print var4 GoWorks/GoDbg/mylib.MyStruct { A: 1, B: "golang dbg my struct field B", C: map[int]string [ 1: "value1", 2: "value2", 3: "value3", ], D: []string len: 3, cap: 3, ["D1","D2","D3"],} 类比gdb调试,我们看到,之前我们使用gdb进行调试时,发现gdb在此时无法输出var3, var4的内容,而dlv可以
  10. 在第n层调用栈上执行相应指令(frame n cmd) (dlv) frame 1 ls 22: var4.A = 1 23: var4.B = "golang dbg my struct field B" 24: var4.C = map[int]string{1: "value1", 2: "value2", 3: "value3"} 25: var4.D = []string{"D1", "D2", "D3"} 26: => 27: mylib.DBGTestRun(var1, var2, var3, var4) 28: fmt.Println("Golang dbg test over") 29: } frame 1 ls将显示程序在第1层调用栈上的具体实行位置
  11. 查看goroutine的信息(goroutines) 当我们执行到dbgTest.go:26时,我们已经启动了两个goroutine (dlv) > GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:26 (hits goroutine(1):1 total:1) (PC: 0x458123) 21: go RunFunc1(var1, waiter) 22: 23: waiter.Add(1) 24: go RunFunc2(var2, waiter) 25: => 26: waiter.Add(1) 27: go RunFunc3(&var3, waiter) 28: 29: waiter.Add(1) 30: go RunFunc4(&var4, waiter) 31: 此时我们来查看程序的goroutine状态信息 (dlv) goroutines [6 goroutines] * Goroutine 1 - User: ./mylib/dbgTest.go:26 GoWorks/GoDbg/mylib.DBGTestRun (0x458123) (thread 9022) Goroutine 2 - User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/proc.go:263 runtime.gopark (0x42b2d3) Goroutine 3 - User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/proc.go:263 runtime.gopark (0x42b2d3) Goroutine 4 - User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/proc.go:263 runtime.gopark (0x42b2d3) Goroutine 5 - User: ./mylib/dbgTest.go:39 GoWorks/GoDbg/mylib.RunFunc1 (0x4583eb) (thread 9035) Goroutine 6 - User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/format.go:130 fmt.(*fmt).padString (0x459545) 从输出的信息来看,先启动的goroutine 5,执行RunFunc1,此时还没有执行fmt.Printf,而后启动的goroutine 6,执行RunFunc2,则已经进入到fmt.Printf的内部调用过程中了
  12. 进一步查看goroutine信息(goroutine x) 接第11步的操作,此时我想查看goroutine 6的具体执行情况,则执行goroutine 6 (dlv) goroutine 6 Switched from 1 to 6 (thread 9022) 在此基础上,执行bt,则可以看到当前goroutine的调用栈情况 (dlv) bt 0 0x0000000000454730 in runtime.systemstack_switch at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:245 1 0x000000000040f700 in runtime.mallocgc at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/malloc.go:643 2 0x000000000040fc43 in runtime.rawmem at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/malloc.go:809 3 0x000000000043c2a5 in runtime.growslice at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/slice.go:95 4 0x000000000043c015 in runtime.growslice_n at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/slice.go:44 5 0x0000000000459545 in fmt.(*fmt).padString at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/format.go:130 6 0x000000000045a13f in fmt.(*fmt).fmt_s at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/format.go:322 7 0x000000000045e905 in fmt.(*pp).fmtString at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:518 8 0x000000000046200f in fmt.(*pp).printArg at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:797 9 0x0000000000468a8d in fmt.(*pp).doPrintf at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:1238 10 0x000000000045c654 in fmt.Fprintf at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:188 此时输出了10层调用栈,但似乎最原始的我自身程序dbgTest.go的调用栈没有输出, 可以通过bt加depth参数,设定bt的输出深度,进而找到我们自己的调用栈,例如bt 13 (dlv) bt 13 ... 10 0x000000000045c654 in fmt.Fprintf at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:188 11 0x000000000045c74b in fmt.Printf at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:197 12 0x000000000045846f in GoWorks/GoDbg/mylib.RunFunc2 at ./mylib/dbgTest.go:50 13 0x0000000000456df0 in runtime.goexit at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:1998 我们看到,我们自己dbgTest.go的调用栈在第12层。当前goroutine已经不再我们自己的调用栈上,而是进入到系统函数的调用中,在这种情况下,使用gdb进行调试时,我们发现,此时我们没有很好的方法能够输出我们需要的调用栈变量信息。dlv可以!此时只需简单的通过frame x cmd就可以输出我们想要的调用栈信息了 (dlv) frame 12 ls 45: time.Sleep(10 * time.Second) 46: waiter.Done() 47: } 48: 49: func RunFunc2(variable string, waiter *sync.WaitGroup) { => 50: fmt.Printf("var2:%vn", variable) 51: time.Sleep(10 * time.Second) 52: waiter.Done() 53: } 54: 55: func RunFunc3(pVariable *[]int, waiter *sync.WaitGroup) { (dlv) frame 12 print variable "golang dbg test" (dlv) frame 12 print waiter *sync.WaitGroup { state1: [12]uint8 [0,0,0,0,2,0,0,0,0,0,0,0], sema: 0,} 多好的功能啊!
  13. 查看当前是在哪个goroutine上(goroutine) 当使用goroutine不带参数时,dlv就会显示当前goroutine信息,这可以帮助我们在调试时确认是否需要做goroutine切换 (dlv) goroutine Thread 9022 at ./mylib/dbgTest.go:26 Goroutine 6: Runtime: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:245 runtime.systemstack_switch (0x454730) User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/format.go:130 fmt.(*fmt).padString (0x459545) Go: ./mylib/dbgTest.go:26 GoWorks/GoDbg/mylib.DBGTestRun (0x458123)

dlv前端(gdlv)

dlv提供了类似gdb的cli调试系统,而有第三方还提供了dlv的GUI前端(gdlv),对于那些习惯了使用GUI进行调试的人来说,结合gdlv和dlv,调试会更加方便。gdlv有个问题是:他无法在xwindows server上运行,只能在server本地运行。

结论

综合比较两个Golang程序调试器gdb和dlv,我认为dlv的功能更为完善,更能满足实际调试时的功能需求。两者的优缺点比较大致如下

调试器

优势

不足

dlv

对goroutine, go类型调试支持比较完善

只支持 go, 不支持调试某些Go内部底部数据

gdb

符合现有的调试习惯,类似C/C++调试指令都有

对goroutine场景支持不足,不能很好的应对goroutine的调试

https://github.com/go-delve/delve/tree/master/Documentation