go 调试器 delve(dlv) 的使用(附实例及常用命令解释)

news/2024/7/5 3:33:21

前言

Go 目前的调试器有如下几种:

  • GDB 最早期的调试工具,现在用的很少。
  • LLDB macOS 系统推荐的标准调试工具,单 Go 的一些专有特性支持的比较少。
  • Delve 专门为 Go 语言打造的调试工具,使用最为广泛。

本篇简单说明如何使用 Delve 工具来调试 Go 程序,使用的 delve 版本为 1.20.1

安装

  1. Go1.16 及之后:
# 最新版本:
$ go install github.com/go-delve/delve/cmd/dlv@latest

# 指定分支:
$ go install github.com/go-delve/delve/cmd/dlv@master

# 特定版本或伪版本:
$ go install github.com/go-delve/delve/cmd/dlv@v1.7.3
$ go install github.com/go-delve/delve/cmd/dlv@v1.7.4-0.20211208103735-2f13672765fe
  1. 可以自己下载源码 build
$ git clone https://github.com/go-delve/delve
$ cd delve
$ go install github.com/go-delve/delve/cmd/dlv

实例

讲两个经常使用的方式:

  • dlv debug
  • dlv attach

dlv debug

  1. 创建 main.go 文件:
package main

import (
    "fmt"
)

func main() {
    nums := make([]int, 5)
    for i := 0; i <len(nums); i++ {
        nums[i] = i * i
    }
    fmt.Println(nums)
}
  1. 命令行进入包所在目录,然后输入 dlv debug 命令进入调试
$ dlv debug
Type 'help' for list of commands.

#设置断点:main.main
(dlv)b main.main 
Breakpoint 1 set at 0xff3c0a for main.main() ./main.go:7

# 查看已设置的所有断点,我们发现除了我们自己设置的 main.main 函数断点外,Delve 内部已经为 panic 异常函数设置了一个断点。
(dlv) bp 
Breakpoint runtime-fatal-throw (enabled) at 0xc97940 for runtime.throw() d:/go1.18/go/src/runtime/panic.go:982 (0)
Breakpoint unrecovered-panic (enabled) at 0xc97ce0 for runtime.fatalpanic() d:/go1.18/go/src/runtime/panic.go:1065 (0)
        print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0xd03c0a for main.main() ./main.go:7 (0)

# 查看全局变量(可通过正则参数过滤)
(dlv) vars main
runtime.main_init_done = chan bool nil
runtime.mainStarted = false

# 运行到下个断点处
(dlv) c
> main.main() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0xd03c0a)
     2:
     3: import (
     4:     "fmt"
     5: )
     6:
=>   7: func main() {
     8:     nums := make([]int, 5)
     9:     for i := 0; i <len(nums); i++ {
    10:         nums[i] = i * i
    11:     }
    12:     fmt.Println(nums)
	
# 单步执行
(dlv) n
> main.main() ./main.go:8 (PC: 0xd03c18)
     3: import (
     4:     "fmt"
     5: )
     6:
     7: func main() {
=>   8:     nums := make([]int, 5)
     9:     for i := 0; i <len(nums); i++ {
    10:         nums[i] = i * i
    11:     }
    12:     fmt.Println(nums)
    13: }

# 查看传入函数的参数
(dlv) args
(no args) # 这里main函数没有参数

# 查看局部变量
(dlv) locals
(no locals) # 这里还没初始化nums

# 再下一步,初始化nums了,可以看到有局部变量了
(dlv) n
> main.main() ./main.go:9 (PC: 0xd03c43)
     4:     "fmt"
     5: )
     6:
     7: func main() {
     8:     nums := make([]int, 5)
=>   9:     for i := 0; i <len(nums); i++ {
    10:         nums[i] = i * i
    11:     }
    12:     fmt.Println(nums)
    13: }
(dlv) locals
nums = []int len: 5, cap: 5, [...]

# 组和使用 break 和 condition 命令设置条件断点
(dlv) b main.go:10
Breakpoint 2 set at 0xd03c62 for main.main() ./main.go:10
(dlv) bp
Breakpoint runtime-fatal-throw (enabled) at 0xc97940 for runtime.throw() d:/go1.18/go/src/runtime/panic.go:982 (0)
Breakpoint unrecovered-panic (enabled) at 0xc97ce0 for runtime.fatalpanic() d:/go1.18/go/src/runtime/panic.go:1065 (0)
        print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0xd03c0a for main.main() ./main.go:7 (1) # Breakpoint 1(这个 1 是断点的编号,设置条件断点,清理断点等时候会用到)
Breakpoint 2 (enabled) at 0xd03c62 for main.main() ./main.go:10 (0)
(dlv) cond 2 i==3 # 这里设置条件断点 i 等于 3

# 继续 continue 执行到刚设置的条件断点,输出局部变量
(dlv) c
> main.main() ./main.go:10 (hits goroutine(1):1 total:1) (PC: 0xd03c62)
     5: )
     6:
     7: func main() {
     8:     nums := make([]int, 5)
     9:     for i := 0; i <len(nums); i++ {
=>  10:         nums[i] = i * i
    11:     }
    12:     fmt.Println(nums)
    13: }
(dlv) locals
nums = []int len: 5, cap: 5, [...]
i = 3
(dlv) print nums
[]int len: 5, cap: 5, [0,1,4,0,0]

# 查看栈帧信息
(dlv) stack
0  0x0000000000d03c62 in main.main
   at ./main.go:10
1  0x0000000000c99f28 in runtime.main
   at d:/go1.18/go/src/runtime/proc.go:250
2  0x0000000000cc18a1 in runtime.goexit
   at d:/go1.18/go/src/runtime/asm_amd64.s:1571
   
# 退出
(dlv) q

dlv attach

常见的 http 服务调试即可使用这种方式。

  1. 创建 main.go 文件:
package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
		nums := make([]int, 5)
		for i := 0; i < len(nums); i++ {
			nums[i] = i * i
		}
		writer.Write([]byte("OK"))
	})

	err := http.ListenAndServe(":4781", nil)
	if err != nil {
		log.Fatalln(err)
	}
}
  1. go build -gcflags="all=-N -l" main.go 生成 main.exe,**注意:这里一定要加上 -gcflags="all=-N -l" 不然有可能代码被编译器优化,断点打不上。
  2. 执行 main.exe,得到程序的 PID
  3. dlv attach PID 进入调试
$ dlv attach 22300
Type 'help' for list of commands.
(dlv) b main.go:12
Breakpoint 1 set at 0x6f34ee for main.main.func1() ./main.go:12
(dlv) c # 执行到这里之后,会卡住
  1. 访问网页 http://127.0.0.1:4781/hello,这时可以看到我们刚刚打的断点生效了
> main.main.func1() ./main.go:12 (hits goroutine(6):1 total:1) (PC: 0x6f34ee)
     7:
     8: func main() {
     9:         http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
    10:                 nums := make([]int, 5)
    11:                 for i := 0; i < len(nums); i++ {
=>  12:                         nums[i] = i * i
    13:                 }
    14:                 writer.Write([]byte("OK"))
    15:         })
    16:
    17:         err := http.ListenAndServe(":4781", nil)

接下来的用法就同上面讲的 dlv debug 用法一致。

其他

进入 dlv 调试后,可以输入 help 查看帮助信息:

(dlv) help
The following commands are available:

Running the program:
    call ------------------------ 继续进程,注入函数调用(实验!!!)
    continue (alias: c) --------- 运行直到断点或程序终止。
    next (alias: n) ------------- 运行至下一行。
    rebuild --------------------- 重新生成可执行文件,若可执行文件不是delve生成的,则不能使用。
    restart (alias: r) ---------- 重新启动进程。
    step (alias: s) ------------- 单步执行程序。
    step-instruction (alias: si)  单步执行单个cpu指令。
    stepout (alias: so) --------- 跳出当前函数。

Manipulating breakpoints:
    break (alias: b) ------- 设置断点。
    breakpoints (alias: bp)  查看所有断点信息。
    clear ------------------ 删除断点。
    clearall --------------- 删除多个断点。
    condition (alias: cond)  设置断点条件。
    on --------------------- 命中断点时执行命令。
    toggle ----------------- 打开或关闭断点。
    trace (alias: t) ------- 设置跟踪点。
    watch ------------------ 设置观察点。

Viewing program variables and memory:
    args ----------------- 打印函数参数。
    display -------------- 每次程序停止时打印表达式的值。
    examinemem (alias: x)  检查给定地址的原始内存。
    locals --------------- 打印局部变量。
    print (alias: p) ----- 对表达式求值。
    regs ----------------- 打印CPU寄存器的内容。
    set ------------------ 更改变量的值。
    vars ----------------- 打印包变量。
    whatis --------------- 打印表达式的类型。

Listing and switching between threads and goroutines:
    goroutine (alias: gr) -- 显示或更改当前goroutine。
    goroutines (alias: grs)  列出程序所有goroutine。
    thread (alias: tr) ----- 切换到指定的线程。
    threads ---------------- 打印每个跟踪线程的信息。

Viewing the call stack and selecting frames:
    deferred --------- 在延迟调用的上下文中执行命令。
    down ------------- 向下移动当前帧。
    frame ------------ 设置当前帧,或在其他帧上执行命令。
    stack (alias: bt)  打印堆栈跟踪。
    up --------------- 向上移动当前帧。

Other commands:
    config --------------------- 更改配置参数。
    disassemble (alias: disass)  反汇编程序。
    dump ----------------------- 从当前进程状态创建核心转储
    edit (alias: ed) ----------- 自己指定编辑器编辑,读的环境变量 $DELVE_EDITOR 或者 $EDITOR
    exit (alias: quit | q) ----- 退出调试器。
    funcs ---------------------- 打印函数列表。
    help (alias: h) ------------ 打印帮助消息。
    libraries ------------------ 列出加载的动态库
    list (alias: ls | l) ------- 显示源代码。
    source --------------------- 执行包含 delve 命令的文件
    sources -------------------- 打印源文件列表。
    transcript ----------------- 将命令输出追加到文件。
    types ---------------------- 打印类型列表

Type help followed by a command for full documentation.

想要查看具体的命令使用方式,可以 help 命令(例:help call)。

下面做一些常见的命令的解释

dlv 指令

命令解释
attach这个命令将使Delve控制一个已经运行的进程,并开始一个新的调试会话。 当退出调试会话时,你可以选择让该进程继续运行或杀死它。
debug默认情况下,没有参数,Delve将编译当前目录下的 "main "包,并开始调试。或者,你可以指定一个包的名字,Delve将编译该包,并开始一个新的调试会话。
exec使Delve执行二进制文件,并立即附加到它,开始一个新的调试会话。请注意,如果二进制文件在编译时没有关闭优化功能,可能很难正确地调试它。请考虑在Go 1.10或更高版本上用-gcflags=“all=-N -l"编译调试二进制文件,在Go的早期版本上用-gcflags=”-N -l"。
testtest命令允许你在单元测试的背景下开始一个新的调试会话。默认情况下,Delve将调试当前目录下的测试。另外,你可以指定一个包的名称,Delve将在该包中调试测试。双破折号–可以用来传递参数给测试程序。
version查看dlv版本

进入 dlv 调试后的指令

命令缩写解释
breakb设置断点
breakpointsbp查看当前所有断点
clear/删除断点
clearall/删除多个断点
toggle/启用或关闭断点
continuec继续执行到一个断点或者程序结束
nextn执行下一行代码
restartr重新执行程序
steps执行代码的下一步
step-instructionsi执行下一行机器码
stepoutso跳出当前执行函数
args/打印函数input
display/打印加入到display的变量的值,每次执行下一行代码或下一个断点时
locals/打印局部变量
printp打印表达式的结果
set/设置某个变量的值
vars/查看全局变量
whatis/查看变量类型
disassembledisass查看反编译后的代码,机器码
exitquit/q退出
funcs/打印程序用到的所有函数
helph帮助信息
listls/l打印代码
examinememx检查给定地址的原始内存

GoLand 的断点调试

GoLand 使用的也是 delve 来进行调试的,它使用的是 --listen 参数来启动了一个服务,本篇就不做详细介绍了。

1. go build -gcflags "all=-N -l" -o main.exe
2. dlv --listen=127.0.0.1:5739 --headless=true --api-version=2 --check-go-version=false --only-same-user=false exec main.exe --

使用 GoLand 进行调试也很简单,都是图形化界面,加断点即直接点击行号,开始调试,点击右上角的小甲壳虫即可。

总结

delve 工具非常强大,本篇介绍了其基础使用,希望在遇到问题的时候可以快速的进行问题排查,如果安装了 GoLand,也可以使用 Goland 进行调试,相对来说更加的快捷,但遇到一些需要查看汇编代码或者其他高阶应用时,我还没发现 GoLand 如何使用,所以 delve 的命令使用最好也是要了解的,不会到时候一头雾水。

参考

  • github 官网
  • 为什么 Go 不支持 []T 转换为 []interface
  • Delve调试器-Go语言高级编程
  • GO delve(dlv)调试工具笔记及实操

http://lihuaxi.xjx100.cn/news/797751.html

相关文章

logback配置指定某个包下日志单独输入独立文件

在logback-spring.xml配置文件中添加如下配置:<appender name="OPR_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/opr.log</file><rollingPolicy class="ch.qos.logback.core.rolling.Size…

Metasploitable靶场

1 基本环境 虚拟机账号密码:msfadmin/msfadmin kali主机IP:192.168.5.136 metasploitable2靶机IP:192.168.5.160 使用浏览器登录靶机: 2 信息收集└─# nmap 192.168.5.16 Starting Nmap 7.92 ( https://nmap.org ) at 2023-02-03 01:30 EST Nmap scan report for 192.168.…

JavaScript高级 ES7-ES13 新特性

1. ES7 1. Array Includes 在ES7之前&#xff0c;如果我们想判断一个数组中是否包含某个元素&#xff0c;需要通过 indexOf 获取结果&#xff0c;并且判断是否为 -1 在ES7中&#xff0c;我们可以通过includes来判断一个数组中是否包含一个指定的元素&#xff0c;根据情况&am…

Qt-源码部分编译-C++

-debug 只编译出 debug 版本&#xff0c;是默认选项&#xff08;相对于 -release 选项&#xff09; -debug-and-release 两种版本都编译 -opensource 使用 opensource 版本的Qt&#xff08;相对于 -commercial 选项&#xff09; -c11 打开 C11 的支持 -shared 使用共享库&#…

多个 List 如何取交集、并集、去重并集、差集?

因为如果 List 里面装的是对象&#xff0c;一定要重写 equals 和 hashcode 方法(都是 Object 类下面的方法); 不然比较的是堆内存地址&#xff0c;那么本文也就毫无意义了。 /*** 对象类型的处理*/public static void test1() {List<Student> list1 new ArrayList<&…

C/C++ 内存泄漏检测

C/C 内存泄漏检测内存泄漏的两个问题使用宏定义覆盖 malloc 和 free 函数使用 hook 钩子最近学习了 C/C 内存泄漏检测的相关知识&#xff0c;写博客记录一下。 内存泄漏的两个问题 是否有内存泄漏&#xff1f;内存泄漏是在代码的哪一行&#xff1f; 检测内存泄漏主要从上面两…

软件测试项目实战,我们拿到项目第一步应该怎么做【附过程文档】

对于从事软件研发的组织来说&#xff0c;工作类型至少包括项目管理、产品设计、编码、测试、质量保证和软件配置管理&#xff0c;以及其它人员&#xff0c;如文档编制人员和美工人员/系统硬件管理人员等。根据职能需要&#xff0c;可以以半独立方式进行部门和项目的矩阵管理&am…

关于fuse的常用启动参数

1、启动fuse用户态守护进程 参数比较多&#xff0c;一般正常使用需要的命令类似下面即可 /myfuseexe 源挂载路径 目的挂载路径 -o allow_other -o auto_unmount 例如&#xff1a; /myfuseexe /src /data/src -o allow_other -o auto_unmount 把/src目录挂载到/dat…