Go 调试工具 - Delve 快速入门

开发 开发工具
gdb 只能做到最基本的变量打印,却理解不了 golang 的一些特殊类型,比如 channel,map,slice 等,gdb 原生是无法调适 goroutine 协程的, 因为这是用户态的调度单位,gdb 只能理解线程,所以只能通过 python 脚本的扩展,把协程结构按照链表输出.
本文转载自微信公众号「洋芋编程」,作者蛮荆 。转载本文请联系洋芋编程公众号。

简介

Delve​ 用来调试 Go​ 语言开发的程序,该工具的目标是为 Go 语言提供一个简单、功能齐全的调试工具。

为什么不推荐 gdb

  • gdb 对 Go 的调试支持是通过一个 python 脚本文件 src/runtime/runtime-gdb.py 扩展的,功能有限
  • gdb 只能做到最基本的变量打印,却理解不了 golang 的一些特殊类型,比如 channel,map,slice 等,gdb 原生是无法调适 goroutine 协程的, 因为这是用户态的调度单位,gdb 只能理解线程,所以只能通过 python 脚本的扩展,把协程结构按照链表输出

安装

$ go install github.com/go-delve/delve/cmd/dlv@latest

# 安装完成后查看版本
$ dlv verison

Delve Debugger
Version: 1.20.1
Build: $Id: 96e65b6c615845d42e0e31d903f6475b0e4ece6e

常用命令

  •  dlv attach - 调试进程
  • dlv core - 调试 core
  • dlv debug​ - 编译并调试当前目录的 main 包,也可以通过参数指定其他包
  • dlv exec - 调试二进制文件
  • dlv test - 编译并调试测试文件

快速开始

我们首先从一个简单的示例程序开始,改程序打印字符串 hello world, 然后结束并退出。

源文件

// main.go

package main

func main() {
println("hello world")
}

调试源文件

$ dlv debug main.go

# Type 'help' for list of commands.
(dlv)

# 输入 help 查看参数说明
(dlv) help
The following commands are available:

Running the program:
call ------------------------ Resumes process
...
...
types ---------------------- Print list of types

Type help followed by a command for full documentation.

# 运行程序
(dlv) continue
hello world
Process 3637 has exited with status 0

调试编译后二进制文件

# 编译源文件
$ go build -o main main.go

$ dlv exec ./main

Type 'help' for list of commands.
(dlv)

# 接下来的步骤和调试源文件的一样,这里不再赘述

调试进程

为了让进程保持在运行状态,我们在程序中加一行休眠代码:

package main

import "time"

func main() {
time.Sleep(time.Minute)
println("hello world")
}

调试前先运行程序:

$ go run main.go

# 查看进程 ID
$ ps -ef | grep "go run main.go"

7602 27666 0 21:30 pts/6 00:00:00 go run main.go

# 调试进程
$ dlv attach 7602

Type 'help' for list of commands.
(dlv)

...

# 1 分钟之后,main.go 并未正常退出,因为当前正在调试
# 输入 continue 继续运行

(dlv) continue
Process 7602 has exited with status 0

常用调试命令

下列命令是启动 dlv​ 调试后可用的命令 (也就是当前命令行变为 (dlv) 之后可用)。

运行程序

命令

描述

call

恢复进程,调用函数 (实验阶段)

continue

继续运行程序,直到遇到断点或程序结束

next

单步调试

restart

重新运行

step

单步调试某个函数

step-instruction

单步调试某个 CPU 指令

stepout

从当前函数跳出

操作断点

命令

描述

break

设置断点

breakpoints

打印所有断点

clear

删除断点

clearall

删除所有断点

condition

设置条件断点

on

设置一个断点触发时执行的命令

toggle

打开/关闭 断点

查看变量或内存

命令

描述

args

打印函数参数

display

每次程序停止时打印表达式的值

examinemem

解析给定地址的内存

locals

打印本地变量

print

解析一个表达式

regs

打印寄存器信息

set

设置变量的值

vars

打印包内变量

whatis

打印类型信息

线程 / goroutine 的展示与切换

命令

描述

goroutine

打印或切换 goroutine

goroutines

打印所有 goroutine

thread

切换到指定的线程

threads

打印所有线程信息

调用堆栈

命令

描述

deferred

在 defer 上下文中执行命令

frame

设置当前帧,或在不同的帧上执行命令

stack

打印堆栈信息

其他命令

命令

描述

config

更改配置参数

disassemble

反汇编

dump

dump core

exit

结束调试,也可以用 ​​quit​

综合示例

最后,我们使用一个的小例子,熟悉下常用的几个命令。

示例程序代码如下:

// main.go

package main

var (
x = 1024
)

func main() {
for i := 0; i < 5; i++ {
println(i)
}
}

# 开始调试
$ dlv debug main.go

Type 'help' for list of commands.
(dlv)

# 增加断点
(dlv) b main.main
Breakpoint 1 set at 0x45f0c6 for main.main() ./main.go:7

# 查看断点
(dlv) bp
...
Breakpoint 2 (enabled) at 0x45f0c6 for main.main() ./main.go:7 (0)

# 运行程序
(dlv) continue
> main.main() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0x45f0c6)
2:
3: var (
4: x = 1024
5: )
6:
=> 7: func main() {
8: for i := 0; i < 5; i++ {
9: println(i)
10: }
11: }
12:
# 可以看到,程序停在了设置的断点上

# 打印包变量
(dlv) vars vars main.x
...
main.x = 1024
...

# 单步调试
(dlv) next
> main.main() ./main.go:8 (PC: 0x45f0d4)
3: var (
4: x = 1024
5: )
6:
7: func main() {
=> 8: x = 0
9: for i := 0; i < 5; i++ {
10: println(i)
11: }
12: }
13:

# 再次单步调试
(dlv) next
> main.main() ./main.go:9 (PC: 0x45f0df)
4: x = 1024
5: )
6:
7: func main() {
8: x = 0
=> 9: for i := 0; i < 5; i++ {
10: println(i)
11: }
12: }
13:
14: //timeout := time.After(time.Minute)

# 可以看到,程序停在了循环语句

# 打印包变量
(dlv) vars main.x
main.x = 0

# 再次单步调试
(dlv) next
> main.main() ./main.go:10 (PC: 0x45f0f4)
5: )
6:
7: func main() {
8: x = 0
9: for i := 0; i < 5; i++ {
=> 10: println(i)
11: }
12: }
13:
14: //timeout := time.After(time.Minute)
15: //


# 打印本地变量
(dlv) locals
i = 0

# 查看堆栈信息
(dlv) stack
0 0x000000000045f0f4 in main.main
at ./main.go:10
1 0x00000000004358b8 in runtime.main
at /usr/local/go/src/runtime/proc.go:250
2 0x000000000045c0c1 in runtime.goexit
at /usr/local/go/src/runtime/asm_amd64.s:1594


# 打印 goroutine 信息
(dlv) goroutine
Thread 27873 at ./main.go:10
Goroutine 1:
...

# 删除所有断点
(dlv) clearall
Breakpoint 1 cleared at 0x45f0c6 for main.main() ./main.go:7

# 继续执行
(dlv) continue
1
2
3
4
Process 27873 has exited with status 0

# 退出调试
(dlv) exit

与 IDE 集成

Delve 支持以插件的形式集成到主流的 IDE 里面,具体的支持列表请看 这个页面[1]。

常见问题

单点调试总是执行非预期的代码?

一般是被编译器优化了,比如内联会导致 dlv 单步调试无法打印某些变量,解决方法是禁止编译优化。

# 禁用内联和优化 (细节可以阅读引用文章列表)
go run -gcflags "-N -l" main.go

Reference

  • go-delve/delve[2]
  • 高效获取堆栈调用信息
  • 内联优化
  • 逃逸分析
  • golang 调试分析的高阶技巧
  • 如何定位 golang 进程 hang 死的 bug[3]
  • Debugging with GDB[4]
  • 100-gdb-tips[5]
  • 深入 Go 语言 - 11[6]
  • WSL2 安装 perf[7]

引用链接

[1]​ 这个页面: ​​https://github.com/go-delve/delve/blob/master/Documentation/EditorIntegration.md​

[2]​ go-delve/delve: ​​https://github.com/go-delve/delve​

[3]​ 如何定位 golang 进程 hang 死的 bug: ​​https://xargin.com/how-to-locate-for-block-in-golang/​

[4]​ Debugging with GDB: ​​https://sourceware.org/gdb/current/onlinedocs/gdb.html/​

[5]​ 100-gdb-tips: ​​https://github.com/hellogcc/100-gdb-tips​

[6]​ 深入 Go 语言 - 11: ​​https://colobu.com/2016/07/04/dive-into-go-11/​

[7]​ WSL2 安装 perf: https://gist.github.com/abel0b/b1881e41b9e1c4b16d84e5e083c38a13

责任编辑:武晓燕 来源: 洋芋编程
相关推荐

2022-05-23 09:22:20

Go语言调试器Delve

2020-07-10 16:52:43

DelveGo程序开源

2021-07-26 10:14:38

Go语言工具

2020-06-15 08:39:41

调试工具

2022-08-28 10:36:53

调试工具通用

2015-08-14 09:21:09

gdb工具调试 Go

2011-08-15 17:38:48

iPhone开发调试工具

2024-01-24 13:22:40

Python调试工具技巧

2024-07-09 08:31:26

2016-12-02 20:23:51

AndroidADB

2012-02-24 09:25:20

JavaScript

2024-02-23 10:00:27

Linux工具

2022-08-21 14:05:54

调试工具CDP

2010-06-02 15:21:29

Linux 网络性能

2010-06-02 15:45:45

Linux 网络性能

2019-07-15 16:00:06

微软开源TensorWatch

2019-04-30 15:10:42

Python调试工具编程语言

2018-11-27 11:35:32

systemtapMySQL调试工具

2020-05-21 15:53:59

远程调试工具

2010-06-02 15:37:38

Linux 网络性能
点赞
收藏

51CTO技术栈公众号