目前 Go 语言支持 GDB、LLDB 和 Delve 几种调试器。其中 GDB 是最早支持的调试工具,LLDB 是 macOS 系统推荐的标准调试工具。但是 GDB 和 LLDB 对 Go 语言的专有特性都缺乏很大支持,而只有 Delve 是专门为 Go 语言设计开发的调试工具。而且 Delve 本身也是采用 Go 语言开发,对 Windows 平台也提供了一样的支持。本节我们基于 Delve 简单解释如何调试 Go 汇编程序。

安装

Github地址: https://github.com/go-delve/delve/
如果是在本地调试,直接通过go install命令将其安装到本地的$GOPATH/bin下即可

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

容器环境下由于不一定支持 go,需要先安装 go 语言环境,会比较麻烦,可以直接将本地下载好的dlv二进制命令复制到$PATH的某一个目录中。安装好后,通过执行dlv version验证是否可用。

注意

使用go build编译时,默认会进行一些优化,会影响调试过程。
因此,对于对于需要进行调试的程序,在编译时最好设置-gcflags把优化关掉。

  • 在>= 1.10的 golang 版本中,设置-gcflags="all=-N -l"
  • 在<1.10版本的 golang 中,设置-gcflags="-N -l"
    其中:
  • -N表示不优化代码,并生成调试信息。
  • -l表示禁用函数内联优化
    如下面命令将当前文件夹下的代码编译成可执行文件main,并禁止优化:

    go build -gcflags="all=-N -l" -o main .

    基本使用

    Delve 的基本命令:

    (dlv) break main.main  # 设置断点
    (dlv) continue        # 继续执行
    (dlv) next           # 下一步
    (dlv) step           # 步入
    (dlv) print 变量名    # 打印变量
    
    (dlv) b main.main    # 在main函数设置断点
    (dlv) bp 文件名:行号  # 在特定行设置断点
    (dlv) c             # continue 继续运行
    (dlv) n             # next 下一步
    (dlv) s             # step 步入
    (dlv) p 变量名       # print 打印变量
    (dlv) vars          # 显示包级变量
    (dlv) locals        # 显示本地变量
    (dlv) bt            # 显示调用栈

常用命令

attach

用于调试一个已经存在的进程,这个命令一般在调试 web 程序时使用。如下:

  • 使用lsof命令查看占用某个端口的进程 pid
  • 使用dlv attach $pid启动调试该进程
    比如假定某个 web 程序的 http端口为 8080,进程号为10001

    lsof -i:8080
    dlv attach 10001

    exec

    如果没有现成的进程,而只有一个二进制可执行文件,可以使用dlv exec命令启动一个进程并进行调试,如上面生成的main

    dlv exec ./main

    如果 main 程序本身需要一些参数,可以通过--来传递参数,比如给上面的 main 传入一个 conf 参数

    dlv exec ./main -- conf=./conf.yaml

    debug

    用于直接调试源码文件,这个命令连手动编译都省了,只需要指定调试的包名和源码所在的文件夹即可。

    dlv debug .

    version

    用于查看dlv版本号,如下:

    dlv version
    # WARN[0000] CGO_CFLAGS already set, Cgo code could be optimized.  layer=dlv
    # Delve Debugger
    # Version: 1.21.0
    # Build: $Id: fec0d226b2c2cce1567d5f59169660cf61dc1efe $

    调试指令

    通过execdebugattach进入 dlv 的调试终端后,需要执行调试指令才能完成诸如下一步、退出当前函数、查看变量值等目的。

    断点管理

    主要是断点的增删改查

指令缩写作用
breakb添加断点
breakpointsbp用于查看设置的所有断点,每个断点都有一个编号
clear 如clear 1,表示删除编号为 1 的断点
clearall 删除所有断点
toggle 用于启用/禁用断点。如toggle 1
conditioncond用于设置条件断点,如cond 2 i == 10指定断点 2 在 i等于 10 时执行

需要注意的是,使用 break 创建断点时,有几种方法:

  • b 包名.方法名: 在指定包的函数中设置断点,如b main.main。如果函数名全局唯一,则不用指定包名
  • b 文件名:行数: 在指定的 go 文件的指定行设置断点,如b main.go:14

调试执行

主要用于控制程序的执行

指令缩写作用
nextn执行到下一行,如果是函数,不会进入函数
continuec执行到下一个断点处或结束执行
steps执行到下一步,如果是函数,会进入函数内部
stepoutso跳出当前函数
restartr重新执行程序,断点会保留。注意无法用于 attach 的进程
step-instructionsi执行到下一行机器码,一般在查看汇编代码时使用
rebuild 重新编译程序并执行,断点会保留。无法用于 attach 和 exec

参数管理

主要用于对设置和查看变量、参数等

指令缩写作用
printp查看变量或表达式的值
whatis 查看变量类型
args 查看函数的入参
locals 查看函数的局部变量
vars 查看全局变量
set 设置某个变量的值
display 将变量加入/移除监控列表、或查看监控列表

注意上述locals/vars/display都支持指定正则,具体用法可以使用h locals查看。

其他

指令缩写作用
exitq退出调试会话
disassembledisass用于查看指定函数的汇编代码
funcs 查看函数,同样支持正则
helph查看使用手册
listls/l查看源代码

总结

本文介绍了dlv工具的基本使用姿势,掌握该工具,不仅能够辅助我们进行调试,以后想学习一些项目的原理时,也能够很快摸清楚调用链路。

后续我的其他分析原理性的文章中,也会使用使用这一工具对源代码进行分析。

References

最后修改:2024 年 12 月 18 日
如果觉得我的文章对你有用,请随意赞赏