# 11.2 使用GDB調試
開發程序過程中調試代碼是開發者經常要做的一件事情,Go語言不像PHP、Python等動態語言,只要修改不需要編譯就可以直接輸出,而且可以動態的在運行環境下打印數據。當然Go語言也可以通過Println之類的打印數據來調試,但是每次都需要重新編譯,這是一件相當麻煩的事情。我們知道在Python中有pdb/ipdb之類的工具調試,Javascript也有類似工具,這些工具都能夠動態的顯示變量信息,單步調試等。不過慶幸的是Go也有類似的工具支持:GDB。Go內部已經內置支持了GDB,所以,我們可以通過GDB來進行調試,那么本小節就來介紹一下如何通過GDB來調試Go程序。
## GDB調試簡介
GDB是FSF(自由軟件基金會)發布的一個強大的類UNIX系統下的程序調試工具。使用GDB可以做如下事情:
1. 啟動程序,可以按照開發者的自定義要求運行程序。
2. 可讓被調試的程序在開發者設定的調置的斷點處停住。(斷點可以是條件表達式)
3. 當程序被停住時,可以檢查此時程序中所發生的事。
4. 動態的改變當前程序的執行環境。
目前支持調試Go程序的GDB版本必須大于7.1。
編譯Go程序的時候需要注意以下幾點
1. 傳遞參數-ldflags "-s",忽略debug的打印信息
2. 傳遞-gcflags "-N -l" 參數,這樣可以忽略Go內部做的一些優化,聚合變量和函數等優化,這樣對于GDB調試來說非常困難,所以在編譯的時候加入這兩個參數避免這些優化。
## 常用命令
GDB的一些常用命令如下所示
- list
簡寫命令`l`,用來顯示源代碼,默認顯示十行代碼,后面可以帶上參數顯示的具體行,例如:`list 15`,顯示十行代碼,其中第15行在顯示的十行里面的中間,如下所示。
10 time.Sleep(2 * time.Second)
11 c <- i
12 }
13 close(c)
14 }
15
16 func main() {
17 msg := "Starting main"
18 fmt.Println(msg)
19 bus := make(chan int)
- break
簡寫命令 `b`,用來設置斷點,后面跟上參數設置斷點的行數,例如`b 10`在第十行設置斷點。
- delete
簡寫命令 `d`,用來刪除斷點,后面跟上斷點設置的序號,這個序號可以通過`info breakpoints`獲取相應的設置的斷點序號,如下是顯示的設置斷點序號。
Num Type Disp Enb Address What
2 breakpoint keep y 0x0000000000400dc3 in main.main at /home/xiemengjun/gdb.go:23
breakpoint already hit 1 time
- backtrace
簡寫命令 `bt`,用來打印執行的代碼過程,如下所示:
#0 main.main () at /home/xiemengjun/gdb.go:23
#1 0x000000000040d61e in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244
#2 0x000000000040d6c1 in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267
#3 0x0000000000000000 in ?? ()
- info
info命令用來顯示信息,后面有幾種參數,我們常用的有如下幾種:
- `info locals`
顯示當前執行的程序中的變量值
- `info breakpoints`
顯示當前設置的斷點列表
- `info goroutines`
顯示當前執行的goroutine列表,如下代碼所示,帶*的表示當前執行的
* 1 running runtime.gosched
* 2 syscall runtime.entersyscall
3 waiting runtime.gosched
4 runnable runtime.gosched
- print
簡寫命令`p`,用來打印變量或者其他信息,后面跟上需要打印的變量名,當然還有一些很有用的函數$len()和$cap(),用來返回當前string、slices或者maps的長度和容量。
- whatis
用來顯示當前變量的類型,后面跟上變量名,例如`whatis msg`,顯示如下:
type = struct string
- next
簡寫命令 `n`,用來單步調試,跳到下一步,當有斷點之后,可以輸入`n`跳轉到下一步繼續執行
- coutinue
簡稱命令 `c`,用來跳出當前斷點處,后面可以跟參數N,跳過多少次斷點
- set variable
該命令用來改變運行過程中的變量值,格式如:`set variable <var>=<value>`
## 調試過程
我們通過下面這個代碼來演示如何通過GDB來調試Go程序,下面是將要演示的代碼:
package main
import (
"fmt"
"time"
)
func counting(c chan<- int) {
for i := 0; i < 10; i++ {
time.Sleep(2 * time.Second)
c <- i
}
close(c)
}
func main() {
msg := "Starting main"
fmt.Println(msg)
bus := make(chan int)
msg = "starting a gofunc"
go counting(bus)
for count := range bus {
fmt.Println("count:", count)
}
}
編譯文件,生成可執行文件gdbfile:
go build -gcflags "-N -l" gdbfile.go
通過gdb命令啟動調試:
gdb gdbfile
啟動之后首先看看這個程序是不是可以運行起來,只要輸入`run`命令回車后程序就開始運行,程序正常的話可以看到程序輸出如下,和我們在命令行直接執行程序輸出是一樣的:
(gdb) run
Starting program: /home/xiemengjun/gdbfile
Starting main
count: 0
count: 1
count: 2
count: 3
count: 4
count: 5
count: 6
count: 7
count: 8
count: 9
[LWP 2771 exited]
[Inferior 1 (process 2771) exited normally]
好了,現在我們已經知道怎么讓程序跑起來了,接下來開始給代碼設置斷點:
(gdb) b 23
Breakpoint 1 at 0x400d8d: file /home/xiemengjun/gdbfile.go, line 23.
(gdb) run
Starting program: /home/xiemengjun/gdbfile
Starting main
[New LWP 3284]
[Switching to LWP 3284]
Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23
23 fmt.Println("count:", count)
上面例子`b 23`表示在第23行設置了斷點,之后輸入`run`開始運行程序。現在程序在前面設置斷點的地方停住了,我們需要查看斷點相應上下文的源碼,輸入`list`就可以看到源碼顯示從當前停止行的前五行開始:
(gdb) list
18 fmt.Println(msg)
19 bus := make(chan int)
20 msg = "starting a gofunc"
21 go counting(bus)
22 for count := range bus {
23 fmt.Println("count:", count)
24 }
25 }
現在GDB在運行當前的程序的環境中已經保留了一些有用的調試信息,我們只需打印出相應的變量,查看相應變量的類型及值:
(gdb) info locals
count = 0
bus = 0xf840001a50
(gdb) p count
$1 = 0
(gdb) p bus
$2 = (chan int) 0xf840001a50
(gdb) whatis bus
type = chan int
接下來該讓程序繼續往下執行,請繼續看下面的命令
(gdb) c
Continuing.
count: 0
[New LWP 3303]
[Switching to LWP 3303]
Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23
23 fmt.Println("count:", count)
(gdb) c
Continuing.
count: 1
[Switching to LWP 3302]
Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23
23 fmt.Println("count:", count)
每次輸入`c`之后都會執行一次代碼,又跳到下一次for循環,繼續打印出來相應的信息。
設想目前需要改變上下文相關變量的信息,跳過一些過程,并繼續執行下一步,得出修改后想要的結果:
(gdb) info locals
count = 2
bus = 0xf840001a50
(gdb) set variable count=9
(gdb) info locals
count = 9
bus = 0xf840001a50
(gdb) c
Continuing.
count: 9
[Switching to LWP 3302]
Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23
23 fmt.Println("count:", count)
最后稍微思考一下,前面整個程序運行的過程中到底創建了多少個goroutine,每個goroutine都在做什么:
(gdb) info goroutines
* 1 running runtime.gosched
* 2 syscall runtime.entersyscall
3 waiting runtime.gosched
4 runnable runtime.gosched
(gdb) goroutine 1 bt
#0 0x000000000040e33b in runtime.gosched () at /home/xiemengjun/go/src/pkg/runtime/proc.c:927
#1 0x0000000000403091 in runtime.chanrecv (c=void, ep=void, selected=void, received=void)
at /home/xiemengjun/go/src/pkg/runtime/chan.c:327
#2 0x000000000040316f in runtime.chanrecv2 (t=void, c=void)
at /home/xiemengjun/go/src/pkg/runtime/chan.c:420
#3 0x0000000000400d6f in main.main () at /home/xiemengjun/gdbfile.go:22
#4 0x000000000040d0c7 in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244
#5 0x000000000040d16a in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267
#6 0x0000000000000000 in ?? ()
通過查看goroutines的命令我們可以清楚地了解goruntine內部是怎么執行的,每個函數的調用順序已經明明白白地顯示出來了。
## 小結
本小節我們介紹了GDB調試Go程序的一些基本命令,包括`run`、`print`、`info`、`set variable`、`coutinue`、`list`、`break` 等經常用到的調試命令,通過上面的例子演示,我相信讀者已經對于通過GDB調試Go程序有了基本的理解,如果你想獲取更多的調試技巧請參考官方網站的GDB調試手冊,還有GDB官方網站的手冊。
## links
* [目錄](<preface.md>)
* 上一節: [錯誤處理](<11.1.md>)
* 下一節: [Go怎么寫測試用例](<11.3.md>)
- 目錄
- Go環境配置
- Go安裝
- GOPATH 與工作空間
- Go 命令
- Go開發工具
- 小結
- Go語言基礎
- 你好,Go
- Go基礎
- 流程和函數
- struct
- 面向對象
- interface
- 并發
- 小結
- Web基礎
- web工作方式
- Go搭建一個簡單的web服務
- Go如何使得web工作
- Go的http包詳解
- 小結
- 表單
- 處理表單的輸入
- 驗證表單的輸入
- 預防跨站腳本
- 防止多次遞交表單
- 處理文件上傳
- 小結
- 訪問數據庫
- database/sql接口
- 使用MySQL數據庫
- 使用SQLite數據庫
- 使用PostgreSQL數據庫
- 使用beedb庫進行ORM開發
- NOSQL數據庫操作
- 小結
- session和數據存儲
- session和cookie
- Go如何使用session
- session存儲
- 預防session劫持
- 小結
- 文本文件處理
- XML處理
- JSON處理
- 正則處理
- 模板處理
- 文件操作
- 字符串處理
- 小結
- Web服務
- Socket編程
- WebSocket
- REST
- RPC
- 小結
- 安全與加密
- 預防CSRF攻擊
- 確保輸入過濾
- 避免XSS攻擊
- 避免SQL注入
- 存儲密碼
- 加密和解密數據
- 小結
- 國際化和本地化
- 設置默認地區
- 本地化資源
- 國際化站點
- 小結
- 錯誤處理,調試和測試
- 錯誤處理
- 使用GDB調試
- Go怎么寫測試用例
- 小結
- 部署與維護
- 應用日志
- 網站錯誤處理
- 應用部署
- 備份和恢復
- 小結
- 如何設計一個Web框架
- 項目規劃
- 自定義路由器設計
- controller設計
- 日志和配置設計
- 實現博客的增刪改
- 小結
- 擴展Web框架
- 靜態文件支持
- Session支持
- 表單支持
- 用戶認證
- 多語言支持
- pprof支持
- 小結
- 參考資料