別再用GDB了,一文掌握Go最好用的調(diào)試器Delve
2024年我們將全面開啟線下meetup,為了確保我們的活動能夠滿足大家的需求和興趣,希望邀請大家一起參與投票,選擇最適合的城市。
另外我們開放2024議題征集通道,歡迎各位有實戰(zhàn)經(jīng)驗、獨特觀點的小伙伴勇躍分享~
議題提交掃這里~
?? 導(dǎo)讀
Delve 是 Go 中使用最多的調(diào)試器,本文基于 Delve 對 Go 的調(diào)試進(jìn)行了介紹。如果你對基于日志的調(diào)試感到無奈,不妨看一下本文。讀完全文還可以參加文末龍年紅包封面抽獎活動哦!?? 目錄
1 Delve 1.1 dlv 安裝1.2 內(nèi)聯(lián)和優(yōu)化的關(guān)閉
1.3 dlv 中位置的定義
1.4 dlv 支持的表達(dá)式
1.5 使用 dlv 開啟 debug 的幾種方式
2 dlv 中相關(guān)命令 2.1 dlv 的配置 2.2 運(yùn)行相關(guān)命令
2.3 斷點相關(guān)命令 2.4 變量、內(nèi)存查看相關(guān)
2.5 堆棧相關(guān)
2.6 查詢相關(guān)命令
3 Goland 的支持 3.1 斷點相關(guān)
3.2 變量、內(nèi)存查看相關(guān) 3.3 協(xié)程、堆棧相關(guān)
01
Delve
Go 語言支持 GDB、LLDB 和 Delve 幾種調(diào)試器。其中 GDB 是最早支持的調(diào)試工具,Delve 則是專門為 Go 語言設(shè)計開發(fā)的調(diào)試工具。在調(diào)試 Go 程序時,Delve 是 GDB 的更好替代方案,它比 GDB 更了解 Go 的運(yùn)行時、數(shù)據(jù)結(jié)構(gòu)和表達(dá)式。其源碼位于 Delve 源碼。
1.1 dlv 安裝
參見 dlv 安裝:https://github.com/go-delve/delve/tree/master/Documentation/installation
1.2 內(nèi)聯(lián)和優(yōu)化的關(guān)閉
使用 dlv 進(jìn)行調(diào)試,需要關(guān)閉編譯器的內(nèi)聯(lián)、優(yōu)化:
-
1.10及以后,編譯時需指定-gcflags="all=-N -l"
-
1.10之前,編譯時需指定-gcflags="-N -l"
1.3 dlv 中位置的定義
dlv 中多數(shù)命令涉及位置(下文的 locspec),位置的定義支持以下幾種方式:
-
通過 Go 文件和行號指定,其格式為 filename:lineNo,如:
list aa/aa.go:15 // 打印aa.go:15上下5行的代碼
- 通過包名和和函數(shù)名指定,其格式為 package.function,如:
trace content-service/iteminfo.GetItemV2 // 為包content-service/iteminfo中函數(shù)GetItemV2添加trace斷點
- 通過當(dāng)前文件中絕對、相對位置指定,其格式為 lineNo 或者 +offset/-offset,如:
> iteminfo..GetItemV2()./iteminfo/itemv2.go:52 (hits goroutine(970048):1 total:1) (PC: 0x22e0f46)47: rsp.Code = 048: return nil49: }50:51: // GetItemV2 _=> 52: func (i *ItemInfoServiceImpl) GetItemV2(ctx context.Context,53: req *iteminfopb.GetItemV2Request, rsp *iteminfopb.GetItemV2Reply) (err error) {54:55: debug.Stack()56:57: err = i.getItemV2Impl(ctx, req, rsp)(dlv) b 55 // 命中斷點后,再次在本文件的55行添加斷點,其id為2Breakpoint 2 set at 0x22e0f73 for iteminfo.GetItemV2() ./api/iteminfo/itemv2.go:55(dlv) b +5 // 命中斷點后,再次在本文件的52+5行添加斷點,其id為3Breakpoint 3 set at 0x22e0f78 for iteminfo..GetItemV2() ./api/iteminfo/itemv2.go:57(dlv) b -5 // 命中斷點后,再次在本文件的52-5行添加斷點,其id為4Breakpoint 4 set at 0x22e0ecc for iteminfo.getItemV2Impl() ./api/iteminfo/itemv2.go:47
1.4 dlv 支持的表達(dá)式
目前 dlv 中支持表達(dá)式(下文中 expr),包括:
-
基本類型的一元、二元操作(不支持++ 、--)、比較運(yùn)算等,如 p 1+1、p 1<<2、p 1<2 等;
-
類型轉(zhuǎn)換,包括數(shù)值類型間轉(zhuǎn)換、string 和 []byte 間轉(zhuǎn)換等,如 p float32(1)、p []byte("aaa") ;
-
成員訪問,包括結(jié)構(gòu)體、map、數(shù)組類型成員的訪問,如 p mp["k"]、p arr[1] 。需要注意默認(rèn)對于結(jié)構(gòu)體,只打印2級的數(shù)據(jù),如需多級數(shù)據(jù)訪問,需要通過成員訪問;
-
指針的相關(guān)操作,包括獲取訪問地址、指針的解引用等,如 p x.i、p *(x.i) 等
-
一些內(nèi)建的函數(shù),包括 cap、len 等,如 p len("aaa") ;
-
Interface 類型的斷言,如 p iface1.(*struct astruct).B 或者 p iface1.(data).B ,需要注意兩種表達(dá)方式是等價的;
-
一些內(nèi)建的變量,如 p runtime.curg.sched ,具體可以通過 vars 命令查看,常用參數(shù)具體參見下表:
| 內(nèi)置變量 | 說明 |
| runtime.defaultGOROOT | Go默認(rèn)root路徑 |
| runtime.buildVersion | 構(gòu)建代碼的Go版本號 |
| runtime.argc、runtime.argv | argc、argv信息 |
| runtime.gomaxprocs | 邏輯處理器數(shù)目 |
| runtime.ncpu | cpu數(shù)目 |
| runtime.curg | 當(dāng)前線程的相關(guān)信息,具體參見curg定義 |
| time.Local | 當(dāng)前時區(qū)信息 |
| .......... | |
需要注意,上述 p 命令為 print 命令簡寫,具體可參見2.3.1節(jié)
如下:
(dlv) c main.go:48Breakpoint 1 set at 0x4bf4a7 for main.main() ./main.go:48> main.main() ./main.go:48 (hits goroutine(1):1 total:1) (PC: 0x4bf4a7)43: a = uint8(i)44: x.TestC(i)45: }46: }47:=> 48: x.TestB()49:50: mp := make(map[string]string)51: var intData int52: if reflect.TypeOf(intData).AssignableTo(reflect.TypeOf(mp).Elem()) {53: log.Println("no")(dlv) p a9(dlv) p -a // 一元操作符,打印負(fù)a-9(dlv) p %3.2f float32(a) // 強(qiáng)制類型轉(zhuǎn)換, 打印強(qiáng)制類型轉(zhuǎn)換后結(jié)果9.00(dlv) p []byte("aaa") // string和byte的轉(zhuǎn)換, 打印[]byte類型"aaa"[]uint8 len: 3, cap: 3, [97,97,97](dlv) p x.i // 成員的訪問, 打印x.i的值0(dlv) p &x.i // 獲取變量地址, 打印x.i的地址(*int)(0xc000193e30)(dlv) p *((*int)(0xc000193e30)) // 指針的解引用, 打印x.i的值0(dlv) p len("abc") // 內(nèi)置函數(shù)調(diào)用,打印abc的長度3(dlv) p animial.(*main.Cat).s // interface的斷言"test"(dlv) p animial.(data).s // interface的斷言、成員訪問"test"(dlv) p runtime.curg.sched // 內(nèi)置變量的訪問, 打印runtime.curg.sched的值runtime.gobuf {sp: 0, pc: 4438237, g: 824633745824, ctxt: unsafe.Pointer(0x0), ret: 0, lr: 0, bp: 0}
1.5 使用 dlv 開啟 debug 的幾種方式
1.5.1 開啟 debug 時的一些可選參數(shù)
- 參數(shù)傳遞,debug 時傳遞參數(shù)需要通過--(其主要作用為分割命令和參數(shù))。如下:
[]
也可在 restart 時指定參數(shù),如下:
[]Type 'help' for list of commands.(dlv) r -cfg config.yaml // 此時不需要--Process restarted with PID 29548(dlv) c
- --headless, --listen 啟動 debug server、設(shè)定 debug server 監(jiān)聽地址,--headless、--listen 通常會與 dlv attach 聯(lián)合用于遠(yuǎn)程調(diào)試。
目前123的測試環(huán)境(測試、pre)與 devCloud 網(wǎng)絡(luò)是連通的,能夠進(jìn)行遠(yuǎn)程調(diào)試。其操作步驟,參見1.5.3 節(jié)
1.5.2 開啟 debug 的方式
需要注意本節(jié)中 flags 參數(shù)指的是1.5.1中參數(shù)。
-
#### dlv debug、dlv exec
默認(rèn) dlv debug 會查找當(dāng)前目錄中的 main.main,編譯、并開啟 debug。其格式如下:
dlv debug [package] [flags]
需要注意,可以通過 package 指定要 debug 的包
與之對應(yīng),dlv exec 需要指定一個可執(zhí)行程序。如下:
dlv exec <path/to/binary>
-
#### dlv attach
dlv 提供了進(jìn)程的 attach 支持,使用 dlv 可以對運(yùn)行中的進(jìn)程進(jìn)行調(diào)試??紤]到123測試環(huán)境和 devCloud 的網(wǎng)絡(luò)已經(jīng)打通,使用該機(jī)制可以非常方便的進(jìn)行遠(yuǎn)程調(diào)試,如下:
dlv attach pid
在123和 devCloud 間進(jìn)行遠(yuǎn)程調(diào)試步驟:
-
123開啟 debug server:
dlv attach pid - devCloud 鏈接到遠(yuǎn)程服務(wù)器進(jìn)行調(diào)試:
root /mnt/code/content_service]# dlv connect xx.xx.xx.xx:2346 // 鏈接到遠(yuǎn)程debug serverType 'help' for list of commands.(dlv) funcs GetItemV2git.code.oa.com/ForwardIndex/content-service/api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2git.code.oa.com/ForwardIndex/content-service/api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2Data......
需注意,devCloud 只能連接到123的測試、預(yù)發(fā)布環(huán)境,無法連接到正式環(huán)境。辦公網(wǎng)無法連接到123環(huán)境
-
#### dlv core
dlv 也對 coredump 調(diào)試提供了支持,使用 dlv core 可以方便的對線上 coredump 進(jìn)行調(diào)試、分析,發(fā)現(xiàn)相關(guān)問題。其使用方法如下:
dlv core <executable> <core> [flags]
// executeable 即為可執(zhí)行程序 core為相關(guān)coredump文件
Go 中使用 dlv 進(jìn)行 core dump 調(diào)試一般步驟:
1.修改操作系統(tǒng)限制, 略。目前相關(guān)鏡像都已經(jīng)進(jìn)行了配置;
2.設(shè)置 GOTRACEBACK,123為服務(wù)配置環(huán)境變量 GOTRACEBACK=crash,如下:

需要注意,如果此時啟用了 recovery 相關(guān)機(jī)制,協(xié)程內(nèi)的 panic 并不會導(dǎo)致 coredump,此時可在 /usr/local/app/serverHistory.log 中查看相關(guān)原因
需要注意,默認(rèn) coredump 文件會在可執(zhí)行程序所在目錄,如果需改變位置請修改 /proc/sys/kernel/core_pattern
3.通過 bt ,查看調(diào)用堆棧,定位可能的問題發(fā)生的位置。
[root@b0f67f8932a2 /mnt/code/gotest667]# dlv core main core.28183Type 'help' for list of commands.(dlv) bt0 0x0000000000460fe1 in runtime.raiseat /usr/local/go1.20/go/src/runtime/sys_linux_amd64.s:154.........10 0x0000000000497358 in gotest667/aa.(*Xxx).TestC // 離panic最近的用戶邏輯是最有可能導(dǎo)致panic的位置at ./aa/aa.go:2611 0x0000000000497358 in main.mainat ./main.go:4412 0x0000000000434507 in runtime.mainat /usr/local/go1.20/go/src/runtime/proc.go:25013 0x000000000045f6c1 in runtime.goexitat /usr/local/go1.20/go/src/runtime/asm_amd64.s:1598
4.通過 frame 命令,移動到相關(guān)棧幀,通過 args -v、locals -v 等收集問題發(fā)生時各種數(shù)據(jù)。
(dlv) frame 10 // 移動到可能的函數(shù)棧幀> runtime.raise() /usr/local/go1.20/go/src/runtime/sys_linux_amd64.s:154 (PC: 0x460fe1)Warning: debugging optimized functionFrame 10: ./aa/aa.go:26 (PC: 497358)21:22: func (x *Xxx) TestC(i int) {23: x.TestA(i)24: j := 025: for ; j < i; j++ {=> 26: y[j] = i27: }28: }29:30: func (x *Xxx) TestE() {31: x.i++(dlv) locals -v // 查看局部變量j = (unreadable could not find loclist entry at 0x7076 for address 0x497358)(dlv) args -v // 查看函數(shù)入?yún)?/span>i = (unreadable could not find loclist entry at 0x6ff1 for address 0x497358)(dlv) vars -v gotest667/aa.y // 查看全局變量gotest667/aa.y = []int len: 3, cap: 3, [4,4,4]
5.根據(jù)已有相關(guān)數(shù)據(jù),對問題進(jìn)行定位、分析。
02
dlv 中相關(guān)命令
2.1 dlv 的配置
dlv 配置默認(rèn)包含2種方式,基于命令、基于配置文件。需要注意,基于命令的修改是 session 級的,重啟 debug 相關(guān)配置會丟失。
linux 下 dlv 配置文件默認(rèn)位于$HOME/.config/dlv/config.yml,其常用配置項包含:
substitute-path: // 用于配置代碼查找、替換路徑。比如mac發(fā)布代碼,devcloud進(jìn)行調(diào)試- {from: $HOME/go/src/content-service, to: /mnt/code/content_service}max-string-len: 99999 // 打印string的最大長度,如果調(diào)試時候無法打印string的全部值,可修改此變量max-array-values: 99999 // 打印array等的最大長度aliases:command: ["alias"] // 為命令command設(shè)置別名alias,如display: ["dis"]
對應(yīng)的相應(yīng)命令包括:
config -list // 列出所有配置項config -save // 保存配置信息至文件config max-string-len 9999 // 設(shè)置打印最大string長度為9999config max-array-values 9999 // 設(shè)置打印最大數(shù)組長度為9999config substitute-path <from> <to> // 設(shè)置代碼查找、替換路徑。需要注意此時不執(zhí)行save命令,相關(guān)配置不會更新
以 substitute-path 為例(mac 編譯發(fā)布、devCloud 調(diào)試,二者路徑不同),配置前后對比如下:
配置前:
(dlv) b api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2Breakpoint 1 set at 0x183d006 for api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2() api/service/iteminfo/itemv2.go:51(dlv) c> api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2() api/service/iteminfo/itemv2.go:51 (hits goroutine(6999):1 total:1) (PC: 0x183d006) // 此時命中斷點未打印任何信息Warning: debugging optimized function
配置后:
(dlv) b api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2Breakpoint 1 set at 0x183d006 for api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2() ./api/service/iteminfo/itemv2.go:51(dlv) c> api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2() ./api/service/iteminfo/itemv2.go:51 (hits goroutine(5462):1 total:1) (PC: 0x183d006) // 命中斷點,打印了相關(guān)調(diào)試信息Warning: debugging optimized function46: rsp.Code = 047: return nil48: }49:50: // GetItemV2 _=> 51: func (i *ItemInfoServiceImpl) GetItemV2(ctx context.Context,52: req *iteminfopb.GetItemV2Request, rsp *iteminfopb.GetItemV2Reply) (err error) {53:54: err = i.getItemV2Impl(ctx, req, rsp)55: return err56: }(dlv)
2.2 運(yùn)行相關(guān)命令
2.2.1 restart、exit
restart、exit 分 別用于重啟進(jìn)程、退出調(diào)試,其命令格式分別如下:
restart // 重啟被調(diào)試進(jìn)程,此時并未開始debug,還需要運(yùn)行continue命令exit // 退出調(diào)試
需注意,restart 只會重啟進(jìn)程,并不會真正開始調(diào)試。啟動調(diào)試需要用 continue
其相關(guān)別名(簡寫)如下:
| 命令 | 簡寫 | 說明 |
| restart | r | 重啟調(diào)試進(jìn)程 |
| exit | q | 退出調(diào)試 |
2.2.2 continue
continue 會持續(xù)運(yùn)行直到斷點、位置或者程序結(jié)束,其命令格式如下:
continue [locspec]
需要注意,continue 命令支持直接運(yùn)行到文件中某行、或者某個函數(shù)
continue 命令的別名為: c。
其用法如下:
(dlv) c main.go:48 // 運(yùn)行調(diào)試,直至main.go:48Breakpoint 1 set at 0x4bf4a7 for main.main() ./main.go:48> main.main() ./main.go:48 (hits goroutine(1):1 total:1) (PC: 0x4bf4a7)43: a = uint8(i)44: x.TestC(i)45: }46: }47:=> 48: x.TestB() // 此時會在main.go:48行暫停運(yùn)行.........
2.2.3 next、step、step out
| 命令 | 簡寫 | 說明 |
| next | n | 單步調(diào)試 |
| step | s | 單步調(diào)試(進(jìn)入調(diào)試函數(shù)) |
| step out | so | 單步調(diào)試(退出調(diào)試函數(shù)) |
其效果略。
2.3 斷點相關(guān)命令
2.3.1 dlv 中斷點
dlv 中斷點包含以下幾種:
-
trace 斷點
trace 斷點用于在命中斷點時,打印相關(guān)提示信息。在查看實現(xiàn)、或者調(diào)用路徑時比較有用。
trace 斷點命令格式如下:
trace [name] [locspec]
// name為斷點名// 省略所有參數(shù),默認(rèn)會在當(dāng)前行添加trace斷點
其效果如下:
(dlv) trace test api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2 // 設(shè)置名為test的trace斷點Tracepoint test set at 0x1843446 for api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2() ./api/service/iteminfo/itemv2.go:51(dlv) c> goroutine(2935359): [test] api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2(("*api/service/iteminfo.ItemInfoServiceImpl")(0x3da4940), context.Context(*context.valueCtx) 0xbeef000000000108, ("*fwd_content_service_iteminfo.GetItemV2Request")(0xc02ebbf5c0), ("*fwd_content_service_iteminfo.GetItemV2Reply")(0xc06eae7b30)) // trace斷點命中,打印相關(guān)信息,未暫停程序運(yùn)行>> goroutine(2935359): => ((unreadable empty OP stack))
需要注意,trace斷點只會打印相關(guān)信息,不會暫停程序的運(yùn)行
-
break 斷點
break 斷點(即 breakpoint,其簡寫為 b)會在命中斷點時,暫停程序的運(yùn)行。其命令格式如下:
break [name] [locspec]
// name為斷點名
其效果如下:
(dlv) b test api/service/iteminfo/itemv2.go:51 // 新增名為test的斷點位于itemv2.go:51Breakpoint test set at 0x1843446 for api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2() ./api/service/iteminfo/itemv2.go:51(dlv) c> [test] api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2() ./api/service/iteminfo/itemv2.go:51 (hits goroutine(2941592):1 total:1) (PC: 0x1843446)Warning: debugging optimized function46: rsp.Code = 047: return nil48: }49:50: // GetItemV2 _=> 51: func (i *ItemInfoServiceImpl) GetItemV2(ctx context.Context,52: req *iteminfopb.GetItemV2Request, rsp *iteminfopb.GetItemV2Reply) (err error) { // 命中斷點,暫停程序運(yùn)行.......
需要注意,無論是 trace 斷點、break 斷點,都可在添加斷點時候,為斷點命名
-
watch 斷點
watch 斷點,會在被監(jiān)視對象(或者地址),在發(fā)生讀、寫時暫停程序運(yùn)行并打印相關(guān)信息,其命令如下:
watch [-r|-w|-rw] <expr>
-r stops when the memory location is read-w stops when the memory location is written-rw stops when the memory location is read or written
// expr為1.4中表達(dá)式
其執(zhí)行效果如下:
(dlv) c main.go:41Breakpoint 1 set at 0x4bf48a for main.main() ./main.go:41default> main.main() ./main.go:41 (hits goroutine(1):1 total:1) (PC: 0x4bf48a)36: x := aa.Xxx{}37: t := true38:39: time.Sleep(1 * time.Second)40:=> 41: if t {42: for i := 0; i < 10; i++ {43: a = uint8(i)44: x.TestC(i)45: }46: }(dlv) p a(*uint8)(0x59d241)(dlv) watch -rw * (*uint8) (0x59d241) // 添加watch pointWatchpoint * (*uint8) (0x59d241) set at 0x59d241(dlv) c> watchpoint on [* (*uint8) (0x59d241)] main.main() ./main.go:44 (hits goroutine(1):1 total:1) (PC: 0x4bf70f)39: time.Sleep(1 * time.Second)40:41: if t {42: for i := 0; i < 10; i++ {43: a = uint8(i)=> 44: x.TestC(i) // 在a被第一次寫時,程序暫停運(yùn)行45: }46: }.......
2.3.2 斷點相關(guān)命令
-
condition 命令
condition(其簡寫為 cond)命令用于只在條件滿足后暫停程序執(zhí)行,比如要調(diào)試的 slice 或者 map 含有較多元素,我們只關(guān)心其中某個。其命令如下:
condition <breakpoint name or id> <boolean expression>
// <breakpoint name or id> 為已經(jīng)存在的breakpoint名或者id// dlv會在<boolean expression>為true時暫停程序運(yùn)行
需要注意,cond 命令必須作用于已存在的斷點上
如下:
(dlv) b main.go:43 // 在main.go:43添加斷點,其id為1Breakpoint 1 set at 0x4bf704 for main.main() ./main.go:43(dlv) cond 1 i==3 // 斷點1在i==3時暫停(dlv) c> main.main() ./main.go:43 (hits goroutine(1):1 total:1) (PC: 0x4bf704)38:39: time.Sleep(1 * time.Second)40:41: if t {42: for i := 0; i < 10; i++ {=> 43: a = uint8(i) // i==3時暫停程序執(zhí)行44: x.TestC(i)45: }46: }47:48: x.TestB()(dlv) p i3.........
-
on 命令
on 命令用于在命中斷點時執(zhí)行一些操作,目前支持的操作包括:print、stack、goroutine、trace、cond。
其命令格式如下:
on <name or id> <command>
需要注意,on 命令也只能作用于已經(jīng)存在的斷點
需要注意,on 命令是可以用于 trace 斷點的
以命中斷點,打印某些變量值為例,其具體操作如下:
(dlv) b main.go:43 // 添加斷點,其id為1Breakpoint 1 set at 0x4bf704 for main.main() ./main.go:43(dlv) on 1 p a // 命中斷點1時 打印a的值(dlv) c> main.main() ./main.go:43 (hits goroutine(1):1 total:1) (PC: 0x4bf704)a: 10 // 先打印a的值38:39: time.Sleep(1 * time.Second)40:41: if t {42: for i := 0; i < 10; i++ {=> 43: a = uint8(i)44: x.TestC(i)45: }46: }47:48: x.TestB()(dlv)
-
其他
其他斷點相關(guān)命令如下:
| 命令 | 簡寫 | 使用方法 | 說明 |
| breakpoints | bp | bp | 打印當(dāng)前所有斷點(所有類型)信息 |
| toggle | - | toggle <breakpoint name or id> | 禁用breakpoint |
| clear | - | clear <breakpoint name or id> | 刪除breakpoint |
| clearall | - | clearall | 刪除所有(所有類型)斷點 |
2.4 變量、內(nèi)存查看相關(guān)
2.4.1 print
print 用于打印變量或表達(dá)式的值,其用法如下:
print [%format] <expr>
需要注意 print 支持 fmt 系列的選項,常用包含:%f %x %v %T 等
其執(zhí)行效果參見1.4
2.4.2 args、locals、vars
args 命令用于打印函數(shù)的入?yún)?,其用法如?/span>
args -v [<regex>] // args命令在-v參數(shù)下會打印更詳細(xì)數(shù)據(jù)
locals 用于打印局部變量,其用法如下:
locals [-v] [<regex>] // -v用于打印更詳細(xì)信息
vars 用于打印包級變量,其用法如下:
vars [-v] [<regex>] // -v參數(shù)能夠打印更詳細(xì)數(shù)據(jù)
需要注意,vars 不只能打印自定義的包級別變量,也可以打印內(nèi)置的包級變量,如 runtime.curg 等
以 args 為例,其輸出如下:
(dlv) argsi = ("*api/service/iteminfo.ItemInfoServiceImpl")(0x3da4940)ctx = context.Context(*context.valueCtx) 0xbeef000000000108req = ("*fwd_content_service_iteminfo.GetItemV2Request")(0xc03b80aa80)rsp = ("*fwd_content_service_iteminfo.GetItemV2Reply")(0xc03bf10960)err = (unreadable empty OP stack)(dlv) args -vi = ("*api/service/iteminfo.ItemInfoServiceImpl")(0x3da4940)*api/service/iteminfo.ItemInfoServiceImpl {}ctx = context.Context(*context.valueCtx) *{Context: context.Context(*context.valueCtx) *{Context: context.Context(*context.valueCtx) ...,key: interface {}(go.opentelemetry.io/otel/trace.traceContextKeyType) *(*interface {})(0xc0cf8d6e20),val: interface {}(go.opentelemetry.io/otel/trace.nonRecordingSpan) *(*interface {})(0xc0cf8d6e30),},key: interface {}(go.opentelemetry.io/otel/trace.traceContextKeyType) currentSpanKey (0),val: interface {}(*go.opentelemetry.io/otel/sdk/trace.recordingSpan) *{mu: (*sync.Mutex)(0xc03b1a0f00),.........
需要注意,args、locals、vars 等通常與 dlv core 一起用于問題的定位與分析
以 args 為例,其輸出如下:
2.4.3 display
display -a [%format] <expr> // 添加需要展示的表達(dá)式或者變量display -d <number> // 刪除相關(guān)display信息
// %format為fmt系列格式化字符串
其執(zhí)行效果如下:
(dlv) display -a %v tools.isProdEnv // 每次程序暫停打印tool.isProdEnv值, 其id為00: pkg/tools.isProdEnv = true(dlv) c> api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2() ./api/service/iteminfo/itemv2.go:51 (hits goroutine(2945141):1 total:1) (PC: 0x1843446)Warning: debugging optimized function46: rsp.Code = 047: return nil48: }49:50: // GetItemV2 _=> 51: func (i *ItemInfoServiceImpl) GetItemV2(ctx context.Context,52: req *iteminfopb.GetItemV2Request, rsp *iteminfopb.GetItemV2Reply) (err error) {53:54: err = i.getItemV2Impl(ctx, req, rsp)55: return err56: }0: pkg/tools.isProdEnv = true // 打印pkg/tools.isProdEnv(dlv) display -d 0 // 移除相關(guān)display機(jī)制
display 相關(guān)機(jī)制類似于 on 命令的 print,但 on 命令比 print 支持更多功能
2.4.4 set
set <variable> = <value>
需要注意,如果相關(guān)變量的 set 是可以影響函數(shù)的執(zhí)行路徑的,但 set 命令需要提前執(zhí)行
(dlv) cdefault> main.main() ./main.go:39 (hits goroutine(1):1 total:1) (PC: 0x4bf480)34: a = 1035:36: x := aa.Xxx{}37: t := false38:=> 39: time.Sleep(1 * time.Second)40:41: if t {42: for i := 0; i < 10; i++ {43: a = uint8(i)44: x.TestC(i)(dlv) p t // t的值此時為falsefalse(dlv) set t=true // 設(shè)置t的值為true(dlv) c main.go:42> main.main() ./main.go:42 (PC: 0x4bf493)37: t := false38:39: time.Sleep(1 * time.Second)40:41: if t {=> 42: for i := 0; i < 10; i++ { // 此時已經(jīng)可以進(jìn)入到if43: a = uint8(i)44: x.TestC(i)45: }46: }47:(dlv)
2.4.5 whatis
whatis <expr>
如下:
(dlv) c main.go:30Breakpoint 1 set at 0x4bf46f for main.main() ./main.go:30> main.main() ./main.go:30 (hits goroutine(1):1 total:1) (PC: 0x4bf46f)25:26: func main() {27: var animial Animial = Cat{s: "test"}28: animial.bark()29:=> 30: flag.StringVar(cfg, "cfg", "default", "-cfg")31: flag.Parse()32:33: fmt.Println(cfg)34: a = 1035:(dlv) whatis animial // 打印animial類型main.AnimialConcrete type: *main.Cat(dlv) whatis animial.s // 打印animial.s類型string
2.4.6 examinemem
examinemem [-fmt <format>] [-count <count>] [-size <size>] <address>
其中 fmt 支持 bin、hex、oct,-count 和 -size 分別用于指定要打印的次數(shù)、空間大小,用法如下:
(dlv) c main.go:43Breakpoint 1 set at 0x4bf704 for main.main() ./main.go:43default> main.main() ./main.go:43 (hits goroutine(1):1 total:1) (PC: 0x4bf704)38:39: time.Sleep(1 * time.Second)40:41: if t {42: for i := 0; i < 10; i++ {=> 43: a = uint8(i)44: x.TestC(i)45: }46: }47:48: x.TestB()(dlv) p a10(dlv) p &a(*uint8)(0x59d241)(dlv) x -fmt hex -count 2 -size 1 0x59d241 // 以16機(jī)制打印0x59d241地址開始內(nèi)容2次,每次1byte0x59d241: 0x0a 0x01(dlv) x -fmt hex -count 1 -size 2 0x59d241 // 以16機(jī)制打印0x59d241地址開始內(nèi)容1次,每次2byte0x59d241: 0x010a(dlv)
2.5 堆棧相關(guān)
2.5.1 stack
stack 命令用于打印調(diào)用堆棧,其簡寫命令為 bt,用法如下:
[] stack [<depth>] [-full]
// goroutine <n> 指定要打印堆棧的協(xié)程// depth 打印0-depth層堆棧// -full 打印相關(guān)入?yún)?、局部變?/span>
其用法如下:
(dlv) grs // 列出所有協(xié)程.......Goroutine 19 - User: /root/go/pkg/mod/github.com/panjf2000/ants/v2@v2.4.6/pool.go:69 github.com/panjf2000/ants/v2.(*Pool).purgePeriodically (0x95552b) [chan receive]......(dlv) gr 19 bt 3 -full // 打印協(xié)程19的前0-3個函數(shù)棧幀信息0 0x000000000043de36 in runtime.goparkat /usr/local/go1.20/go/src/runtime/proc.go:382.......
1 0x0000000000408cbd in runtime.chanrecvat /usr/local/go1.20/go/src/runtime/chan.go:583c = (*runtime.hchan)(0xc000168060)ep = unsafe.Pointer(0xc00013a798)block = trueselected = (unreadable empty OP stack)received = (unreadable empty OP stack)~R0 = (unreadable empty OP stack)t0 = 0gp = (*runtime.g)(0xc000207380)mysg = (*runtime.sudog)(0xc0001680c0)
2 0x00000000004087f8 in runtime.chanrecv2at /usr/local/go1.20/go/src/runtime/chan.go:447c = (unreadable could not find loclist entry at 0x10dde for address 0x4087f8)elem = (unreadable could not find loclist entry at 0x10e11 for address 0x4087f8)received = (unreadable empty OP stack)
3 0x000000000095552b in github.com/panjf2000/ants/v2.(*Pool).purgePeriodicallyat /root/go/pkg/mod/github.com/panjf2000/ants/v2@v2.4.6/pool.go:69p = ("*github.com/panjf2000/ants/v2.Pool")(0xc0002cdb20)heartbeat = (unreadable could not find loclist entry at 0x7668bf for address 0x95552b)
2.5.2 frame、up、down
frame、up、down 都用于移動棧幀并執(zhí)行命令,frame 用于移動到指定棧幀、up 和 down 分別用于向上和向下移動棧幀。其用法如下:
frame <m> command // 移動到棧幀m,執(zhí)行commandup <m> command // 向上移動m個棧幀,執(zhí)行commanddown <m> command // 向下移動m個棧幀,執(zhí)行command
其執(zhí)行效果如下圖:
bt........6 0x0000000001cffc5d in main.glob..func1at ./main.go:99.......frame 6 args -v // 打印棧幀6的入?yún)?/span>ctx = context.Context(*context.valueCtx) *{...}req = interface {}(*git.woa.com/trpcprotocol/forwardindex/fwd_content_service_iteminfo.GetItemV2Request) *{state: google.golang.org/protobuf/internal/impl.MessageState {NoUnkeyedLiterals: google.golang.org/protobuf/internal/pragma.NoUnkeyedLiterals {},DoNotCompare: google.golang.org/protobuf/internal/pragma.DoNotCompare [],DoNotCopy: google.golang.org/protobuf/internal/pragma.DoNotCopy [],atomicMessageInfo: *(*"google.golang.org/protobuf/internal/impl.MessageInfo")(0xc001323fb0),},sizeCache: 0,unknownFields: []uint8 len: 0, cap: 0, nil。。。。。。。。。
2.6 查詢相關(guān)命令
2.6.1 常用查詢命令
注意以下命令都支持正則匹配。
| 命令 | 簡寫 | 說明 |
| funcs | - | 打印所有函數(shù)信息 |
| goroutines | grs | 打印所有協(xié)程信息 |
| threads | - | 列出所有線程信息 |
| sources | - | 列出所有源文件 |
| types | - | 列出所有類型 |
2.6.2 groutine
groutine 命令用于打印當(dāng)前協(xié)程信息、或者切換到指定協(xié)程執(zhí)行命令,其使用方式如下:
goroutine [<id>] [<command>] // 切換到相應(yīng)groutine,執(zhí)行command
(dlv) gr 6 bt 2 -full // 打印id為6的協(xié)程的堆棧信息0 0x000000000043de36 in runtime.gopark.......
1 0x000000000044e51e in runtime.selectgo........
2 0x000000000118dc87 in github.com/RussellLuo/timingwheel.(*TimingWheel).Start.func2at /root/go/pkg/mod/github.com/!russell!luo/timingwheel.0.0-20191022104228-f534fd34a762/timingwheel.go:145
2.6.3 list
list 命令用于打印源碼,list 會打印指定位置上下5行源碼,其用法如下:
[goroutine <n>] [frame <m>] list [<locspec>] // 查看到某個goroutine的某個函數(shù)調(diào)用棧幀的源碼
(dlv) gr 6 frame 1 listGoroutine 6 frame 1 at /usr/local/go1.20/go/src/runtime/select.go:327 (PC: 0x44e51e)322: // Signal to anyone trying to shrink our stack that we're about323: // to park on a channel. The window between when this G's status324: // changes and when we set gp.activeStackChans is not safe for325: // stack shrinking.326: gp.parkingOnChan.Store(true)=> 327: gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1)328: gp.activeStackChans = false329:330: sellock(scases, lockorder)331:332: gp.selectDone.Store(0)
03
Goland 的支持
Goland 對 dlv 相關(guān)機(jī)制提供了支持。
Goland 中 debug 相關(guān)機(jī)制都位于 debug 面板,可通過 view->tool windows->debug 打開。debug 面板默認(rèn)包含2部分,分別用于展示協(xié)程信息、堆棧信息、數(shù)據(jù)信息,如下:

3.1 斷點相關(guān)
Goland 中斷點相關(guān)支持,主要是 evaluate and log、condition 相關(guān)機(jī)制,具體如下:
-
evaluate and log,調(diào)試過程中 console 打印相關(guān)表達(dá)式或變量的值;
-
condition,只有在命中斷點 && 相關(guān)條件成立時才會暫停程序運(yùn)行。
在 Goland 中添加斷點后,只需要在相關(guān)斷點右鍵并單擊 More,在彈出對話框中既可使用相關(guān)機(jī)制:

3.2 變量、內(nèi)存查看相關(guān)
Goland 對 debug 中變量、內(nèi)存查看的支持,主要是 evalute expression、watch、view as、set 相關(guān)機(jī)制,在 debug 面板右鍵即可添加相關(guān)機(jī)制,如下:

3.3 協(xié)程、堆棧相關(guān)
Goland 對 debug 中協(xié)程、堆棧等也提供了支持,默認(rèn)展示的是當(dāng)前協(xié)程的調(diào)用堆棧,可通過下拉列表進(jìn)行選擇,如下:
-End- 原創(chuàng)作者|張玉新
推薦閱讀:
Go區(qū)不大,創(chuàng)造神話,科目三殺進(jìn)來了
想要了解Go更多內(nèi)容,歡迎掃描下方??關(guān)注公眾號, 回復(fù)關(guān)鍵詞 [實戰(zhàn)群] ,就有機(jī)會進(jìn)群和我們進(jìn)行交流
分享、在看與點贊Go
