> ## Content Index
> Fetch the complete content index at: https://blog.hacking.pub/llms.txt
> Use this file to discover other available public pages before exploring further.

# dlv 源码阅读
- URL: https://blog.hacking.pub/2018/12/27/dlv-yuan-ma-yue-du/
- Published: 2018-12-27T12:45:42.000Z
- Updated: 2018-12-27T12:46:28.000Z
- Author: chainhelen
- Tags: dlv, delve, 源码剖析

#### 一. 目录介绍

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` 的信息

> 参考  
> [DWARF, 说不定你也需要它哦](https://www.jianshu.com/p/20dfe4fe1b3f?ref=blog.hacking.pub)  
> [中断、异常、信号](https://blog.csdn.net/zat111/article/details/36420903/?ref=blog.hacking.pub)  
> [go语言词法作用域调试信息](https://go-review.googlesource.com/c/go/+/29591?ref=blog.hacking.pub)