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)
 
 |