dlv
dlv
针对go语言的调试器,内部架构介绍
--headless
package main
import (
"fmt"
)
func main() {
m := 120
fmt.Printf("Hello world\n")
fmt.Printf("m = %d\n", m)
}
终端1
dlv debug --headless --api-version=2 --log --listen=127.0.0.1:8181 tmp.go
client
1 直接使用官方提供的dlv作为客户端连接
dlv connect 127.0.0.1:8181
break tmp.go:8
c
2 自己使用tcp来发送json-rpc
首先,我们使用tcpdump抓一下dlv-client
和dlv-rp2
服务之间的报文,在tmp.go:8
处下断点
-XX 能将报文转成assic可读性
-q 能精简报文
tcpdump -XX -i lo tcp and port 8181
或者 tcpdump -q -XX -i lo tcp and port 8181
提取一下有用的信息如下
localhost.44386 > localhost.8181 发送
{"method":"RPCServer.FindLocation","params":[{"Scope":{"GoroutineID":-1,"Frame":0},"Loc":"tmp.go:8"}],"id":38}
localhost.8181 > localhost.44062 回复
{"id":38,"result":{"Locations":[{"pc":4851695,"file":"/root/workspace/go/src/tmp.go","line":8,"function":{"name":"main.main","value":4851648,"type":0,"goType":0,"optimized":false}}]},"error":null}
localhost.44386 > localhost.8181 发送
{"method":"RPCServer.CreateBreakpoint","params":[{"Breakpoint":{"id":0,"name":"","addr":4851695,"file":"","line":0,"Cond":"","continue":false,"goroutine":false,"stacktrace":0,"LoadArgs":null,"LoadLocals":null,"hitCount":null,"totalHitCount":0}}],"id":39}
localhost.8181 > localhost.44062 回复
{"id":39,"result":{"Breakpoint":{"id":1,"name":"","addr":4851695,"file":"/root/workspace/go/src/tmp.go","line":8,"functionName":"main.main","Cond":"","continue":false,"goroutine":false,"stacktrace":0,"LoadArgs":null,"LoadLocals":null,"hitCount":{},"totalHitCount":0}},"error":null}
如果我再次发报文设置断点
localhost.44386 > localhost.8181 发送
{"method":"RPCServer.CreateBreakpoint","params":[{"Breakpoint":{"id":0,"name":"","addr":4851695,"file":"","line":0,"Cond":"","continue":false,"goroutine":false,"stacktrace":0,"LoadArgs":null,"LoadLocals":null,"hitCount":null,"totalHitCount":0}}],"id":39}
那么
localhost.8181 > localhost.44062 回复
{"id":39,"result":null,"error":"Breakpoint exists at /root/workspace/go/src/tmp.go:8 at 4a07ef"}
那么可以使用nc 127.0.0.1 8181
直接发送以上报文
$ nc 127.0.0.1 8181
{"method":"RPCServer.FindLocation","params":[{"Scope":{"GoroutineID":-1,"Frame":0},"Loc":"tmp.go:8"}],"id":38}
{"id":38,"result":{"Locations":[{"pc":4851695,"file":"/root/workspace/go/src/tmp.go","line":8,"function":{"name":"main.main","value":4851648,"type":0,"goType":0,"optimized":false}}]},"error":null}
{"method":"RPCServer.CreateBreakpoint","params":[{"Breakpoint":{"id":0,"name":"","addr":4851695,"file":"","line":0,"Cond":"","continue":false,"goroutine":false,"stacktrace":0,"LoadArgs":null,"LoadLocals":null,"hitCount":null,"totalHitCount":0}}],"id":39}
{"id":39,"result":{"Breakpoint":{"id":1,"name":"","addr":4851695,"file":"/root/workspace/go/src/tmp.go","line":8,"functionName":"main.main","Cond":"","continue":false,"goroutine":false,"stacktrace":0,"LoadArgs":null,"LoadLocals":null,"hitCount":{},"totalHitCount":0}},"error":null}
{"method":"RPCServer.FindLocation","params":[{"Scope":{"GoroutineID":-1,"Frame":0},"Loc":"tmp.go:8"}],"id":38}
{"id":38,"result":{"Locations":[{"pc":4851695,"file":"/root/workspace/go/src/tmp.go","line":8,"function":{"name":"main.main","value":4851648,"type":0,"goType":0,"optimized":false}}]},"error":null}{"method":"RPCServer.CreateBreakpoint","params":[{"Breakpoint":{"id":0,"name":"","addr":4851695,"file":"","line":0,"Cond":"","continue":false,"goroutine":false,"stacktrace":0,"LoadArgs":null,"LoadLocals":null,"hitCount":null,"totalHitCount":0}}],"id":39}
{"id":39,"result":null,"error":"Breakpoint exists at /root/workspace/go/src/tmp.go:8 at 4a07ef"}
自己调试自己
dlv debug /root/workspace/go/src/github.com/derekparker/delve/cmd/dlv/main.go -- debug --headless --api-version=2 --log --listen=127.0.0.1:8181 tmp.go
多文件调试
dlv debug
并不支持main package
中含有多个文件的情况,之前观赏gor
的代码发现的这种情况
所以交了一次pull request
通过修改过后的dlv,调试多文件 gor 并附带参数
find . -maxdepth 1 -name "*.go" | awk -F '/' '{s=s" "$2}END{print "./testdlv debug --headless --api-version=2 --log --listen=127.0.0.1:8181 " s " -- --input-raw :8000 --output-stdout"}' |bash
架构
dlv
的命令主要有 dlv debug
dlv test
dlv trace
所有命令行的入口都是在cmd/dlv/commands.go
(采用的package
是spf13
的cobra
,相信玩vim
都知道这个人)
就拿debug
来看,首先会根据代码生成一个名为debug
的临时文件(调试完成后defer remove
掉)
传到service/rpccommon/server.go
里面创建一个server
变量(rpccommon.NewServer)
server.run
运行debug
文件,注意这里面涉及到ptrace
(linux下的情况)去跟踪程序(这里可以用ps命令看一下进程处于trace
状态)
dlv
的上层都是做一些逻辑(service
目录)行为,最底层最核心的都是在其仓库pkg
的目录使用系统级别提供的进程调试工具,例如linux的ptrace
而dlv connetct
也只是其client
功能,所有client
和srever
端服务都是通过JSON-PRC
完成的