學會使用 GDB 調試 Go 代碼
大家好,我是煎魚。
上一篇文章《一個 Demo 學會使用 Go Delve 調試》我們詳細介紹了 Go 語言如何使用 Delve 進行排查和調試,對於問題的解決非常的有幫助。
但調試工具肯定不止只有 Delve,今天我們來介紹第二個神器,那就是:GDB,補全我們的調試工具技術棧。
根據小夥伴們的反饋,我們後面再增加 IDE 的調試篇章。
GDB 是什麼
GDB 是一個類 UNIX 系統下的程序調試工具,允許你看到另一個程序在執行時 "內部" 發生了什麼,或者程序在崩潰時正在做什麼。
GDB Logo
主要可以做四類事情:
-
啓動你的程序,指定任何可能影響其行爲的東西。
-
使你的程序在指定的條件下停止。
-
檢查當你的程序停止時發生了什麼。
-
改變你程序中的東西,這樣你就可以試驗糾正一個錯誤的影響,並繼續瞭解另一個錯誤。
安裝
如果是在 MacOS 上的話,可以直接使用 brew 安裝:
brew install gdb
如果是在 Linux ,則使用自帶的包管理工具進行安裝即可,但需要注意安裝完畢後需要在 HOME 目錄進行相關配置。
安裝完畢後,執行 gdb
就可以看到:
$ gdb
GNU gdb (GDB) 10.2
...
(gdb)
寫此文時最新的 gdb 版本已經是 10.2 了,我也升級了上去。問題不大,還多了不少功能。
編譯
我們還是使用先前的演示程序來進行調試。但由於 Go 語言的不少編譯優化,因此在編譯運行程序時,有以下幾點需要注意:
-
go build 編譯時需要增加
-gcflags=all="-N -l"
指令來關閉內聯優化,方便接下來的調試。 -
若是 MacOS,在 go build 編譯時需要增加
-ldflags='-compressdwarf=false'
指令。 -
若不禁止,則會出現
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 的調試模式來給大家演示功能。
我們在執行命令 gdb -tui ./awesome-project
後,窗口會切換爲如下:
gdb tui 初始樣子
你會發現中間提示 “No Source Available”,此時你需要繼續回車兩次,他就會自動加載插件支持,提示:“Loading Go Runtime support.”。
我們就可以看到具體的代碼塊內容,如下:
用 MacOS 的同學需要注意,如果你在斷點時發現發現瞭如下錯誤:
(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,可帶代碼行數。
-
r:run 的縮寫,作用是運行程序到下一個斷點處。
-
c:continue 的縮寫,作用是繼續執行到下一個斷點。
-
s:step 的縮寫,作用是單步執行,如果有所調用的方法,將會進入該方法。
-
l:list 的縮寫,作用是查看對應的源碼。
-
n:next 的縮寫,作用是單步執行,不會進入所調用的方法,。
-
q:quit 的縮寫,作用是退出。
-
info breakpoints:作用是查看所有設置的斷點信息。
-
info locals:作用是查看變量信息。
-
info args:作用是查看函數的入參和出參的具體值。
-
info goroutines:作用是查看 goroutines 的信息。
-
goroutine 1 bt:作用是查看指定序號的 goroutine 調用堆棧。
進行調試
在調試上與 dlv 差不多,也是先執行關鍵字 b
打斷點:
(gdb) b main.main
Breakpoint 1 at 0x10cbaa0: file /Users/eddycjy/go-application/awesomeProject/main.go, line 9.
也可以先執行關鍵字 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 正在運行的函數情況:
(gdb) info goroutines
1 waiting runtime.gosched
* 13 running runtime.goexit
根據 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...
注意一個細節,gdb 調試是可以看到並對 runtime 包內容的代碼進行斷點和分析的。
也可以和 dlv 一樣執行 p 關鍵字輸出相應的值的類型、值內容:
(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 環境下使用 gdb 還是更方便些。
由於 dlv 和 gdb 在大致的調試上不會差距的太遠,因此本文就沒有過於展開。
若是對業務代碼進行分析,更建議使用 dlv,也就是我們上一篇文章所講的內容。若有 runtime 庫的調試需求的話,推薦使用 gdb 來作爲首要調試工具,若無這方面訴求,建議使用 dlv。
公衆號
關注煎魚,吸取他的知識 👆
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/O9Ngzgg9xfHMs5RSK5wHQQ