<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          別再用GDB了,一文掌握Go最好用的調(diào)試器Delve

          共 42548字,需瀏覽 86分鐘

           ·

          2024-03-22 07:30




          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 = 0
                  
                  
                        48:         return nil
                  
                  
                        49: }
                  
                  
                        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為2
                  
                  
                    Breakpoint 2 set at 0x22e0f73 for iteminfo.GetItemV2() ./api/iteminfo/itemv2.go:55
                  
                  
                    (dlv) b +5  // 命中斷點后,再次在本文件的52+5行添加斷點,其id為3
                  
                  
                    Breakpoint 3 set at 0x22e0f78 for iteminfo..GetItemV2() ./api/iteminfo/itemv2.go:57
                  
                  
                    (dlv) b -5  // 命中斷點后,再次在本文件的52-5行添加斷點,其id為4
                  
                  
                    Breakpoint 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:48
                  
                  
                    Breakpoint 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 int
                  
                  
                        52:         if reflect.TypeOf(intData).AssignableTo(reflect.TypeOf(mp).Elem()) {
                  
                  
                        53:                 log.Println("no")
                  
                  
                    (dlv) p a
                  
                  
                    
                      9
                    
                  
                  
                    (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ù))。如下:

                  
                    [root@b0f67f8932a2 /mnt/code/gotest667]# dlv exec main -- -cfg config.yaml // 為cfg傳遞參數(shù)config.yaml
                  
                

          也可在 restart 時指定參數(shù),如下:
                  
                    [root@b0f67f8932a2 /mnt/code/gotest667]# dlv exec main 
                  
                  
                    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> [flags]
                  
                

          • #### 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 [flags]
                  
                

          在123和 devCloud 間進(jìn)行遠(yuǎn)程調(diào)試步驟:


          1. 123開啟 debug server:


                        
                          dlv attach pid [flags]
                        
                      

          2. devCloud 鏈接到遠(yuǎn)程服務(wù)器進(jìn)行調(diào)試:

                  
                    root@b0f67f8932a2 /mnt/code/content_service]# dlv connect xx.xx.xx.xx:2346 // 鏈接到遠(yuǎn)程debug server
                  
                  
                    Type 'help' for list of commands.
                  
                  
                    (dlv) funcs GetItemV2
                  
                  
                    git.code.oa.com/ForwardIndex/content-service/api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2
                  
                  
                    git.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,如下:

          46d08a87a4edaa0c1d46a11a4315f65a.webp

          需要注意,如果此時啟用了 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.28183 
                  
                  
                    Type 'help' for list of commands.
                  
                  
                    (dlv) bt 
                  
                  
                     0  0x0000000000460fe1 in runtime.raise
                  
                  
                        at /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:26
                  
                  
                    11  0x0000000000497358 in main.main
                  
                  
                        at ./main.go:44
                  
                  
                    12  0x0000000000434507 in runtime.main
                  
                  
                        at /usr/local/go1.20/go/src/runtime/proc.go:250
                  
                  
                    13  0x000000000045f6c1 in runtime.goexit
                  
                  
                        at /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 function
                  
                  
                    Frame 10: ./aa/aa.go:26 (PC: 497358)
                  
                  
                        21:
                  
                  
                        22: func (x *Xxx) TestC(i int) {
                  
                  
                        23:         x.TestA(i)
                  
                  
                        24:         j := 0
                  
                  
                        25:         for ; j < i; j++ {
                  
                  
                    =>  26:                 y[j] = i
                  
                  
                        27:         }
                  
                  
                        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長度為9999
                  
                  
                    config max-array-values 9999    // 設(shè)置打印最大數(shù)組長度為9999
                  
                  
                    config substitute-path <from> <to>  // 設(shè)置代碼查找、替換路徑。需要注意此時不執(zhí)行save命令,相關(guān)配置不會更新
                  
                

          以 substitute-path 為例(mac 編譯發(fā)布、devCloud 調(diào)試,二者路徑不同),配置前后對比如下:


          配置前:


                  
                    (dlv)  b api/service/iteminfo.(*ItemInfoServiceImpl).GetItemV2
                  
                  
                    Breakpoint 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).GetItemV2
                  
                  
                    Breakpoint 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 function
                  
                  
                        46:         rsp.Code = 0
                  
                  
                        47:         return nil
                  
                  
                        48: }
                  
                  
                        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 err
                  
                  
                        56: }
                  
                  
                    (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:48
                  
                  
                    Breakpoint 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 命令用于單步執(zhí)行,在函數(shù)調(diào)用時,不會進(jìn)入函數(shù)內(nèi)部進(jìn)行調(diào)試。相應(yīng)的 step 命令能夠進(jìn)入到被調(diào)函數(shù)進(jìn)行調(diào)試、step out 用于跳出被調(diào)試的函數(shù)。如下:
          命令 簡寫 說明
          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:51
                  
                  
                    Breakpoint 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 function
                  
                  
                        46:         rsp.Code = 0
                  
                  
                        47:         return nil
                  
                  
                        48: }
                  
                  
                        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:41
                  
                  
                    Breakpoint 1 set at 0x4bf48a for main.main() ./main.go:41
                  
                  
                    
                      default
                    
                  
                  
                    > main.main() ./main.go:41 (hits goroutine(1):1 total:1) (PC: 0x4bf48a)
                  
                  
                        36:         x := aa.Xxx{}
                  
                  
                        37:         t := true
                  
                  
                        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:         }
                  
                  
                    (dlv) p a
                  
                  
                    (*uint8)(0x59d241)
                  
                  
                    (dlv) watch -rw * (*uint8) (0x59d241)       // 添加watch point
                  
                  
                    Watchpoint * (*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為1
                  
                  
                    Breakpoint 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 i
                  
                  
                    
                      3
                    
                  
                  
                    .........
                  
                

          • on 命令


          on 命令用于在命中斷點時執(zhí)行一些操作,目前支持的操作包括:print、stack、goroutine、trace、cond。


          其命令格式如下:


                  
                    on <name or id> <command>
                  
                

          需要注意,on 命令也只能作用于已經(jīng)存在的斷點

          需要注意,on 命令是可以用于 trace 斷點的

          以命中斷點,打印某些變量值為例,其具體操作如下:


                  
                    (dlv) b main.go:43 // 添加斷點,其id為1
                  
                  
                    Breakpoint 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) args  
                  
                  
                    i = ("*api/service/iteminfo.ItemInfoServiceImpl")(0x3da4940)
                  
                  
                    ctx = context.Context(*context.valueCtx) 0xbeef000000000108
                  
                  
                    req = ("*fwd_content_service_iteminfo.GetItemV2Request")(0xc03b80aa80)
                  
                  
                    rsp = ("*fwd_content_service_iteminfo.GetItemV2Reply")(0xc03bf10960)
                  
                  
                    err = (unreadable empty OP stack)
                  
                  
                    (dlv) args -v
                  
                  
                    i = ("*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 命令在每次程序暫停都會 打印 相關(guān)表達(dá)式的值,如果想要在單步調(diào)試中查看變量的變化情況,可以使用 display 命令。其用法如下:
                  
                    display -a [%format] <expr>  // 添加需要展示的表達(dá)式或者變量  
                  
                  
                    display -d <number> // 刪除相關(guān)display信息
                  
                  
                    
                      
          // %format為fmt系列格式化字符串

          其執(zhí)行效果如下:
                  
                    (dlv) display -a %v tools.isProdEnv // 每次程序暫停打印tool.isProdEnv值, 其id為0
                  
                  
                    0: 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 function
                  
                  
                        46:         rsp.Code = 0
                  
                  
                        47:         return nil
                  
                  
                        48: }
                  
                  
                        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 err
                  
                  
                        56: }
                  
                  
                    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 命令用于在運(yùn)行期間修改變量的值,其用法如下:
                  
                    set <variable> = <value>
                  
                

          需要注意,如果相關(guān)變量的 set 是可以影響函數(shù)的執(zhí)行路徑的,但 set 命令需要提前執(zhí)行

                  
                    (dlv) c
                  
                  
                    
                      default
                    
                  
                  
                    > main.main() ./main.go:39 (hits goroutine(1):1 total:1) (PC: 0x4bf480)
                  
                  
                        34:         a = 10
                  
                  
                        35:
                  
                  
                        36:         x := aa.Xxx{}
                  
                  
                        37:         t := false
                  
                  
                        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)
                  
                  
                    (dlv) p t   // t的值此時為false
                  
                  
                    
                      false
                    
                  
                  
                    (dlv) set t=true // 設(shè)置t的值為true
                  
                  
                    (dlv) c main.go:42
                  
                  
                    > main.main() ./main.go:42 (PC: 0x4bf493)
                  
                  
                        37:         t := false
                  
                  
                        38:
                  
                  
                        39:         time.Sleep(1 * time.Second)
                  
                  
                        40:
                  
                  
                        41:         if t {
                  
                  
                    =>  42:                 for i := 0; i < 10; i++ { // 此時已經(jīng)可以進(jìn)入到if
                  
                  
                        43:                         a = uint8(i)
                  
                  
                        44:                         x.TestC(i)
                  
                  
                        45:                 }
                  
                  
                        46:         }
                  
                  
                        47:
                  
                  
                    (dlv)
                  
                

                2.4.5 whatis


          whatis 用于打印變量或者表達(dá)式類型,其用法如下:
                  
                    whatis <expr>
                  
                

          如下:
                  
                    (dlv) c main.go:30
                  
                  
                    Breakpoint 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 = 10
                  
                  
                        35:
                  
                  
                    (dlv) whatis animial  // 打印animial類型
                  
                  
                    main.Animial
                  
                  
                    Concrete type: *main.Cat
                  
                  
                    (dlv) whatis animial.s // 打印animial.s類型
                  
                  
                    
                      string
                    
                  
                

                2.4.6 examinemem


          examinemem 用于打印指定地址的信息,其簡寫為 x,其用法如下:
                  
                    examinemem [-fmt <format>] [-count <count>] [-size <size>] <address>
                  
                

          其中 fmt 支持 bin、hex、oct,-count 和 -size 分別用于指定要打印的次數(shù)、空間大小,用法如下:
                  
                    (dlv) c main.go:43
                  
                  
                    Breakpoint 1 set at 0x4bf704 for main.main() ./main.go:43
                  
                  
                    
                      default
                    
                  
                  
                    > 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 a
                  
                  
                    
                      10
                    
                  
                  
                    (dlv) p &a
                  
                  
                    (*uint8)(0x59d241)
                  
                  
                    (dlv) x -fmt hex -count 2 -size 1 0x59d241  // 以16機(jī)制打印0x59d241地址開始內(nèi)容2次,每次1byte
                  
                  
                    0x59d241:   0x0a   0x01   
                  
                  
                    (dlv) x -fmt hex -count 1 -size 2 0x59d241 // 以16機(jī)制打印0x59d241地址開始內(nèi)容1次,每次2byte
                  
                  
                    0x59d241:   0x010a   
                  
                  
                    (dlv)
                  
                

                2.5 堆棧相關(guān)


                2.5.1 stack


          stack 命令用于打印調(diào)用堆棧,其簡寫命令為 bt,用法如下:
                  
                    [goroutine <n>] 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.gopark
                  
                  
                       at /usr/local/go1.20/go/src/runtime/proc.go:382
                  
                  
                          .......
                  
                  
                    
                      
          1 0x0000000000408cbd in runtime.chanrecv at /usr/local/go1.20/go/src/runtime/chan.go:583 c = (*runtime.hchan)(0xc000168060) ep = unsafe.Pointer(0xc00013a798) block = true selected = (unreadable empty OP stack) received = (unreadable empty OP stack) ~R0 = (unreadable empty OP stack) t0 = 0 gp = (*runtime.g)(0xc000207380) mysg = (*runtime.sudog)(0xc0001680c0)
          2 0x00000000004087f8 in runtime.chanrecv2 at /usr/local/go1.20/go/src/runtime/chan.go:447 c = (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).purgePeriodically at /root/go/pkg/mod/github.com/panjf2000/ants/v2@v2.4.6/pool.go:69 p = ("*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í)行command
                  
                  
                    up <m> command // 向上移動m個棧幀,執(zhí)行command
                  
                  
                    down <m> command // 向下移動m個棧幀,執(zhí)行command
                  
                

          其執(zhí)行效果如下圖:
                  
                    (dlv) bt
                  
                  
                     ........
                  
                  
                     6  0x0000000001cffc5d in main.glob..func1
                  
                  
                        at ./main.go:99
                  
                  
                     .......
                  
                  
                     (dlv) 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.func2 at /root/go/pkg/mod/github.com/!russell!luo/timingwheel@v0.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 list
                  
                  
                    Goroutine 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 about
                  
                  
                       323:         // to park on a channel. The window between when this G's status
                  
                  
                       324:         // changes and when we set gp.activeStackChans is not safe for
                  
                  
                       325:         // stack shrinking.
                  
                  
                       326:         gp.parkingOnChan.Store(true)
                  
                  
                    => 327:         gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1)
                  
                  
                       328:         gp.activeStackChans = false
                  
                  
                       329:
                  
                  
                       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ù)信息,如下:


          cb6f371b134607ac6dd2da2aad21dd34.webp


                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ī)制:


          6a6e71836f3338160f13530191553ffe.webp


                3.2 變量、內(nèi)存查看相關(guān)


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

          8760b8803cd2f64be42559b01dda6993.webp


                3.3 協(xié)程、堆棧相關(guān)


          Goland 對 debug 中協(xié)程、堆棧等也提供了支持,默認(rèn)展示的是當(dāng)前協(xié)程的調(diào)用堆棧,可通過下拉列表進(jìn)行選擇,如下:
          e36de8d818b64e447d82667ccb841118.webp
          -End- 原創(chuàng)作者|張玉新

           

          推薦閱讀:

          我是如何實現(xiàn)Go性能5倍提升的?

          「GoCN酷Go推薦」我用go寫了魔獸世界登錄器?

          Go區(qū)不大,創(chuàng)造神話,科目三殺進(jìn)來了

          Go 1.22新特性前瞻

          這些流行的K8S工具,你都用上了嗎?


          想要了解Go更多內(nèi)容,歡迎掃描下方??關(guān)注公眾號, 回復(fù)關(guān)鍵詞 [實戰(zhàn)群]   ,就有機(jī)會進(jìn)群和我們進(jìn)行交流



          分享、在看與點贊Go  eed4de2d51690c2cb9a8af6b9e19c7e5.webp
          瀏覽 242
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美精品性视频 | 天堂网在线播放 | 在线尻屄视频 | 国产又粗又猛又爽又黄的视频网站 | 操B久久|