使用Delve進(jìn)行Golang代碼的調(diào)試

追蹤代碼中的錯誤可能是一件非常頭疼的事情。這在高度依賴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

", i)

time.Sleep(300 * time.Second)

fmt.Printf("goroutine id %d

", 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

", 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

", 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

", i)

11: ? ? ? ?time.Sleep(300 * time.Second)

12: ? ? ? ?fmt.Printf("goroutine id %d

", 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)編譯好在不同機器運行的程序。這就需要我們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

", i)

11: ? ? ? ?time.Sleep(300 * time.Second)

=> ?12: ? ? ? ?fmt.Printf("goroutine id %d

", 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)的源碼顯示出來,是不是很強大呢。更多的功能就自己參考文檔摸索吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容