ptrace 函数原型
#include <sys/ptrace.h>
long ptrace(int request, pid_t pid, void *addr, void *data);
-
request
:指定操作类型(如读取内存、设置断点等)。 -
pid
:目标进程的 PID(进程 ID)。 -
addr
:内存地址或偏移量(取决于request
)。 -
data
:要写入的数据(某些request
需要)。
request 参数详解
Request | 功能 |
---|---|
PTRACE_TRACEME | 让当前进程被父进程跟踪(调试器启动子进程时使用) |
PTRACE_ATTACH | 附加到目标进程(调试器附加到正在运行的进程) |
PTRACE_DETACH | 解除跟踪 |
PTRACE_PEEKTEXT / PTRACE_PEEKDATA | 读取目标进程的内存(addr 是内存地址) |
PTRACE_POKETEXT / PTRACE_POKEDATA | 写入目标进程的内存 |
PTRACE_GETREGS | 读取目标进程的寄存器(data 指向 struct user_regs_struct ) |
PTRACE_SETREGS | 修改目标进程的寄存器 |
PTRACE_SINGLESTEP | 单步执行(类似 gdb 的 si ) |
PTRACE_CONT | 继续执行(类似 gdb 的 continue ) |
PTRACE_SYSCALL | 在进入/退出系统调用时暂停(strace 使用) |
PTRACE_KILL | 终止目标进程 |
基本工作流程
- 调试器(父进程)启动被调试进程(子进程)
- 子进程调用
PTRACE_TRACEME
,表示愿意被父进程跟踪。 - 父进程使用
waitpidwaitpid- 父进程使用
waitpidwaitpid()等待子进程暂停(如遇到断点)。()
等待子进程暂停(如遇到断点)。
- 子进程调用
- 调试器附加到正在运行的进程
- 父进程调用
PTRACE_ATTACH
附加到目标进程。 - 目标进程会收到
SIGSTOP
信号并暂停。
- 父进程调用
- 调试器读取/修改目标进程
- 使用
PTRACE_PEEKTEXT
读取内存。 - 使用
PTRACE_POKETEXT
修改内存(如设置断点)。 - 使用
PTRACE_GETREGS
读取寄存器。
- 使用
- 控制目标进程执行
PTRACE_CONT
继续执行。PTRACE_SINGLESTEP
单步执行。
- 结束调试
traceMe
子进程调用 PTRACE_TRACEME,表明这个进程由它的父进程来跟踪。任何发给这个进程的信号 signal(除了 SIGKILL)将导致该进程停止运行,而它的父进程会通过 wait() 获得通知。另外,该进程之后所有对 exec() 的调用都将使操作系统产生一个 SIGTRAP 信号发送给它,这让父进程有机会在新程序开始执行之前获得对子进程的控制权。
PTRACE_PEEKUSER
PTRACE_PEEKUSER Read a word at offset addr in the tracee’s USER area, which holds the registers and other information about the process (see <sys/user.h>). The word is returned as the result of the ptrace() call. Typically, the offset must be word- aligned, though this might vary by architecture. See NOTES. (data is ignored; but see NOTES.)
PTRACE_PEEKUSER 读取被跟踪进程 USER 区域(包含寄存器和其他进程信息,参见)中偏移量 addr 处的一个字,并将该字作为 ptrace() 调用的结果返回。通常,偏移量必须按字对齐,但这可能因架构而异。参见注意事项。(data 被忽略;但请参阅注意事项。)
在 arm64 下的 user.h
struct user_regs_struct
{
unsigned long long regs[31];
unsigned long long sp;
unsigned long long pc;
unsigned long long pstate;
};
struct user_fpsimd_struct
{
__uint128_t vregs[32];
unsigned int fpsr;
unsigned int fpcr;
};
还是没看懂怎么用,还是用下面这个👇
PTRACE_GETREGSET
这么 get 寄存器组:
#include <linux/elf.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t child;
child = fork();
if (child == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execl("/bin/ls", "ls", NULL); // start
} else {
int status;
ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);
while (1) {
ptrace(PTRACE_SYSCALL, child, 0, 0);
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
printf("Child exited with status %d\n", WEXITSTATUS(status));
break;
}
if ((status >> 8) == (SIGTRAP)) {
struct user_regs_struct regs;
struct iovec io = {.iov_base = ®s, .iov_len = sizeof(regs)};
// 使用 PTRACE_GETREGSET 读取寄存器
if (ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &io) == 0) {
printf("System call number: %lld\n",
(long long)regs.regs[8]); // arm
} else {
perror("ptrace(GETREGSET)");
}
}
}
}
return 0;
}
要用头文件 <sys/uio.h>
中的 iovec
中转,提高兼容性