概述
原文: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:%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,具体操作如下:
-
启动调试程序(
gdb
)[lday@alex GoDbg]$ gdb ./GoDbg
-
在main函数上设置断点(
b
)(gdb) b main.main Breakpoint 1 at 0x401000: file /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go, line 9.
-
带参数启动程序(
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() {
-
在文件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.
-
查看断点设置情况(
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
-
禁用断点(
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
-
删除断点(
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
-
断点后继续执行(
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)
-
显示代码(
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)
-
单步执行(
n
)(gdb) n DBGTestRun Begin! 18 waiter := &sync.WaitGroup{}
-
打印变量信息(
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.
-
查看调用栈(
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"}}
-
显示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
-
查看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 = "