使用dlv调试golang程序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/KentZhang_/article/details/84925878

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打断点打不到

猜你喜欢

转载自blog.csdn.net/KentZhang_/article/details/84925878
今日推荐