學會使用 GDB 調試 Go 代碼

大家好,我是煎魚。

上一篇文章《一個 Demo 學會使用 Go Delve 調試》我們詳細介紹了 Go 語言如何使用 Delve 進行排查和調試,對於問題的解決非常的有幫助。

但調試工具肯定不止只有 Delve,今天我們來介紹第二個神器,那就是:GDB,補全我們的調試工具技術棧。

根據小夥伴們的反饋,我們後面再增加 IDE 的調試篇章。

GDB 是什麼

GDB 是一個類 UNIX 系統下的程序調試工具,允許你看到另一個程序在執行時 "內部" 發生了什麼,或者程序在崩潰時正在做什麼。

GDB Logo

主要可以做四類事情:

  1. 啓動你的程序,指定任何可能影響其行爲的東西。

  2. 使你的程序在指定的條件下停止。

  3. 檢查當你的程序停止時發生了什麼。

  4. 改變你程序中的東西,這樣你就可以試驗糾正一個錯誤的影響,並繼續瞭解另一個錯誤。

安裝

如果是在 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" -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.
...

常用的關鍵字如下:

進行調試

在調試上與 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")
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