原文:http://lday.me/2017/02/27/0005_gdb-vs-dlv/

通过log库输出日志,我们可以对程序进行异常分析和问题追踪。但有时候,我也希望能有更直接的程序跟踪及定位工具能够帮助我们更方便快捷的追踪、定位问题,最直观的感觉还是使用调试器。Linux平台下,原生的C/C++程序,我们往往使用gdb进行程序调试,切换到Golang,我们同样还是可以使用gdb进行调试。同时我们还可以使用golang实现的调试器dlv进行调试。以下内容是我对gdb以及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:%d\n", argc)
fmt.Printf("argv:%v\n", 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:%v\n", 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:%v\n", variable)
time.Sleep(10 * time.Second)
waiter.Done()
}
 
func RunFunc3(pVariable *[]int, waiter *sync.WaitGroup) {
fmt.Printf("*pVar3:%v\n", *pVariable)
time.Sleep(10 * time.Second)
waiter.Done()
}
 
func RunFunc4(pVariable *MyStruct, waiter *sync.WaitGroup) {
fmt.Printf("*pVar4:%v\n", *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 = "\000\000\000\000\001\000\000\000\000\000\000", 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:%v\n", 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:%v\n", 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:%v\n", 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:%v\n", 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环境调试支持比较完善 暂时没找到
gdb 符合现有的调试习惯,类似C/C++调试指令都有 对goroutine场景支持不足,不能很好的应对goroutine的调试

Golang程序调试工具介绍(gdb vs dlv)的更多相关文章

  1. 应用程序调试工具gdb,王明学learn

    应用程序调试工具gdb学习使用 一.GDB简介 GDB 是 GNU 发布的一款功能强大的程序调试工具.GDB 主要完成下面三个方面的功能: 1.启动被调试程序. 2.让被调试的程序在指定的位置停住. ...

  2. Linux GDB程序调试工具使用简单介绍

    GDB概述 GDB是GNU开源组织公布的一个强大的UNIX下的程序调试工具.也许,各位比較喜欢那种图形界面方式的,像VC.BCB等IDE的调试,但假设你是在UNIX平台下做软件,你会发现GDB这个调试 ...

  3. Linux环境下的GCC编译器与GDB调试工具介绍

    假如现在我们有如下代码需要编译运行和调试.文件名为:test.c #include <stdio.h> int main() { int day, month, year, sum, le ...

  4. 在MacOS上使用gdb(cgdb)调试Golang程序

    如果你在MacOS上使用GDB工具载入Golang程序时无法载入,这篇文章可以解决.本文不具体介绍调试的方法,网上的文章太多了就不赘述了. cgdb使用的是gdb的内核,方法和原理试用本文. 问题分析 ...

  5. C实战:强大的程序调试工具GDB

    C实战:强大的程序调试工具GDB 1.基本调试 这里只列举最最常用的GDB命令. 1.1 启动GDB gdb program:准备调试程序.也可以直接进入gdb,再通过file命令加载. 1.2 添加 ...

  6. GDB程序调试工具

    GDB程序调试工具 GDB主要完成下面三个方面的功能: 启动被调试程序 让被调试程序在指定的位置停住 当程序被停住时,可以检查程序状态 GDB快速入门 编译生成可执行文件 gcc -g test.c ...

  7. Linux GDB 程序调试工具使用详解

    转自    http://www.codeceo.com/article/linux-gdb-tools.html 整理的挺全的 GDB概述 GDB是GNU开源组织发布的一个强大的UNIX下的程序调试 ...

  8. 多阶段构建Golang程序Docker镜像

    Docker简介 Docker是基于Linux容器技术(LXC),使用Go语言实现的开源项目,诞生于2013年,遵循Apache2.0协议.Docker自开源后,受到广泛的关注和讨论. Docker在 ...

  9. CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中

    CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中 转载注明来源: 本文链接 来自osnosn的博客,写于 2019-10-28. 编写了个golang程序,用到了这个C ...

随机推荐

  1. 201871010101-陈来弟《面向对象程序设计(JAVA)》 第14周学习总结

    实验十二  Swing图形界面组件(一) 实验时间 2019-11-29 第一部分:基础知识 Swing和MVC设计模式 (1)设计模式(Design pattern)是设计者一种流行的 思考设计问题 ...

  2. oracle查看被锁的表及解除锁

    -- 查看被锁对象 select object_name,machine,s.sid,s.serial# from v$locked_object l,dba_objects o ,v$session ...

  3. 【Java】Spring

    AnnotationConfigApplicationContext:从一个或多个基于Jav a的配置类中加载Spring应用上下文.AnnotationConfigWebApplicationCon ...

  4. SVN服务器_客户端下载和安装

    SVN服务器下载和安装 1.登录 http://subversion.apache.org/packages.html 在该页面可以看到SVN为各种操作系统提供的服务端,单击Win32Svn链接就会导 ...

  5. Nginx配置文件nginx.conf(八)

    原文链接:https://www.cnblogs.com/knowledgesea/p/5175711.html 在nginx.conf的注释符号是#. 默认的nginx.conf内容为: #user ...

  6. Win10 Mactype 字体优化

    1.下载安装 Mactype :http://www.mactype.net/ 2. 打开MacType Tray.exe,右键其在任务栏图标就能选择配置文件. 分享一个配置文件: [General] ...

  7. 安装WIN7系统备忘录

    安装WIN7系统备忘录因为安装WIN7设置项太多,制作RAMOS如果忘了某项设置,终归是不方便,记录如下:1.用WINNTSETUP安装到VHD中,安装时优化选项中建议勾选关闭休眠和虚拟内存功能(假设 ...

  8. [LeetCode] 891. Sum of Subsequence Widths 子序列宽度之和

    Given an array of integers A, consider all non-empty subsequences of A. For any sequence S, let the  ...

  9. [LeetCode] 43. Multiply Strings 字符串相乘

    Given two non-negative integers num1 and num2represented as strings, return the product of num1 and  ...

  10. divide two numbers using + opertor

    package testpacknm; import java.util.Scanner; public class testcnm { public static void main(String[ ...