-
Notifications
You must be signed in to change notification settings - Fork 7
4 Linux系统与CPU调试支持
在第2章里,我们介绍了Linux下对进程的隔离机制。一个进程是没办法直接读写另一个进程内存地址空间的数据的,更别说控制其行为了。而对于调试器来说,这些功能却至关重要。为了满足调试器的需求,操作系统乃至硬件架构都提供了相关的特性来支持调试。
首先看Linux系统层面,当调试器进程尝试控制被调试进程和读写其内存数据的时候,如何向操作系统发起请求呢?操作系统提供服务申请和权限审核的途径是什么呢?系统调用?没错,就是系统调用。Linux系统提供了ptrace(2)
这个系统调用来支持调试行为。其接口定义如下:
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
其中 request 参数是要执行的请求类型,这个类型决定后面几个参数是否需要传入有意义的值,request 的主要取值和含义有:
PTRACE_TRACEME —— 表示这个进程要被其父进程跟踪, 忽略 pid, addr, data 参数
PTRACE_PEEKTEXT —— 从addr指向的地址读取一个 word(4字节), 忽略 data 参数
PTRACE_PEEKDATA —— 等同 PTRACE_PEEKTEXT,Linux 未区分代码和数据地址空间
PTRACE_POKETEXT —— 往内存地址中写入一个 word
PTRACE_POKEDATA —— 等同 PTRACE_POKETEXT, Linux 未区分代码和数据地址空间
PTRACE_CONT —— 继续执行
PTRACE_KILL —— 结束子进程,使其退出
PTRACE_SINGLESTEP —— 设置单步执行标志
PTRACE_GETREGS —— 读取寄存器值
PTRACE_SETREGS —— 设置寄存器值
PTRACE_GETFPREGS —— 读取浮点寄存器
PTRACE_SETFPREGS —— 设置浮点寄存器
PTRACE_ATTACH —— 跟踪指定 pid 的进程(要有权限)
PTRACE_DETACH —— 结束跟踪
PTRACE_SYSCALL —— 在每次执行系统调用前和执行系统调用后停下来
... ...
其他的参数可以man 2 ptrace
查看,这里不在赘述。不过需要留意的是错误判断:
On success, the PTRACE_PEEK* requests return the requested data (but see NOTES), while other requests return zero.
On error, all requests return -1, and errno is set appropriately. Since the value returned by a successful PTRACE_PEEK* request may be -1, the caller must clear errno before the call, and then check it afterward to determine whether or not an error occurred.
PTRACE_PEEK*
系列的操作有点反人类,我觉得这个设计就有问题,干嘛忽略data
参数又拿返回值表示。要么是历史包袱,要么就是扯淡的设计。
Intel CPU 提供的调试相关的机制有:
- 陷阱指令
- 单步指令
- 调试寄存器与断点检测
- 任务切换时的自陷
《Intel® 64 and IA-32 Architectures Software Developer’s Manual》Volume 3: System Programming Guide 的第17章 DEBUG, BRANCH PROFILE, TSC, AND RESOURCE MONITORING FEATURES 详细介绍了调试相关的机制和原理,本着暂时用不到的东西就不要提的原则,这里只介绍后续要用到的陷阱指令INT3(0xCC)。调试器会在被调试程序添加断点的指令位置保存原先的指令,覆盖写入INT3指令(0xCC),然后继续执行被调试程序,当被调试进程执行INT3指令会导致CPU产生一个异常(#BP),暂停执行并由异常处理机制会通知到内核,继而通知到调试器进行下一步的处理。陷阱指令除了INT3(0xCC)还有INT4(0xCD)。如果你在windows操作系统下使用Visual Studio写过代码的话,也许你已经见过INT3了,只是你没有认出来。
还记得printf
打印字符串出来的一堆"烫烫烫"吗?原因其实很简单,我在知乎上回答过的。