本文转载自微信公众号「脑子进煎鱼了」,作者陈煎鱼。转载本文请联系脑子进煎鱼了公众号。

创新互联建站是一家专业从事成都网站设计、网站建设的网络公司。作为专业网站设计公司,创新互联建站依托的技术实力、以及多年的网站运营经验,为您提供专业的成都网站建设、成都全网营销及网站设计开发服务!
大家好,我是煎鱼。
在 Go 语言中,除了 go tool 工具链中的 pprof、trace 等剖析工具的大利器外。常常还会有小伙伴问,有没有更好用,更精细的,
大家总嫌弃 pprof、trace 等工具,不够细,没法一口气看到根因,或者具体变量...希望能够最好能追到代码级别调试的,看到具体变量的值是怎么样的,随意想怎么看怎么看的那种。
为此今天给大家介绍 Go 语言强大的 Delve (dlv)调试工具,来更深入问题剖析。
我们需要先安装 Go delve,若是 Go1.16 及以后的版本,可以直接执行下述命令安装:
- $ go install github.com/go-delve/delve/cmd/dlv@latest
 
也可以通过 git clone 的方式安装:
- $ git clone https://github.com/go-delve/delve
 - $ cd delve
 - $ go install github.com/go-delve/delve/cmd/dlv
 
在安装完毕后,我们执行 dlv version 命令,查看安装情况:
- $ dlv version
 - Delve Debugger
 - Version: 1.7.0
 - Build: $Id: e353a65161e6ed74952b96bbb62ebfc56090832b $
 
可以明确看到我们所安装的版本是 v1.7.0。
我们计划用一个反转字符串的演示程序来进行 Go 程序的调试。第一部分先是完成 stringer 包的 Reverse 方法。
代码如下:
- package stringer
 - func Reverse(s string) string {
 - r := []rune(s)
 - for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
 - r[i], r[j] = r[j], r[i]
 - }
 - return string(r)
 - }
 
再在具体的 main 启动函数中进行调用。代码如下:
- import (
 - "fmt"
 - "github.com/eddycjy/awesome-project/stringer"
 - )
 - func main() {
 - fmt.Println(stringer.Reverse("脑子进煎鱼了!"))
 - }
 
输出结果:
- !了鱼煎进子脑
 
Delve 是 Go 程序的源代码级调试器。Delve 使您能够通过控制流程的执行与您的程序进行交互,查看变量,提供线程、goroutine、CPU 状态等信息。
其一共支持如下 11 个子命令:
- Available Commands:
 - attach Attach to running process and begin debugging.
 - connect Connect to a headless debug server.
 - core Examine a core dump.
 - dap [EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
 - debug Compile and begin debugging main package in current directory, or the package specified.
 - exec Execute a precompiled binary, and begin a debug session.
 - help Help about any command
 - run Deprecated command. Use 'debug' instead.
 - test Compile test binary and begin debugging program.
 - trace Compile and begin tracing program.
 - version Prints version.
 
我们今天主要用到的是 debug 命令,他能够编译并开始调试当前目录下的主包,或指定的包,是最常用的功能之一。
接下来我们利用这个演示程序来进行 dlv 的深入调试和应用。
执行如下命令:
- awesomeProject dlv debug .
 - Type 'help' for list of commands.
 - (dlv)
 
我们先在演示程序根目录下执行了 debug,进入了 dlv 的交互模式。
再使用关键字 b(break 的缩写)对 main.main 方法设置断点:
- (dlv) b main.main
 - Breakpoint 1 (enabled) set at 0x10cbab3 for main.main() ./main.go:9
 - (dlv)
 
设置完毕后,我们可以看到方法对应的文件名、行数。接着我们可以执行关键字 c(continue 的缩写)跳转到下一个断点处:
- (dlv) c
 - > main.main() ./main.go:9 (hits goroutine(1):1 total:1) (PC: 0x10cbab3)
 - 4: "fmt"
 - 5:
 - 6: "github.com/eddycjy/awesome-project/stringer"
 - 7: )
 - 8:
 - => 9: func main() {
 - 10: fmt.Println(stringer.Reverse("脑子进煎鱼了!"))
 - 11: }
 - (dlv)
 
在断点处,我看可以看到具体的代码块、goroutine、CPU 寄存器地址等运行时信息。
紧接着执行关键字 n(next 的缩写)单步执行程序的下一步:
- (dlv) n
 - > main.main() ./main.go:10 (PC: 0x10cbac1)
 - 5:
 - 6: "github.com/eddycjy/awesome-project/stringer"
 - 7: )
 - 8:
 - 9: func main() {
 - => 10: fmt.Println(stringer.Reverse("脑子进煎鱼了!"))
 - 11: }
 
我们可以看到程序走到了 main.go 文件中的第 10 行中,并且调用了 stringer.Reverse 方法去处理。
此时我们可以执行关键字 s(step 的关键字)进入到这个函数中去继续调试:
- (dlv) s
 - > github.com/eddycjy/awesome-project/stringer.Reverse() ./stringer/string.go:3 (PC: 0x10cb87b)
 - 1: package stringer
 - 2:
 - => 3: func Reverse(s string) string {
 - 4: r := []rune(s)
 - 5: for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
 - 6: r[i], r[j] = r[j], r[i]
 - 7: }
 - 8: return string(r)
 
输入后,调试的光标会到 Reverse 方法上,此时我们可以调用关键字 p(print 的缩写)传出所传入的变量的值:
- (dlv) p s
 - "脑子进煎鱼了!"
 
此处函数的形参变量是 s,输出了 “脑子进煎鱼了!”,与我们所传入的是一致的。
但故事一般没有这么的简单,会用到 Delve 来调试,说明是比较细致、隐患的 BUG。为此我们大多需要更进一步的深入。
我们继续围观 Reverse 方法:
- 5: for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
 - 6: r[i], r[j] = r[j], r[i]
 - 7: }
 
从表现来看,我们常常会怀疑是第 6 行可能是问题的所在。这时可以针对性的对第 6 行进行断点查看:
- (dlv) b 6
 - Breakpoint 2 (enabled) set at 0x10cb92c for github.com/eddycjy/awesome-project/stringer.Reverse() ./stringer/string.go:6
 
设置完断点后,我们只需要执行关键字 c,继续下一步:
- (dlv) c
 - > github.com/eddycjy/awesome-project/stringer.Reverse() ./stringer/string.go:6 (hits goroutine(1):1 total:1) (PC: 0x10cb92c)
 - 1: package stringer
 - 2:
 - 3: func Reverse(s string) string {
 - 4: r := []rune(s)
 - 5: for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
 - => 6: r[i], r[j] = r[j], r[i]
 - 7: }
 - 8: return string(r)
 - 9: }
 
走到对应的代码片段后,执行关键字 locals:
- (dlv) locals
 - r = []int32 len: 7, cap: 32, [...]
 - j = 6
 - i = 0
 
我们就可以看到对应的变量 r, i, j 的值是多少,可以根据此来分析程序流转是否与我们预想的一致。
另外也可以调用关键字 set 去针对特定变量设置期望的值:
- (dlv) set i = 1
 - (dlv) locals
 - r = []int32 len: 7, cap: 32, [...]
 - j = 6
 - i = 1
 
设置后,若还需要继续排查,可以继续调用关键字 c 去定位,这种常用于特定变量的特定值的异常,这样一设置一调试基本就能排查出来了。
在排查完毕后,我们可以执行关键字 r(reset 的缩写):
- (dlv) r
 - Process restarted with PID 56614
 
执行完毕后,整个调试就会重置,像是前面在打断点时所设置的变量值就会恢复。
若要查看设置的断点情况,也可以执行关键字 bp 查看:
- (dlv) bp
 - Breakpoint runtime-fatal-throw (enabled) at 0x1038fc0 for runtime.fatalthrow() /usr/local/Cellar/go/1.16.2/libexec/src/runtime/panic.go:1163 (0)
 - Breakpoint unrecovered-panic (enabled) at 0x1039040 for runtime.fatalpanic() /usr/local/Cellar/go/1.16.2/libexec/src/runtime/panic.go:1190 (0)
 - print runtime.curg._panic.arg
 - Breakpoint 1 (enabled) at 0x10cbab3 for main.main() ./main.go:9 (0)
 - Breakpoint 2 (enabled) at 0x10cb92c for github.com/eddycjy/awesome-project/stringer.Reverse() ./stringer/string.go:6 (0)
 
查看断点情况后,若有部分已经排除了,可以调用关键字 clearall 对一些断点清除:
- (dlv) clearall main.main
 - Breakpoint 1 (enabled) cleared at 0x10cbab3 for main.main() ./main.go:9
 
若不指点断点,则会默认清除全部断点。
在日常的 Go 工程中,若都从 main 方法进入就太繁琐了。我们可以直接借助函数名进行调式定位:
- (dlv) funcs Reverse
 - github.com/eddycjy/awesome-project/stringer.Reverse
 - (dlv) b stringer.Reverse
 - Breakpoint 3 (enabled) set at 0x10cb87b for github.com/eddycjy/awesome-project/stringer.Reverse() ./stringer/string.go:3
 - (dlv) c
 - > github.com/eddycjy/awesome-project/stringer.Reverse() ./stringer/string.go:3 (hits goroutine(1):1 total:1) (PC: 0x10cb87b)
 - 1: package stringer
 - 2:
 - => 3: func Reverse(s string) string {
 - 4: r := []rune(s)
 - 5: for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
 - 6: r[i], r[j] = r[j], r[i]
 - 7: }
 - 8: return string(r)
 
紧接着其他步骤都与先前的一样,进行具体的调试就好了。我们也可以借助 Go 语言的公共函数进行计算:
- (dlv) p len(r)-1
 - 6
 
也可以借助关键字 vars 查看某个包下的所有全局变量的值,例如:vars main。这种方式对于查看全局变量的情况非常有帮助。
排查完毕后,执行关键字 exit 就可以愉快的退出了:
- (dlv) exit
 
解决完问题,可以下班了 :)
在 Go 语言中,Delve 调试工具是与 Go 语言亲和度最高的,因为 Delve 是 Go 语言实现的。其在我们日常工作中,非常常用。
像是假设程序的 for 循环运行到第 N 次才出现 BUG 时,我们就可以通过断点对应的方法和代码块,再设置变量的值,进行具体的查看,就可以解决。
                当前题目:一个Demo学会使用GoDelve调试
                
                标题网址:http://www.csdahua.cn/qtweb/news21/242771.html
            
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网