開發程序過程中調試代碼是開發者經常要做的一件事情,Go語言不像PHP、Python等動態語言,只要修改不需要編譯就可以直接輸出,而且可以動態的在運行環境下打印數據。當然Go語言也可以通過Println之類的打印數據來調試,但是每次都需要重新編譯,這是一件相當麻煩的事情。我們知道在Python中有pdb/ipdb之類的工具調試,Javascript也有類似工具,這些工具都能夠動態的顯示變量信息,單步調試等。不過慶幸的是Go也有類似的工具支持:GDB。Go內部已經內置支持了GDB,所以,我們可以通過GDB來進行調試,那么本小節就來介紹一下如何通過GDB來調試Go程序。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/11.2.md#gdb調試簡介)GDB調試簡介
GDB是FSF(自由軟件基金會)發布的一個強大的類UNIX系統下的程序調試工具。使用GDB可以做如下事情:
1. 啟動程序,可以按照開發者的自定義要求運行程序。
2. 可讓被調試的程序在開發者設定的調置的斷點處停住。(斷點可以是條件表達式)
3. 當程序被停住時,可以檢查此時程序中所發生的事。
4. 動態的改變當前程序的執行環境。
目前支持調試Go程序的GDB版本必須大于7.1。
編譯Go程序的時候需要注意以下幾點
1. 傳遞參數-ldflags "-s",忽略debug的打印信息
2. 傳遞-gcflags "-N -l" 參數,這樣可以忽略Go內部做的一些優化,聚合變量和函數等優化,這樣對于GDB調試來說非常困難,所以在編譯的時候加入這兩個參數避免這些優化。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/11.2.md#常用命令)常用命令
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 =`
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/11.2.md#調試過程)調試過程
我們通過下面這個代碼來演示如何通過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內部是怎么執行的,每個函數的調用順序已經明明白白地顯示出來了。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/11.2.md#小結)小結
本小節我們介紹了GDB調試Go程序的一些基本命令,包括`run`、`print`、`info`、`set variable`、`coutinue`、`list`、`break`?等經常用到的調試命令,通過上面的例子演示,我相信讀者已經對于通過GDB調試Go程序有了基本的理解,如果你想獲取更多的調試技巧請參考官方網站的GDB調試手冊,還有GDB官方網站的手冊。
- 第一章 Go環境配置
- 1.1 Go安裝
- 1.2 GOPATH 與工作空間
- 1.3 Go 命令
- 1.4 Go開發工具
- 1.5 小結
- 第二章 Go語言基礎
- 2.1 你好,Go
- 2.2 Go基礎
- 2.3 流程和函數
- 2.4 struct類型
- 2.5 面向對象
- 2.6 interface
- 2.7 并發
- 2.8 總結
- 第三章 Web基礎
- 3.1 Web工作方式
- 3.2 Go搭建一個Web服務器
- 3.3 Go如何使得Web工作
- 3.4 Go的http包詳解
- 3.5 小結
- 第四章 表單
- 4.1 處理表單的輸入
- 4.2 驗證表單的輸入
- 4.3 預防跨站腳本
- 4.4 防止多次遞交表單
- 4.5 處理文件上傳
- 4.6 小結
- 第五章 訪問數據庫
- 5.1 database/sql接口
- 5.2 使用MySQL數據庫
- 5.3 使用SQLite數據庫
- 5.4 使用PostgreSQL數據庫
- 5.5 使用beedb庫進行ORM開發
- 5.6 NOSQL數據庫操作
- 5.7 小結
- 第六章 session和數據存儲
- 6.1 session和cookie
- 6.2 Go如何使用session
- 6.3 session存儲
- 6.4 預防session劫持
- 6.5 小結
- 第七章 文本處理
- 7.1 XML處理
- 7.2 JSON處理
- 7.3 正則處理
- 7.4 模板處理
- 7.5 文件操作
- 7.6 字符串處理
- 7.7 小結
- 第八章 Web服務
- 8.1 Socket編程
- 8.2 WebSocket
- 8.3 REST
- 8.4 RPC
- 8.5 小結
- 第九章 安全與加密
- 9.1 預防CSRF攻擊
- 9.2 確保輸入過濾
- 9.3 避免XSS攻擊
- 9.4 避免SQL注入
- 9.5 存儲密碼
- 9.6 加密和解密數據
- 9.7 小結
- 第十章 國際化和本地化
- 10.1 設置默認地區
- 10.2 本地化資源
- 10.3 國際化站點
- 10.4 小結
- 第十一章 錯誤處理,調試和測試
- 11.1 錯誤處理
- 11.2 使用GDB調試
- 11.3 Go怎么寫測試用例
- 11.4 小結
- 第十二章 部署與維護
- 12.1 應用日志
- 12.2 網站錯誤處理
- 12.3 應用部署
- 12.4 備份和恢復
- 12.5 小結
- 第十三章 如何設計一個Web框架
- 13.1 項目規劃
- 13.2 自定義路由器設計
- 13.3 controller設計
- 13.4 日志和配置設計
- 13.5 實現博客的增刪改
- 13.6 小結
- 第十四章 擴展Web框架
- 14.1 靜態文件支持
- 14.2 Session支持
- 14.3 表單及驗證支持
- 14.4 用戶認證
- 14.5 多語言支持
- 14.6 pprof支持
- 14.7 小結
- 附錄A 參考資料