學會使用 GDB 調試 Go 代碼
本文轉載自微信公眾號「腦子進煎魚了」,作者陳煎魚。轉載本文請聯(lián)系腦子進煎魚了公眾號。
大家好,我是煎魚。
上一篇文章《一個 Demo 學會使用 Go Delve 調試》我們詳細介紹了 Go 語言如何使用 Delve 進行排查和調試,對于問題的解決非常的有幫助。
但調試工具肯定不止只有 Delve,今天我們來介紹第二個神器,那就是:GDB,補全我們的調試工具技術棧。
根據(jù)小伙伴們的反饋,我們后面再增加 IDE 的調試篇章。
GDB 是什么
GDB 是一個類 UNIX 系統(tǒng)下的程序調試工具,允許你看到另一個程序在執(zhí)行時 "內(nèi)部 "發(fā)生了什么,或者程序在崩潰時正在做什么。
主要可以做四類事情:
- 啟動你的程序,指定任何可能影響其行為的東西。
- 使你的程序在指定的條件下停止。
- 檢查當你的程序停止時發(fā)生了什么。
- 改變你程序中的東西,這樣你就可以試驗糾正一個錯誤的影響,并繼續(xù)了解另一個錯誤。
安裝
如果是在 MacOS 上的話,可以直接使用 brew 安裝:
- brew install gdb
如果是在 Linux ,則使用自帶的包管理工具進行安裝即可,但需要注意安裝完畢后需要在 HOME 目錄進行相關配置。
安裝完畢后,執(zhí)行 gdb 就可以看到:
- $ gdb
- GNU gdb (GDB) 10.2
- ...
- (gdb)
寫此文時最新的 gdb 版本已經(jīng)是 10.2 了,我也升級了上去。問題不大,還多了不少功能。
編譯
我們還是使用先前的演示程序來進行調試。但由于 Go 語言的不少編譯優(yōu)化,因此在編譯運行程序時,有以下幾點需要注意:
- go build 編譯時需要增加 -gcflags=all="-N -l" 指令來關閉內(nèi)聯(lián)優(yōu)化,方便接下來的調試。
- 若是 MacOS,在 go build 編譯時需要增加 -ldflags='-compressdwarf=false' 指令。
- 若不禁止,則會出現(xiàn) No symbol table is loaded. Use the "file" command. 的錯誤。
- Go 編譯默認為了減少二進制大小會默認壓縮 DWARF 調試信息,但這會影響 gdb 的調試,因此需要將其關閉。
編譯的命令是:
- $ go build -gcflags=all="-N -l" -ldflags='-compressdwarf=false' .
輸出結果:
- !了魚煎進子腦
嘗試 gdb
GDB 有兩種調試模式,分別是文本用戶界面(Text User Interface,簡稱 tui)和默認的命令行模式:
- // 調試界面
- $ gdb -tui ./awesome-project
- // 命令行模式
- $ gdb ./awesome-project
接下來我們使用 gdb tui 的調試模式來給大家演示功能。
我們在執(zhí)行命令 gdb -tui ./awesome-project 后,窗口會切換為如下:
gdb tui 初始樣子
你會發(fā)現(xiàn)中間提示 “No Source Available”,此時你需要繼續(xù)回車兩次,他就會自動加載插件支持,提示:“Loading Go Runtime support.”。
我們就可以看到具體的代碼塊內(nèi)容,如下:
用 MacOS 的同學需要注意,如果你在斷點時發(fā)現(xiàn)發(fā)現(xiàn)了如下錯誤:
- (gdb) b main.main
- Breakpoint 1 at 0x10a2ea0: file /Users/eddycjy/go-application/awesomeProject/main.go, line 15.
- (gdb) r
- Starting program: /Users/eddycjy/go-application/awesomeProject/hello
- Unable to find Mach task port for process-id 64212: (os/kern) failure (0x5).
- (please check gdb is codesigned - see taskgated(8))
也就是 “please check gdb is codesigned - see taskgated(8)”,則需要重新處理證書認證和授權,是 MacOS 使用上的一個問題,具體可參考:《Codesign gdb on OSX》。
解決后,咱們的 gdb 就算是能夠正確的運行起來了!
常用 gdb 命令
在 gdb 中,和 dlv 一樣有常用的關鍵字命令。當然了,gdb 的 help all 輸出非常多:
- (gdb) help all
- Command class: aliases
- Command class: breakpoints
- awatch -- Set a watchpoint for an expression.
- break, brea, bre, br, b -- Set breakpoint at specified location.
- break-range -- Set a breakpoint for an address range.
- catch -- Set catchpoints to catch events.
- ...
常用的關鍵字如下:
- b:break 的縮寫,作用是打斷點,例如:main.main,可帶代碼行數(shù)。
- r:run 的縮寫,作用是運行程序到下一個斷點處。
- c:continue 的縮寫,作用是繼續(xù)執(zhí)行到下一個斷點。
- s:step 的縮寫,作用是單步執(zhí)行,如果有所調用的方法,將會進入該方法。
- l:list 的縮寫,作用是查看對應的源碼。
- n:next 的縮寫,作用是單步執(zhí)行,不會進入所調用的方法,。
- q:quit 的縮寫,作用是退出。
- info breakpoints:作用是查看所有設置的斷點信息。
- info locals:作用是查看變量信息。
- info args:作用是查看函數(shù)的入?yún)⒑统鰠⒌木唧w值。
- info goroutines:作用是查看 goroutines 的信息。
- goroutine 1 bt:作用是查看指定序號的 goroutine 調用堆棧。
進行調試
在調試上與 dlv 差不多,也是先執(zhí)行關鍵字 b 打斷點:
- (gdb) b main.main
- Breakpoint 1 at 0x10cbaa0: file /Users/eddycjy/go-application/awesomeProject/main.go, line 9.
也可以先執(zhí)行關鍵字 l 查看對應的代碼情況再進行做決定:
- (gdb) l main.main
- 4 "fmt"
- 5
- 6 "github.com/eddycjy/awesome-project/stringer"
- 7 )
- 8
- 9 func main() {
- 10 fmt.Println(stringer.Reverse("腦子進煎魚了!"))
- 11 }
查看對應 goroutines 正在運行的函數(shù)情況:
- (gdb) info goroutines
- 1 waiting runtime.gosched
- * 13 running runtime.goexit
根據(jù) pprof 等所得到的 goroutine 序號進行進一步的分析:
- (gdb) goroutine 1 bt
- #0 0x000000000040facb in runtime.gosched () at /home/user/go/src/runtime/proc.c:873
- #1 0x00000000004031c9 in runtime.chanrecv (c=void, ep=void, selected=void, received=void)
- at /home/user/go/src/runtime/chan.c:342
- #2 0x0000000000403299 in runtime.chanrecv1 (t=void, c=void) at/home/user/go/src/runtime/chan.c:423
- #3 0x000000000043075b in testing.RunTests (matchString...
注意一個細節(jié),gdb 調試是可以看到并對 runtime 包內(nèi)容的代碼進行斷點和分析的。
也可以和 dlv 一樣執(zhí)行 p 關鍵字輸出相應的值的類型、值內(nèi)容:
- (gdb) p re
- (gdb) p t
- $1 = (struct testing.T *) 0xf840688b60
- (gdb) p t
- $1 = (struct testing.T *) 0xf840688b60
- (gdb) p *t
- $2 = {errors = "", failed = false, ch = 0xf8406f5690}
- (gdb) p *t->ch
- $3 = struct hchan<*testing.T>
與 dlv 大同小異。
總結
總體上來講,MacOS 上使用 gdb 還是挺麻煩的,在 Linux 環(huán)境下使用 gdb 還是更方便些。
由于 dlv 和 gdb 在大致的調試上不會差距的太遠,因此本文就沒有過于展開。
若是對業(yè)務代碼進行分析,更建議使用 dlv,也就是我們上一篇文章所講的內(nèi)容。若有 runtime 庫的調試需求的話,推薦使用 gdb 來作為首要調試工具,若無這方面訴求,建議使用 dlv。