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-clientdlv-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(采用的packagespf13cobra,相信玩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功能,所有clientsrever端服务都是通过JSON-PRC完成的