Go 调试工具:gdb vs dlv

1、背景

通过log库输出日志,我们可以对程序进行异常分析和问题追踪。但有时候,我也希望能有更直接的程序跟踪及定位工具能够帮助我们更方便快捷的追踪、定位问题,最直观的感觉还是使用调试器。Linux平台下,原生的C/C++程序,我们往往使用gdb进行程序调试,切换到Golang,我们同样还是可以使用gdb进行调试。同时我们还可以使用golang实现的调试器dlv进行调试。以下内容是我对gdb以及dlv使用及对比总结 

1.1 准备工作

为展示整个调试过程,准备了一个演示项目GoDbg,整个目录结构如下所示

[lday@alex GoDbg]$ tree
.
├── main.go
└── mylib
    └── dbgTest.go

其中,main.go为主函数入口,而dbgTest.go启动多个goroutine,用于演示调试操作。
main.go:

package main
import (
	"GoWorks/GoDbg/mylib"
	"fmt"
	"os"
)
func main() {
	fmt.Println("Golang dbg test...")
	var argc = len(os.Args)
	var argv = append([]string{}, os.Args...)
	fmt.Printf("argc:%d\n", argc)
	fmt.Printf("argv:%v\n", argv)
	var var1 = 1
	var var2 = "golang dbg test"
	var var3 = []int{1, 2, 3}
	var var4 mylib.MyStruct
	var4.A = 1
	var4.B = "golang dbg my struct field B"
	var4.C = map[int]string{1: "value1", 2: "value2", 3: "value3"}
	var4.D = []string{"D1", "D2", "D3"}
	mylib.DBGTestRun(var1, var2, var3, var4)
	fmt.Println("Golang dbg test over")
}

dbgTest.go:

package mylib
import (
	"fmt"
	"sync"
	"time"
)
type MyStruct struct {
	A int
	B string
	C map[int]string
	D []string
}
func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) {
	fmt.Println("DBGTestRun Begin!\n")
	waiter := &sync.WaitGroup{}
	waiter.Add(1)
	go RunFunc1(var1, waiter)
	waiter.Add(1)
	go RunFunc2(var2, waiter)
	waiter.Add(1)
	go RunFunc3(&var3, waiter)
	waiter.Add(1)
	go RunFunc4(&var4, waiter)
	waiter.Wait()
	fmt.Println("DBGTestRun Finished!\n")
}
func RunFunc1(variable int, waiter *sync.WaitGroup) {
	fmt.Printf("var1:%v\n", variable)
	for {
		if variable != 123456 {
			continue
		} else {
			break
		}
	}
	time.Sleep(10 * time.Second)
	waiter.Done()
}
func RunFunc2(variable string, waiter *sync.WaitGroup) {
	fmt.Printf("var2:%v\n", variable)
	time.Sleep(10 * time.Second)
	waiter.Done()
}
func RunFunc3(pVariable *[]int, waiter *sync.WaitGroup) {
	fmt.Printf("*pVar3:%v\n", *pVariable)
	time.Sleep(10 * time.Second)
	waiter.Done()
}
func RunFunc4(pVariable *MyStruct, waiter *sync.WaitGroup) {
	fmt.Printf("*pVar4:%v\n", *pVariable)
	time.Sleep(10 * time.Second)
	waiter.Done()
}

在对程序进行调试前,我们需要对目标程序进行调试版本程序的编译。

C/C++程序,我们会通过gcc/g++进行编译、链接时加入-g3等参数,使得程序编译时带入调试信息,进而让调试器能够最终并解释相关的程序代码。

同样的,在我们对Golang程序进行调试时,我们也需要加入相应的编译、链接选项:-gcflags="-N -l",生成程序调试信息(-N -l用于关闭编译器的内联优化)。编译GoDbg项目指令:

go build -gcflags="-N -l" ../GoDbg

2、gdb

因为gdb对Golang的支持也是在不断完善中,为使用gdb调试Golang程序,建议将gdb升级到相对较新版本,目前,我使用的版本是gdb7.10
大多数命令在使用gdb调试C/C++时都会用到,详细说明可参考:Debugging Go Code with GDB,具体操作如下:

2.1 安装

yum install ncures-devel
wget http://ftp.gnu.org/gnu/gdb/gdb-8.2.tar.gz
tar zxf gdb-8.2.tar.gz
cd gdb-8.2
make && make install

2.2 进行调试

go build -gcflags '-N -l' main.go
gdb main

2.3 使用

(1)启动调试程序(gdb

[lday@alex GoDbg]$ gdb ./GoDbg

(2)在main函数上设置断点(b

(gdb) b main.main
Breakpoint 1 at 0x401000: file /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go, line 9.

(3)带参数启动程序(r

(gdb) r arg1 arg2
Starting program: /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/GoDbg arg1 arg2
[New LWP 8412]
[New LWP 8413]
[New LWP 8414]
[New LWP 8415]
 
Breakpoint 1, main.main () at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9
9    func main() {

(4)在文件dbgTest.go上通过行号设置断点(b

(gdb) b dbgTest.go:16
Breakpoint 3 at 0x457960: file /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go, line 16.

(5)查看断点设置情况(info b

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000401000 in main.main 
                                                   at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9
    breakpoint already hit 1 time
2       breakpoint     keep y   0x0000000000401000 in main.main 
                                                   at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9
    breakpoint already hit 1 time
3       breakpoint     keep y   0x0000000000457960 in GoWorks/GoDbg/mylib.DBGTestRun 
                                                   at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:16

 (6)禁用断点(dis n

(gdb) dis 1   
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x0000000000401000 in main.main 
                                                   at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9
    breakpoint already hit 1 time
2       breakpoint     keep y   0x0000000000401000 in main.main 
                                                   at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9
    breakpoint already hit 1 time
3       breakpoint     keep y   0x0000000000457960 in GoWorks/GoDbg/mylib.DBGTestRun 
                                                   at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:16

 (7)删除断点(del n

(gdb) del 1
(gdb) info b
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000401000 in main.main 
                                                   at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:9
    breakpoint already hit 1 time
3       breakpoint     keep y   0x0000000000457960 in GoWorks/GoDbg/mylib.DBGTestRun 
                                                   at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:16

 (8)断点后继续执行(c

(gdb) c
Continuing.
Golang dbg test...
argc:3
argv:[/home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/GoDbg arg1 arg2]
 
Breakpoint 3, GoWorks/GoDbg/mylib.DBGTestRun (var1=1, var2="golang dbg test")
    at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:16
16    func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) {
(gdb) 

 (9)显示代码(l

(gdb) l
11        B string
12        C map[int]string
13        D []string
14    }
15    
16    func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) {
17        fmt.Println("DBGTestRun Begin!\n")
18        waiter := &sync.WaitGroup{}
19    
20        waiter.Add(1)

 (10)单步执行(n

(gdb) n
DBGTestRun Begin!
 
18        waiter := &sync.WaitGroup{}

 (11)打印变量信息(print/p
在进入DBGTestRun的地方设置断点(b dbgTest.go:16),进入该函数后,通过p命令显示对应变量:

(gdb) l 17
12        C map[int]string
13        D []string
14    }
15    
16    func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) {
17        fmt.Println("DBGTestRun Begin!\n")
18        waiter := &sync.WaitGroup{}
19    
20        waiter.Add(1)
21        go RunFunc1(var1, waiter)
(gdb) p var1 
$3 = 1
(gdb) p var2
$4 = "golang dbg test"
(gdb) p var3
No symbol "var3" in current context.

 从上面的输出我们可以看到一个很奇怪的事情,虽然DBGTestRun有4个参数传入,但是,似乎var3和var4 gdb无法识别,在后续对dlv的实验操作中,我们发现,dlv能够识别var3, var4.

(12)查看调用栈(bt),切换调用栈(f n),显示当前栈变量信息 

(gdb) bt
#0  GoWorks/GoDbg/mylib.DBGTestRun (var1=1, var2="golang dbg test")
    at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/mylib/dbgTest.go:17
#1  0x00000000004018c2 in main.main () at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:27
(gdb) f 1
#1  0x00000000004018c2 in main.main () at /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go:27
27        mylib.DBGTestRun(var1, var2, var3, var4)
(gdb) l
22        var4.A = 1
23        var4.B = "golang dbg my struct field B"
24        var4.C = map[int]string{1: "value1", 2: "value2", 3: "value3"}
25        var4.D = []string{"D1", "D2", "D3"}
26    
27        mylib.DBGTestRun(var1, var2, var3, var4)
28        fmt.Println("Golang dbg test over")
29    }
(gdb) print var1 
$5 = 1
(gdb) print var2
$6 = "golang dbg test"
(gdb) print var3
$7 =  []int = {1, 2, 3}
 
(gdb) print var4
$8 = {A = 1, B = "golang dbg my struct field B", C = map[int]string = {[1] = "value1", [2] = "value2", [3] = "value3"}, 
D =  []string = {"D1", "D2", "D3"}}

3、dlv(推荐)

尝试了”老牌”调试器gdb,我们再来试试新进的Golang原生调试器delve(dlv)

dlv是Golang实现的Golang调试器,目前dlv对windows平台的支持似乎不是很好,我在windows平台调试,dlv无法找到目标程序的源代码,因此建议在Linux平台下调试Golang程序时使用。使用dlv前,需在本地通过go get github.com/derekparker/delve/cmd/dlv进行安装。dlv的详细介绍可参见github上的delve介绍。以下是具体操作说明

3.1 安装

go get -u github.com/go-delve/delve/cmd/dlv

3.2 调试

dlv debug main.go

3.3 使用

    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!!!)
    clear ----------------------- Deletes breakpoint.
    clearall -------------------- Deletes multiple breakpoints.
    condition (alias: cond) ----- Set breakpoint condition.
    config ---------------------- Changes configuration parameters.
    continue (alias: c) --------- Run until breakpoint or program termination.
    deferred -------------------- Executes command in the context of a deferred call.
    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.
    libraries ------------------- List loaded dynamic libraries
    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.
    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.

4、总结

综合比较两个Golang程序调试器gdb和dlv,我认为dlv的功能更为完善,更能满足实际调试时的功能需求。两者的优缺点比较大致如下

调试器优势不足
dlv对goroutine环境调试支持比较完善暂时没找到
gdb符合现有的调试习惯,类似C/C++调试指令都有对goroutine场景支持不足,不能很好的应对goroutine的调试

(1)dlv对goroutine的支持更好,我使用gdb的没有找到goroutine的调试方法,可能姿势不对

(2)gdb对于局部引用变量无法调试,dlv不会

func main() {
  i := 10
  j := &i
}

 

如上代码,gdb 使用 p j 打印变量j的时候报错,dlv却可以

(3)dlv无法调试interface等Go内部实现的一些结构,gdb是可以的

相关文章

1. Go调试工具—— Delve - 慢行厚积 - 博客园

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值