一. 目录介绍
1._fixtures
这个文件夹下面主要放一些用于测试的应用程序源码
2.assets
存放了4个代表delve的图标
3.cmd
这个是server/client的命令行入口
主要从commands.go
这个文件体现,主要使用spf13/cobra
来做命令行工具(相信喜欢使用vim的人都应该听过spf13)
主要命令有debug
、trace
、attach
等,但是这些命令核心都是在使用gobuild
、execute
,所以基本看这两个函数即可
gobuild
主要是对需要调试源码的进行编译
execute
主要是对已经编译好的二进制进行运行、attach,用于调试(已经运行的进程,就直接attach)
4.Documentation
主要放dlv
的文档,另外关于cmd
的文档都是通过script/gen-usage-docs.go
生成的
就是说大部分都是cmd
目录的help
注释
5.service
service
入口是/service/rpccommon/server.go
,主要是由cmd
来调用server
的Run()、Close()
运行的
而server
本身是一个interface
,用于做跨平台,对于不同服务采用不同的debug_*.go
或者pkg
6.pkg
pkg属于内部的package,对外暴露最核心的文件是proc.go
,对应的interface
是Process
,而实现跨平台,内部/pkg/native/proc.go
有对应的实现
二. 断点
对于dlv而言,是怎么实现断点的呢?拿linux系统来说,有ptrace可以实现对进程的监控
著名命令strace就是利用ptrace实现的
对于计算机来说,异常控制流
(ECF)几乎是无处不在的
按照深入理解计算机系统
一书所言,我们把异常
分成中断
、陷阱
、系统调用
、故障
、终止
中断(主要指硬件中断),来自I/O设备的信号,异步,总是返回到下一条指令
陷阱(包括系统调用),有意的异常,同步,总是返回到下一条指令
故障,潜在可恢复的错误,同步,可能会返回到当前指令(注意跟上面不同)
终止,不可恢复的错误,同步,不会返回
差异点:
系统调用和普通函数调用
普通的函数调用是在用户模式下,用户模式限制了函数可以执行的指令的类型,而且只能访问与调用函数相同的栈
系统调用运行在内核模式,可以访问内核中的栈
故障 最典型的事例就是缺页异常
而断点的实行就是利用了陷阱
int 3
当设置某一指令为断点时,调试器就会把该处指令替换成int 3
,系统执行到该处就会中断,恢复int 3
之前的指令,将现场返回给用户(这个信号会被tracer捕获到,并且traced的进程会停止)
被跟踪进程收到任何信号(除SIGKILL)都会停止,将信号转给跟踪器(触发wait)
PTRACE_SYSCALL:跟踪系统调用,每次系统调用会收到一个SIGTRAP
PTRACE_SINGLESTEP:跟踪单步,每执行完一个指令收到一个SIGTRAP
PTRACE_CONT:继续
断点(int 3 指令)会触发一个SIGTRAP
dlv 对应的下断点文件在/pkg/proc/native/ptrace_*.go
本质上也是调用了官方库golang.org/x/sys/unix
三. 单步
关键在于调试信息,使用的是dwarf
的一套规范(debug with attribute record format)
还有一部分的符号表信息来源与 /pkg/proc/bininfo.go
的 gosym.go查看符号表
所谓的当前行单步的实现,就是优先找到当前行所在的function
区域
然后对该function
源码的每一行找到对应的pc
指令位置,进行下断点
四.变量
打印变量,非常依赖dwarf提供的信息
对于拥有词法作用域的go语言来说,ex:
1 package main
2
3 import "fmt"
4
5 func main() {
6 w := 1
7 s := 2
8
9 func() {
10 fmt.Printf("%d\n", w)
11 }()
12 fmt.Printf("%d\n", s)
13 }
如果当前断点下在10 line
,执行print w
是可以显示1
,但是s
就不可以,因为依赖的生成的dwarf
并未没有s
的信息