dlv调试工具安装
Github地址:https://github.com/go-delve/delve
Linux安装:
参考地址:https://github.com/go-delve/delve/blob/master/Documentation/installation/linux/install.md
安装步骤(安装前确保$GOPATH已经安装):
$ git clone https://github.com/go-delve/delve.git $GOPATH/src/github.com/go-delve/delve $ cd $GOPATH/src/github.com/go-delve/delve $ make install
只听到从知秋君办公室传来知秋君的声音: 乳燕飞华屋。有谁来对上联或下联?
调试Go程序core dump文件
默认Go程序是不会产生core dump文件的,需要一些配置才能产生。
Linux环境配置
ulimit -a
:用来显示当前的各种用户进程限制。
此代码由一叶知秋网-知秋君整理[root@localhost dlv-test]# ulimit -a core file size (blocks, -c) unlimited data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 7695 max locked memory (kbytes, -l) 64 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 7695 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited
可以看到core file size (blocks, -c) 0
产生core文件限制是0,就是不能产生core文件,所以需要将这个限制修改:ulimit -c unlimited
。
Go环境配置
要想让Go程序能产生core dump文件,需要配置GOTRACEBACK
环境变量:
export GOTRACEBACK=crash
或者在执行程序时指定(测试用例会演示)。
测试用例
// test.go package main import ( "fmt" "time" "unsafe" ) func main() { for i := 1;i <= 10;i++ { go func(gid int) { n := 0 for { fmt.Println(time.Now().Format("2006-01-02 15:04:05"),gid,n) time.Sleep(time.Second) } }(i) } go func() { arr := 0 p := uintptr(unsafe.Pointer(&arr)) myfun1(p) }() for true { time.Sleep(time.Second) } } func myfun1(p uintptr) { arr := (*int)(unsafe.Pointer(p)) *arr = 1 fmt.Println(*arr) go myfun2() fmt.Println(*arr) } func myfun2() { fmt.Println("myfun2") myfun3() } func myfun3() { var p uintptr = 0 arr := (*int)(unsafe.Pointer(p)) *arr = 1 fmt.Println(*arr) }
运行程序:
-
go build test.go
-
GOTRACEBACK=crash ./test
-
产生
core.5121
文件,使用dlv工具进行类gdb调试,命令dlv core test core.5121
启动dlv调试:
此代码由一叶知秋网-知秋君整理[root@localhost dlv-test]# dlv core test core.5121 Type 'help' for list of commands. (dlv) goroutines Goroutine 1 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:305 runtime.gopark (0x42b7a0) Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:305 runtime.gopark (0x42b7a0) Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:305 runtime.gopark (0x42b7a0) Goroutine 5 - User: /usr/local/go/src/runtime/proc.go:305 runtime.gopark (0x42b7a0) Goroutine 6 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 7 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 8 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 9 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 10 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 11 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 12 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 13 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 14 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 15 - User: /usr/local/go/src/runtime/proc.go:310 time.Sleep (0x4448d7) Goroutine 17 - User: /usr/local/go/src/runtime/lock_futex.go:228 runtime.notetsleepg (0x409d04) * Goroutine 18 - User: ./test.go:47 main.myfun3 (0x495d93) (thread 5123) [17 goroutines]
命令goroutines
查看所有goroutine,会发现Goroutine 18 - User: ./test.go:47 main.myfun3 (0x495d93) (thread 5123)
这是我们写的代码,所以肯定时这个goroutine出现问题了。
(dlv) goroutine 18 Switched from 18 to 18 (thread 5123)
切换到goroutine 18栈帧,当然当前所在的栈帧也是goroutine 18的栈帧,可以通过上一步中* Goroutine 18
前面这个*
可以看出来。
(dlv) bt 0 0x0000000000 in runtime.raise at /usr/local/go/src/runtime/sys_linux_amd64.s:150 1 0x000000000043c50b in runtime.dieFromSignal at /usr/local/go/src/runtime/signal_unix.go:428 2 0x000000000043c96d in runtime.sigfwdgo at /usr/local/go/src/runtime/signal_unix.go:631 3 0x000000000043bbf0 in runtime.sigtrampgo at /usr/local/go/src/runtime/signal_unix.go:289 4 0x0000000000 in runtime.sigtramp at /usr/local/go/src/runtime/sys_linux_amd64.s:357 5 0x0000000000 in runtime.sigreturn at /usr/local/go/src/runtime/sys_linux_amd64.s:449 6 0x000000000043c6aa in runtime.crash at /usr/local/go/src/runtime/signal_unix.go:520 7 0x0000000000429cd4 in runtime.fatalpanic at /usr/local/go/src/runtime/panic.go:874 8 0x0000000000 in runtime.gopanic at /usr/local/go/src/runtime/panic.go:722 9 0x000000000043c42c in runtime.panicmem at /usr/local/go/src/runtime/panic.go:199 10 0x000000000043c42c in runtime.sigpanic at /usr/local/go/src/runtime/signal_unix.go:394 11 0x0000000000495d93 in main.myfun3 at ./test.go:47 12 0x0000000000495d5a in main.myfun2 at ./test.go:42 13 0x0000000000453a91 in runtime.goexit at /usr/local/go/src/runtime/asm_amd64.s:1357
查看当前的栈帧即breakpoints trace
。发现11 0x0000000000495d93 in main.myfun3
正是我们的代码出问题的地方。
(dlv) frame 11 > runtime.raise() /usr/local/go/src/runtime/sys_linux_amd64.s:150 (PC: 0x) Warning: debugging optimized function Frame 11: ./test.go:47 (PC: 495d93) 42: myfun3() 43: } 44: 45: func myfun3() { 46: var p uintptr = 0 => 47: arr := (*int)(unsafe.Pointer(p)) 48: *arr = 1 49: fmt.Println(*arr) 50: }
在goroutine 18中,frame 11
切换到具体11函数栈,发现问题出现在47行的代码,通过理解47行代码的上下文得知,不能操作地址为0的内存。
总结
- dlv功能特别强大,不仅能通过core dump文件定位到哪个goroutine崩溃,还可以具体定位到某行出错的代码,个人觉得调试Go core dump使用dlv比gdb好用。
- dlv还有很多命令,可以通过help查看命令的具体用法。
- 线上Go程序还是需要开启core dump的。