https://yq.aliyun.com/articles/57578
追蹤代碼中的錯誤可能是一件非常頭疼的事情。這在高度依賴goroutine的Golang代碼調(diào)試中更加的突出。有一個趁手的 debug 工具就顯得非常的重要。我們先來看看 Go 官方的debug tool文檔寫的啥。 GDB does not understand Go programs well. The stack management, threading, and runtime contain aspects that differ enough from the execution model GDB expects that they can confuse the debugger, even when the program is compiled with gccgo. In short, the instructions below should be taken only as a guide to how to use GDB when it works, not as a guarantee of success. In time, a more Go-centric debugging architecture may be required.
總結(jié)一下上面說的話。 - Go 的debug工具有GDB這個玩意,但是目前貌似工作的不咋滴
- 目前官方只是給你介紹介紹這個玩意怎么用,但是不保證能成功
- 實話說,我們需要一個更懂 Go 的調(diào)試器
最后一句話透露了本質(zhì),目前還沒有一個非常好的調(diào)試工具。難道我們只能在不斷打日志然后 build 然后再打日志中調(diào)試程序嗎? 當(dāng)然不是,下面我來介紹一個專門為 Go而生的 debug 工具 Delve。 Delve目的就是為了解決開發(fā)者在使用 GDB 調(diào)試中遇到的各種各樣的問題。我們開始詳細(xì)的介紹一些使用Delve 調(diào)試代碼的例子。 安裝首先默認(rèn)你已經(jīng)安裝了 Go 環(huán)境,安裝命令很簡單,一句話。 go get github.com/derekparker/delve/cmd/dlv
注意:如果你使用Go1.5,你必須在運行這個命令前設(shè)置GO15VENDOREXPERIMENT=1 調(diào)試代碼首先得說明一下,實誠點說,當(dāng)你想用debug 工具的時候,你的代碼估計已經(jīng)不按照你想象的方式運行了。只是你不知道為什么會這樣,因此你可能需要換一種方式啟動你的程序,下面我們來演示一下如果使用dlv 來啟動你的程序。我們的示例代碼如下: package main
import (
"fmt"
"sync"
"time"
)
func dostuff(wg *sync.WaitGroup, i int) {
fmt.Printf("goroutine id %d\n", i)
time.Sleep(300 * time.Second)
fmt.Printf("goroutine id %d\n", i)
wg.Done()
}
func main() {
var wg sync.WaitGroup
workers := 10
wg.Add(workers)
for i := 0; i < workers; i++ {
go dostuff(&wg, i)
}
wg.Wait()
}
在這個示例代碼中,我們創(chuàng)建了10個goroutine ,這種代碼對于 GDB 來說是幾乎不可讀的,因為它對于goroutine 的支持很差。但是Delve 作為一個專業(yè)的 Go 調(diào)試器,對于goroutine 這種殺手級功能還是非常了解的。下面我們來啟動程序。 dlv debug test-debug.go
運行這個命令,dlv會去編譯你的代碼,然后傳一些參數(shù)給編譯器,好讓編譯器編譯出來更加方便調(diào)試的可執(zhí)行文件,然后啟動了你的程序,并且attach 上去,這樣你的console就會停留在了debug session ,下面就可以調(diào)試程序了。 首先我們在main函數(shù)上設(shè)置一個斷點。 (dlv) break main.main
Breakpoint 1 set at 0x22d3 for main.main() ./test-debug.go:16
輸出信息里面告訴了我們斷點的 ID和斷點的位置,函數(shù)名和文件名以及所在行數(shù)。我們使用continue 命令讓程序運行到我們打斷點的地方。 (dlv) continue
> main.main() ./test-debug.go:16 (hits goroutine(1):1 total:1) (PC: 0x22d3)
11: time.Sleep(300 * time.Second)
12: fmt.Printf("goroutine id %d\n", i)
13: wg.Done()
14: }
15:
=> 16: func main() {
17: var wg sync.WaitGroup
18: workers := 10
19:
20: wg.Add(workers)
21: for i := 0; i < workers; i++ {
(dlv)
現(xiàn)在程序就停在了第一個斷點,現(xiàn)在我們可以使用next 命令讓程序運行到下一句話,如果你想繼續(xù)向下,可以直接按回車(Delve會重復(fù)上一條命令如果你按下回車鍵)。 (dlv) next
> main.main() ./test-debug.go:17 (PC: 0x22d7)
12: fmt.Printf("goroutine id %d\n", i)
13: wg.Done()
14: }
15:
16: func main() {
=> 17: var wg sync.WaitGroup
18: workers := 10
19:
20: wg.Add(workers)
21: for i := 0; i < workers; i++ {
22: go dostuff(&wg, i)
(dlv)
> main.main() ./test-debug.go:18 (PC: 0x22f1)
13: wg.Done()
14: }
15:
16: func main() {
17: var wg sync.WaitGroup
=> 18: workers := 10
19:
20: wg.Add(workers)
21: for i := 0; i < workers; i++ {
22: go dostuff(&wg, i)
23: }
(dlv)
> main.main() ./test-debug.go:20 (PC: 0x22fa)
15:
16: func main() {
17: var wg sync.WaitGroup
18: workers := 10
19:
=> 20: wg.Add(workers)
21: for i := 0; i < workers; i++ {
22: go dostuff(&wg, i)
23: }
24: wg.Wait()
25: }
(dlv)
現(xiàn)在我們可以嘗試使用print 命令去看一下變量的值。 (dlv) print wg
sync.WaitGroup {
state1: [12]uint8 [0,0,0,0,0,0,0,0,0,0,0,0],
sema: 0,}
(dlv) print workers
10
(dlv)
同時你也可以輸出一個表達(dá)式 (dlv) print workers < 100
true
下面我們在另外一個函數(shù)dostuff 上設(shè)置一個斷點 (dlv) break dostuff
Breakpoint 2 set at 0x2058 for main.dostuff() ./test-debug.go:9
我們使用continue 到我們設(shè)置斷點的地方,然后next (dlv) next
goroutine id 3
> main.dostuff() ./test-debug.go:10 (PC: 0x205f)
5: "sync"
6: "time"
7: )
8:
9: func dostuff(wg *sync.WaitGroup, i int) {
=> 10: fmt.Printf("goroutine id %d\n", i)
11: time.Sleep(300 * time.Second)
12: fmt.Printf("goroutine id %d\n", i)
13: wg.Done()
14: }
15:
(dlv)
可以看到Delve會告訴你目前的goroutine id,我們試試輸出一下i和wg. (dlv) print i
4
(dlv) print wg
*sync.WaitGroup {
state1: [12]uint8 [1,0,0,0,10,0,0,0,0,0,0,0],
sema: 0,}
我們創(chuàng)建了10個goroutine,如果你繼續(xù)使用next,你會發(fā)現(xiàn)你還是在同一個goroutine下。這樣就避免了被調(diào)試器跳轉(zhuǎn)到了另外的goroutine下導(dǎo)致不必要的調(diào)試錯誤??梢娺€是為 Go 而生的調(diào)試器才是真愛啊。 進(jìn)階調(diào)試其實很多時候,我們調(diào)試的代碼可能是daemon 程序或者需要實現(xiàn)編譯好在不同機(jī)器運行的程序。這就需要我們attach 到一個已經(jīng)在運行中的程序上,下面我們就使用上面的代碼來演示一下如何attach 到一個程序上進(jìn)行調(diào)試。首先將剛才的程序運行起來,我這里直接使用了 go build test-debug.go
./test-debug
然后使用ps查看正在運行的程序pid 501 40994 549 0 12:08AM ttys003 0:00.00 ./test-debug
然后我們attach 上去
dlv attach 40994
可以看到,熟悉的debug seesion 又回來了。下面我們可以繼續(xù)使用上面的命令去設(shè)置斷點了 (dlv) break dostuff
Breakpoint 1 set at 0x2058 for main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:9
(dlv) break dostuff:3
Breakpoint 2 set at 0x2144 for main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12
我使用continue 使程序運行到我設(shè)置斷點的地方 (dlv) continue
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(18):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(19):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(26):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(23):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(24):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(20):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(21):1 total:8) (PC: 0x2144)
> main.dostuff() /Users/xianlu/WorkSpace/golang/src/test-debug.go:12 (hits goroutine(25):1 total:8) (PC: 0x2144)
7: )
8:
9: func dostuff(wg *sync.WaitGroup, i int) {
10: fmt.Printf("goroutine id %d\n", i)
11: time.Sleep(300 * time.Second)
=> 12: fmt.Printf("goroutine id %d\n", i)
13: wg.Done()
14: }
15:
16: func main() {
17: var wg sync.WaitGroup
(dlv)
可以看到,Delve已經(jīng)打印出來了當(dāng)前正在運行的goroutine,下面我們print 一下我們當(dāng)前的i (dlv) print i
7
(dlv) print wg
*sync.WaitGroup {
state1: [12]uint8 [1,0,0,0,10,0,0,0,0,0,0,0],
sema: 0,}
和上面一樣,而且attach 到這個進(jìn)程后,也可以把對應(yīng)的源碼顯示出來,是不是很強(qiáng)大呢。更多的功能就自己參考文檔摸索吧。
|