CS110L-Lab/proj-1/deet/implement-note.md
2023-03-07 14:18:25 +00:00

83 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Project-1 DEET 实现笔记
> 这东西虽然不需要啥特别的思考(基本上照着实验指导说明写就行了),但是写起来还是费了我一番功夫,故而还是记录一下实现的主要内容和遇到的问题。
## 总述
这个 project 是去用 `ptrace` 相关的系统调用来实现一个简单的调试器,主要只有断点和继续执行这两个功能,十分的简陋。关键的 crate 包(在实现过程中需要用到的)有以下两个:
- `gimli`DWARF 调试信息的包装库,用来解析编译器塞进可执行文件里面的调试信息,我们(其实是 starter code 的编写者)需要用它来解析指令、行号、函数名等部分的对应关系。原有的 starter code 中用到的部分这个包的 API 发生了一些不兼容的变化,因此需要做一些适配工作。
- `nix`Linux 系统 API 的封装,核心的 ptrace 便是由这个包提供。
## 相关知识
1. ptrace 建立的流程:父进程 fork然后在子进程里面发出 `TRACEME` 请求,然后 `execve` 执行真正的需要被调试的程序。一般而言,这个 `execve` 执行完成后,子进程会立即收到一个 SIGTRAP 然后中断,等待父进程 `waitpid` 取得它的状态,决定下一步要干啥。
2. ptrace 本质上只是一个系统调用,
```c
long ptrace(enum __ptrace_request request, pid_t pid,
void *addr, void *data);
```
只不过 rust 的封装库把它变得更加的符合这门语言的风格。
3. waitpidwait 系列的函数本质上是获取子进程的运行状态变化(而不是简单的退出)。除此之外,对于一个已经终止的子进程,这个调用还起到通知系统回收资源的作用。
4. 断点原理:最基本的断点是指令断点,原理是通过修改子进程的 TEXT 段内存,把需要断点的指令的第一个字节(指令地址对应的字节)设置为 `0xCC`,这个是一条 SYSCALL然后执行到这里的时候就陷入内核了然后内核检查一下发现是调试用的就把进程停下来并返回一个 SIGTRAP。
## 每个 MileStone 的说明
### Milestone 0
看代码,这个好像没啥可说的,就大概看一眼每个文件是在干什么。实际上在做实验的过程中,只需要关注 `debugger_command.rs` `debugger.rs` `inferior.rs` 这三个文件而已。
- `debugger_command.rs`:解析输入的动作命令并将参数打包进 enum 里面
- `debugger.rs`:程序的主要逻辑,一个大循环解析每次的命令并执行
- `inferior.rs`:每次运行的子进程的封装,实现一些直接操作进程的方法
### Milestone 1
实现新建被调试的子进程并运行。也就是实现 run 命令。
主要两个问题:
1.`std::Command` 的应用,除了 spawn 一个新的子进程之外,需要调用 `pre_exec` 使得子进程在 fork 出来之后会先调用 `traceme`,从而建立父进程对子进程的 `ptrace` 控制。
2. `wait` 相关的问题。在创建进程后,首先需要 wait 一下来确保子进程被 TRAP 住了(虽然不确认也问题不大);然后就是在正式运行之后,需要 wait 到子进程停下来(无论是断点还是退出还是爆炸之类的),这个 wait 完了会返回几种状态,需要进行处理。
### Milestone 2
实现中断的继续执行以及进程清理。这里的中断是 `Ctrl+C` 对于 ptrace 进程的特殊结果(一般是发一个 SIGTERM而这里发的则是 SIGINT 使得它停下来)而不是断点。因此只需要简单的调用 ptrace 的 cont 就行。这里的 cont 操作和前面 run 用到的代码一样,因此可以封装成一个内部方法,这件事情我写到后面才想起来,一开始直接复制了两段一样的代码,简直像个傻子。
主要问题是:它要求实现一个功能,在中断之后再执行 run 命令,需要 kill 掉中断的进程然后重新起一个。问题在于我也不知道在 ptrace 的 kill 之后要调用一个 wait 啊。如果不调用 wait 会导致僵尸进程(因为死掉之后的 wait 主要是为了通知系统清理子进程),直到父进程结束才会被清理。更离谱的是, wait 返回的结果居然是 `Signal(SIGKILL)` 而不是一个 `Exit(137)`,如果 assert 的话就会爆炸。
如果用 ps 命令看的话,僵尸进程会在后面写一个 `defunct`
### Milestone 3
实现 backtrace。这个没有什么需要自己调的东西把讲义上写的东西实现了就好。对于一个已经写过 OS 的人来说,栈回溯实在算不上什么难度,连伪代码都给出来了。
### Milestone 4
在程序中断的时候打印停下来的文件和行数。更简单了wait 在返回 Signal 的时候,还会返回中断时的指令指针,直接调用 dwarf 的 `get_line_from_addr` 就行。
### Milestone 5
实现断点设置和停止。也没有什么需要自己调的东西,对着讲义写就完事了。
不过这里可能是由于编译器版本的问题(也有可能是我自己的目录太深了?),无法打印源代码文件名和行号对应信息。原因是,有一部分信息本来是直接写在字段里面的,结果在我这里给挪到了单独的字符串区域 `.debug_line_str` 然后原来的地方填了个 offset所以有一部分信息解读不出来。我于是就更新了一下 gimli 依赖库的版本,然后参考了手册和其他部分的实现,把这部分的解析给补上了,这样就能打印行号和文件名了。
### Milestone 6
实现断点后继续执行。这是整个 project 的最难写的部分(虽然主要原因是我前面写的太随意导致我重构了一部分代码)。继续执行的原理不难理解,就是在断点之后先把被替换成 `0xcc` 的指令恢复,然后单步执行一条指令,然后把 `0xcc` 再插回去,最后 continue。
第一个问题,因为这里恢复指令需要记录原有指令被替换掉的字节,如果没有 run 的话,内存里面是啥也没有的,因此需要等 run 里面把子进程加载之后才能去记录这些内容,导致写的有点冗余。我没改前一个 milestone 的 `breakpoint vector`,直接新加了一个 `hashmap`,在 run 的时候首先遍历 vector把里面的断点打进去然后把换出来的指令数据存进 map 里面。如果是中断状态下打断点,就直接打进去,不需要 continue 的时候再一起插一次断点了。不过这就引出了第二个问题。
第二个问题,我想把插入断点并记录 map 这个操作封装一个内部方法,然后在 run 之前就需要把 vector 里面的所有断点都做一次这个操作。用 `for addr in &self.vec` 遍历的时候,编译器报错了,它告诉我不能在这个里面调用具有 `(&mut self)` 参数的方法。会报错 &self 不可变借用,而函数需要一个可变借用。形象的解释就是:它怕我们在函数里修改 `self.vec` 从而导致出错,虽然实际上写的时候并不会去修改这个东西,但是在调用方法的时候,会要求完整取得引用的所有权。但是我们把这个函数里面的代码 inline 出来就没问题了(你这里不还是 self 的可变引用吗),这个好像和 self 的特殊性质以及 NLL 有关,我没细究,太复杂了。
第三个问题,因为在 continue 之前,需要判断上一次停下来是不是因为断点,所以需要记录上一次 wait 的状态结果。本来我是用 inferior 的 None 来标志程序已经结束的,这样已经记录了上次状态,感觉这个 None 判断就有点多余,应该直接用状态来判断(不过我懒得重构了)
### Milestone 7
这个就没啥东西了,把 break 处理代码那边多写两个 if 就完事了。
## 附加任务?
没写,因为感觉意义不大(从练习 rust 的角度而言,如果是从一个 debugger 的角度,前面相当于啥也没有啊),不过看了一眼。
写一下 `next line` 指令的思路:因为我们有行号和指令的对应表,因此我们只需要不停地指令单步执行,直到下一条指令(其实就是 wait 返回的 `%rip`)不在同一行了。但是有可能会出现一行代码对应的指令不是连续的这种问题,不过好像 `gdb` 都没有很好的解决方案,所以无所谓了。
`print` 的思路:`dwarf` 里面给了每个函数对应的变量表,看上去只要查表打印就行了,但是实际情况会比较麻烦一些,比如要判断作用域,比如复合结构的解析(结构体成员、指针等)等等,需要去深入研究一下 API 和 DWARF 手册。