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()
}
在对程序进行调试前,我们需要对目标程序进行调试版本程序的编译。
-g3
-gcflags="-N -l"-N -l
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 使用
gdb
[lday@alex GoDbg]$ gdb ./GoDbg
b
(gdb) b main.main
Breakpoint 1 at 0x401000: file /home/lday/Works/Go_Works/GoLocalWorks/src/GoWorks/GoDbg/main.go, line 9.
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() {
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.
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
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
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
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)
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)
n
(gdb) n
DBGTestRun Begin!
18 waiter := &sync.WaitGroup{}
print/pb dbgTest.go:16
(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.
btf 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)。
go get github.com/derekparker/delve/cmd/dlv
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
}
p j
(3)dlv无法调试interface等Go内部实现的一些结构,gdb是可以的