使用dlv調試golang程序

1、編譯選項

go build -gcflags=all="-N -l"  ## 必須這樣編譯,才能用gdb打印出變量,第二個是小寫的L,不是大寫的i

需要加編譯選項,類似gcc中的 -g選項,加入調試信息。關於如何安裝dlv,請自行百度或者谷歌。

2、使用dlv調試

dlv的功能介紹

Usage:
  dlv [command]

Available Commands:
  attach      Attach to running process and begin debugging.
  connect     Connect to a headless debug server.
  core        Examine a core dump.
  debug       Compile and begin debugging main package in current directory, or the package specified.
  exec        Execute a precompiled binary, and begin a debug session.
  help        Help about any command
  run         Deprecated command. Use 'debug' instead.
  test        Compile test binary and begin debugging program.
  trace       Compile and begin tracing program.
  version     Prints version.

由於dlv的功能比較多,我只介紹我常用的幾個,包括 attach、debug、exec、core、test,這5個。

1、dlv attach

這個相當於gdb -p 或者 gdb attach ,即跟蹤一個正在運行的程序。這中用法也是很常見,對於一個後臺程序,它已經運行很久了,此時你需要查看程序內部的一些狀態,只能藉助attach.

dlv attach $PID  ## 後面的進程的ID

2、dlv debug

dlv debug main/hundredwar.go ## 先編譯,後啓動調試 

3、dlv exec

dlv exec ./HundredsServer  ## 直接啓動調試

與dlv debug區別就是,dlv debug 編譯一個臨時可執行文件,然後啓動調試,類似與go run。

4、dlv core

用來調試core文件,但是想讓go程序生成core文件,需要配置一個環境變量,默認go程序不會產生core文件。

export GOTRACEBACK=crash

只有定義這個環境變量,go程序纔會coredump。如果在協程入口定義defer函數,然後recover也不會產生core文件。

go func() {
		defer func() {
			if r := recover(); r != nil {
                fmt.Printf("panic error\n") 
			}
		}()
		var p *int = nil
		fmt.Printf("p=%d\n", *p) // 訪問nil指責
	}()  // 這個協程不會生產core文件

因爲recover的作用就是捕獲異常,然後進行錯誤處理,所以不會產生coredump,這個需要注意。這個也是golang的一大特色吧,捕獲異常,避免程序coredump。

調試coredump文件

關於調試core文件,其實和C/C++差不多,最後都是找到發生的函數幀,定位到具體某一行代碼。但是golang稍有不同,對於golang的core文件需要先定位到時哪一個goroutine發生了異常。

dlv core ./Server core.26945  ## 啓動
Type 'help' for list of commands.
(dlv) goroutines   ## 查看所有goroutine
[12 goroutines]
  Goroutine 1 - User: /usr/local/go/src/runtime/time.go:102 time.Sleep (0x440d16)
  Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
  Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
  Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
  Goroutine 5 - User: /usr/local/go/src/runtime/time.go:102 time.Sleep (0x440d16)
  Goroutine 6 - User: /usr/local/go/src/runtime/time.go:102 time.Sleep (0x440d16)
  Goroutine 7 - User: /usr/local/go/src/runtime/time.go:100 time.Sleep (0x440ccd)
  Goroutine 8 - User: ./time.go:114 main.main.func2 (0x4a33cb) (thread 21239)
  Goroutine 9 - User: /usr/local/go/src/runtime/lock_futex.go:227 runtime.notetsleepg (0x40ce42)
  Goroutine 17 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
  Goroutine 33 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
  Goroutine 49 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)

上面,可以看到所以的goroutine,需要找到自己的業務代碼所在的goroutine,這裏需要判斷,不像C/C++的core文件,可以定義定位到所在的函數棧。這裏的是 Goroutine 8 。

需要進入 8 號 goroutine。

(dlv) goroutine 8  ## 切換到 8 號 goroutine
Switched from 0 to 8 (thread 21239)
(dlv) bt    ## 查看棧幀
0  0x0000000000450774 in runtime.raise
   at /usr/local/go/src/runtime/sys_linux_amd64.s:159
1  0x000000000044cea0 in runtime.systemstack_switch
   at /usr/local/go/src/runtime/asm_amd64.s:363
2  0x00000000004265ba in runtime.dopanic
   at /usr/local/go/src/runtime/panic.go:597
3  0x00000000004261f1 in runtime.gopanic
   at /usr/local/go/src/runtime/panic.go:551
4  0x00000000004250ce in runtime.panicmem
   at /usr/local/go/src/runtime/panic.go:63
5  0x0000000000438baa in runtime.sigpanic
   at /usr/local/go/src/runtime/signal_unix.go:388
6  0x00000000004a33cb in main.main.func2
   at ./time.go:114  ## 顯然6號棧是自己的業務代碼
7  0x000000000044f6d1 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:2361
(dlv) frame 6  ## 進入6號棧幀
> runtime.raise() /usr/local/go/src/runtime/sys_linux_amd64.s:159 (PC: 0x450774)
Warning: debugging optimized function
Frame 6: ./time.go:114 (PC: 4a33cb)
   109:			// 	if r := recover(); r != nil {
   110:	
   111:			// 	}
   112:			// }()
   113:			var p *int = nil
=> 114:			fmt.Printf("p=%d\n", *p)  ## 這裏發生了異常
   115:		}()
   116:	
   117:		time.Sleep(10000000)
   118:	}

5、dlv test

dlv test 也很有特色,是用來調試測試代碼的。因爲測試代碼都是某一個包裏面,是以包爲單位的。

dlv test $packname ## 包名
[KentZhang@LOCAL-192-168-97-2 src]$ dlv test db ## 調試db包內部的測試用例
Type 'help' for list of commands.
(dlv) b TestMoneyDbGet ## 打斷點,不用加包名了
Breakpoint 1 set at 0x73c15b for db.TestMoneyDbGet() ./db/moneydb_test.go:9
(dlv) c
> db.TestMoneyDbGet() ./db/moneydb_test.go:9 (hits goroutine(5):1 total:1) (PC: 0x73c15b)
     4:		"logger"
     5:		"testing"
     6:	)
     7:	
     8:	//日誌不分離
=>   9:	func TestMoneyDbGet(t *testing.T) {
    10:		logger.Init("testlog", ".", 1000, 3, logger.DEBUG_LEVEL, false, logger.PUT_CONSOLE)
    11:		c := MoneydbConnect("192.168.202.92:12515")
    12:		if nil == c {
    13:			t.Error("Init() failed.")
    14:			return
(dlv) 

3、調試命令

關於dlv內的調試命令,和gdb差不多,可以使用help查看所有命令。

help

(dlv) help
The following commands are available:
    args -------------------------------- Print function arguments.
    break (alias: b) -------------------- Sets a breakpoint.
    breakpoints (alias: bp) ------------- Print out info for active breakpoints.
    call -------------------------------- Resumes process, injecting a function call (EXPERIMENTAL!!!)
    check (alias: checkpoint) ----------- Creates a checkpoint at the current position.
    checkpoints ------------------------- Print out info for existing checkpoints.
    clear ------------------------------- Deletes breakpoint.
    clear-checkpoint (alias: clearcheck)  Deletes checkpoint.
    clearall ---------------------------- Deletes multiple breakpoints.
    condition (alias: cond) ------------- Set breakpoint condition.
    config ------------------------------ Changes configuration parameters.
    continue (alias: c) ----------------- Run until breakpoint or program termination.
    disassemble (alias: disass) --------- Disassembler.
    down -------------------------------- Move the current frame down.
    edit (alias: ed) -------------------- Open where you are in $DELVE_EDITOR or $EDITOR
    exit (alias: quit | q) -------------- Exit the debugger.
    frame ------------------------------- Set the current frame, or execute command on a different frame.
    funcs ------------------------------- Print list of functions.
    goroutine --------------------------- Shows or changes current goroutine
    goroutines -------------------------- List program goroutines.
    help (alias: h) --------------------- Prints the help message.
    list (alias: ls | l) ---------------- Show source code.
    locals ------------------------------ Print local variables.
    next (alias: n) --------------------- Step over to next source line.
    on ---------------------------------- Executes a command when a breakpoint is hit.
    print (alias: p) -------------------- Evaluate an expression.
    regs -------------------------------- Print contents of CPU registers.
    restart (alias: r) ------------------ Restart process from a checkpoint or event.
    rewind (alias: rw) ------------------ Run backwards until breakpoint or program termination.
    set --------------------------------- Changes the value of a variable.
    source ------------------------------ Executes a file containing a list of delve commands
    sources ----------------------------- Print list of source files.
    stack (alias: bt) ------------------- Print stack trace.
    step (alias: s) --------------------- Single step through program.
    step-instruction (alias: si) -------- Single step a single cpu instruction.
    stepout ----------------------------- Step out of the current function.
    thread (alias: tr) ------------------ Switch to the specified thread.
    threads ----------------------------- Print out info for every traced thread.
    trace (alias: t) -------------------- Set tracepoint.
    types ------------------------------- Print list of types
    up ---------------------------------- Move the current frame up.
    vars -------------------------------- Print package variables.
    whatis ------------------------------ Prints type of an expression.

help [command]

使用 help command 打印出具體命令的用法,例如:

(dlv) help set
Changes the value of a variable.

	[goroutine <n>] [frame <m>] set <variable> = <value>
(dlv) help on
Executes a command when a breakpoint is hit.

	on <breakpoint name or id> <command>.

4、dlv的不足之處

dlv和gdb相比,除了支持協程這一優勢之外,其他的地方遠不如gdb。比如

  • dlv 的print 不支持十六進制打印,gdb就可以,p /x number
  • dlv不支持變量、函數名的自動補全
  • dlv的on 功能與gdb的commands類似,可以的是dlv只支持print, stack and goroutine三個命令,竟然不支持continue

還有一個特殊情況,如果一個函數有定義,但是沒在任何地方調用,那麼dlv打斷點打不到

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章