通過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:%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,具體操作如下:
-
啟動調試程序(
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 = "\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已經進入到
RunFunc1
的fmt.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介紹。以下是具體操作說明
-
帶參數啟動程序(
dlv exec ./GoDbg -- arg1 arg2
)[lday@alex GoDbg]$ dlv exec ./GoDbg -- arg1 arg2 Type 'help' for list of commands. (dlv)
-
在main函數上設置斷點(
b
)(dlv) b main.main Breakpoint 1 set at 0x40101b for main.main() ./main.go:9
-
啟動調試,斷點后繼續執行(
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:
-
在文件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
-
顯示所有斷點列表(
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似乎沒有提供類似gdb
dis x
,禁止某個斷點的功能,在文檔中暫時沒有查到。不過這個功能用處不大。 -
刪除某個斷點(
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)
-
顯示當前運行的代碼位置(
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:
-
查看當前調用棧信息(
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
-
輸出變量信息(
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可以
-
在第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層調用棧上的具體實行位置 -
查看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
的內部調用過程中了 -
進一步查看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,}
多好的功能啊!
-
查看當前是在哪個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的調試 |