ptrace
NB的系统调用,常用的strace和gdb都是用ptrace实现的
#include <sys/ptrace.h> long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
|
参数复杂,其他可以查手册(man 2 ptrace)
手册里,tracer就是调试程序,tracee就是被调试程序,英语一般用-er和-ee来表示主动和被动的关系,例如:employer就是老板,employee就是苦逼的打工人
request指定跟踪的动作
PTRACE_TRACEME:此进程将被父进程跟踪,任何信号(除了 SIGKILL
)都会暂停子进程,接着阻塞于 wait()
等待的父进程被唤醒。子进程内部对 exec()
的调用将发出 SIGTRAP
信号,这可以让父进程在子进程新程序开始运行之前就完全控制它。返回信号被wait之类函数捕获到后并不是信号的值,库提供了宏去解析获得,man 2 wait
查看 (see also strsignal(3))
PTRACE_ATTACH: 附加到一个指定的进程,使其成为当前进程跟踪的子进程,而子进程的行为等同于它进行了一次 PTRACE_TRACEME 操作
PTRACE_SYSCALL: 调试进程每次进入或者退出系统调用时都会触发一次SIGTRAP
信号,
PTRACE_GETREGS:获取通用寄存器值
PTRACE_SETREGS : 设置通用寄存器值
PTRACE_SINGLESTEP:每执行完一次指令之后会触发一次sigtrap,支持获取当前进程的内存/寄存器状态。gdb的单步指令通过该选项实现。
- 硬件实现单步:处理器支持单步模式(x86通过设置EFLAGS寄存器第8位的TF标志实现)
- 软件实现单步:每条指令后面都插入一条断点指令(x86 int3[0xcc烫烫烫])
- 断点的实现可以在指令处插入int3,当被调试的程序运行到断点的时候,产生SIGTRAP信号,把指令位置和调试器维护的断点集合(一般为链表)比较判断SIGTRAP是否由断点产生,把原来指令恢复,设置pc寄存器,就可以继续
PTRACE_CONT:继续运行之前停止的子进程。可同时向子进程交付指定的信号(data参数传递)
pid表示要跟踪的进程pid
addr表示进程的内存地址
data 根据前面设置的requet选项而变化,存放读取出的或者要写入的数据
#include <stdio.h> #include <stdlib.h> #include <sys/ptrace.h> #include <sys/user.h> #include <unistd.h>
int main(int argc, char *argv[]) { pid_t child; int status; struct user_regs_struct regs; int orig_rax;
child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "/bin/ls", NULL); } else { wait(&status); ptrace(PTRACE_GETREGS, child, 0, ®s); printf("orig_rax: %llx\t", regs.orig_rax); printf("rax: %llx\n", regs.rax); while (1) { ptrace(PTRACE_SYSCALL, child, NULL, NULL);
wait(&status); if (WIFEXITED(status)) { puts("--------------"); printf("%d\n", status); break; } ptrace(PTRACE_GETREGS, child, 0, ®s); printf("orig_rax: %llx\t", regs.orig_rax); printf("rax: %llx\t", regs.rax); ptrace(PTRACE_SYSCALL, child, NULL, NULL); wait(&status); ptrace(PTRACE_GETREGS, child, 0, ®s); printf("orig_rax: %llx\t", regs.orig_rax); printf("rax: %llx\n", regs.rax); if (WIFEXITED(status)) { puts("++++++++++++++++++++++"); printf("%d\n", status); break; } } } return 0; }
|
基本上可以和strace输出结果对上
ciscn2022 syscall
首先是父线程分支
通过ptrace(PTRACE_GETREGS, (unsigned int)a1, 0LL, &s);
可以判断处s是一个user_regs_struct的结构体定义在#include <sys/user.h>
#include <sys/user.h> struct user_regs_struct { __extension__ unsigned long long int r15; __extension__ unsigned long long int r14; __extension__ unsigned long long int r13; __extension__ unsigned long long int r12; __extension__ unsigned long long int rbp; __extension__ unsigned long long int rbx; __extension__ unsigned long long int r11; __extension__ unsigned long long int r10; __extension__ unsigned long long int r9; __extension__ unsigned long long int r8; __extension__ unsigned long long int rax; __extension__ unsigned long long int rcx; __extension__ unsigned long long int rdx; __extension__ unsigned long long int rsi; __extension__ unsigned long long int rdi; __extension__ unsigned long long int orig_rax; __extension__ unsigned long long int rip; __extension__ unsigned long long int cs; __extension__ unsigned long long int eflags; __extension__ unsigned long long int rsp; __extension__ unsigned long long int ss; __extension__ unsigned long long int fs_base; __extension__ unsigned long long int gs_base; __extension__ unsigned long long int ds; __extension__ unsigned long long int es; __extension__ unsigned long long int fs; __extension__ unsigned long long int gs; };
|
定义结构体后代码就清晰明了多了
首先第一个waitpid被子线程里的raise(18)
唤醒
这里判断低字节为0x7f是为了判断子线程是否收因信号传递而停止
# define WIFSTOPPED(status) __WIFSTOPPED (status) #define __WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
|