我正在尝试在
Linux CentOS上编写一个工具来跟踪所有生成的进程以及运行的内容.本质上,我有兴趣遍历所有fork / clone并从execve()发出所有命令行. Strace已经(某些)做了这个,但它也截断了调用和参数.我还想更好地理解ptrace()的工作原理.
因此,第一个障碍是弄清楚如何使用ptrace()来执行fork / clone而不需要跟踪程序需要分叉自己的副本.我挖了进去,发现了这个怎么样.因为fork是在Linux上用clone实现的,所以我注意到strace将一些比特放入克隆系统调用中,以便在没有任何额外麻烦的情况下启用子跟踪.
所以,实质上代码只是一个大问题:
while (1) { int pid = wait3(-1,...); /* process what happened */ ptrace(PTRACE_SYSCALL,pid,...); }
这适用于相对简单的进程,如/ bin / sh,但是,某些进程导致wait()无限期挂起.我唯一能够确定的是我正在跟踪的进程是对它的子进行sys_rt_sigsuspend()(因此,跟踪器的孙子)然后事情楔入.
我很好奇是否有一种理智的方式可以调试可能发生的事情.有些事情显然阻止了流程树的进展
这是有问题的程序的源代码:
#include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <string.h> #include <stdlib.h> /* For the clone flags */ #include <sched.h> /* #include <errno.h> */ #include <sys/ptrace.h> #include <sys/user.h> /* Defines our syscalls like */ #include <sys/syscall.h> #include <sys/reg.h> #include <stdio.h> #include <signal.h> #include <ctype.h> #include <map> using namespace std; char bufstr[4096]; #ifdef __x86_64__ #define REG_ACC RAX #define REG_ARG1 RDI #define REG_ARG2 RSI #else #define REG_ACC EAX #define REG_ARG1 EBX #define REG_ARG2 ECX #endif /* Trace control structure per PID that we're tracking */ class tcb { int pid_; int entering_; public: tcb(int pid,int entering = 1) : pid_(pid),entering_(entering) {}; tcb() : pid_(-1) {}; // tcb(const tcb& p) : pid_(pid.pid()),entering_(entering.entering()) {}; int& pid() { return pid_; } int& entering() { return entering_; } }; /* Fetch a string from process (pid) at location (ptr). Buf is the place * to store the data with size limit (size). Return the number of bytes * copied. */ int get_string(int pid,long ptr,char *buf,int size) { long data; char *p = (char *) &data; int j = 0; while ((data = ptrace(PTRACE_PEEKTEXT,(void *) ptr,0)) && j < size) { int i; for (i = 0; i < sizeof(data) && j < size; i++,j++) { if (!(buf[j] = p[i])) goto done; } ptr += sizeof(data); } done: buf[j] = '\0'; return j; } int main(int argc,char *argv[]) { int status = 0; long scno = 0; // int entering = 1; struct user_regs_struct regs; map<int,tcb> pidTable; struct sigaction sa; /* Setup */ int pid = fork(); if (!pid && argc) { if (ptrace(PTRACE_TRACEME,0) < 0) { perror("ptrace(PTRACE_ME,... "); exit(1); } execvp(argv[1],&argv[1]); } else { sa.sa_flags = 0; sa.sa_handler = SIG_DFL; sigemptyset(&sa.sa_mask); sigaction(SIGCHLD,&sa,NULL); waitpid(pid,&status,0); pidTable[pid] = tcb(pid); fprintf(stderr,"pid is %d\n",pidTable[pid].pid()); while (!pidTable.empty()) { if (pid > 0) { //fprintf(stderr,"%d: Restarting %d\n",getpid(),pid); if (ptrace(PTRACE_SYSCALL,0) < 0) { perror("ptrace(PTRACE_SYSCALL,..."); exit(1); } } // waitpid(pid,0); // pid = waitpid(-1,0); pid = wait3(&status,__WALL,0); // fprintf(stderr,"Pid from wait is %d\n",pid); if (pid < 0) { perror("waitpid"); break; } else { /* fprintf(stderr,"%d: Status is: ",pid); */ /* if (WIFEXITED(status)) { fprintf(stderr,"exited"); } else if (WIFSIGNALED(status)) { fprintf(stderr,"exited"); } else if (WIFSTOPPED(status),"stopped") { fprintf(stderr,"stopped"); } else if (WIFCONTINUED(status)) { fprintf(stderr,"continued"); } fprintf(stderr,"\n"); */ if (WIFEXITED(status) || WIFSIGNALED(status)) { /* Probably empty the table here */ pidTable.erase(pid); fprintf(stderr,"Detect process term/kill %d\n",pid); /* if (ptrace(PTRACE_DETACH,0) < 0) { perror("ptrace"); } */ pid = -1; continue; } } ptrace(PTRACE_GETREGS,®s); #ifdef __x86_64__ scno = regs.orig_rax; #else scno = regs.orig_eax; #endif /* __x86_64__ */ if (scno == SYS_execve) { fprintf(stderr,"%d: Exec branch\n",pid); if (pidTable[pid].entering()) { long ldata,ptr,ptr1; ptrace(PTRACE_GETREGS,®s); #ifdef __x86_64__ ptr = regs.rdi; #else ptr = regs.ebx; #endif /* __x86_64__ */ fprintf(stderr,"%d: exec(",pid); if (ptr) { get_string(pid,bufstr,sizeof(bufstr)); fprintf(stderr,"%s",bufstr); } #ifdef __x86_64__ ptr1 = regs.rsi; #else ptr1 = regs.ecx; #endif /* __x86_64__ */ for (; ptr1; ptr1 += sizeof(unsigned long)) { ptr = ptr1; /* Indirect through ptr since we have char *argv[] */ ptr = ptrace(PTRACE_PEEKTEXT,0); if (!ptr) break; get_string(pid,sizeof(bufstr)); fprintf(stderr,",%s",bufstr); } fprintf(stderr,")\n"); pidTable[pid].entering() = 0; } else { long acc = ptrace(PTRACE_PEEKUSER,sizeof(unsigned long) * REG_ACC,0); pidTable[pid].entering() = 1; fprintf(stderr,"%d: Leaving exec: eax is %ld\n",acc); } } else if (scno == SYS_fork || scno == SYS_clone) { fprintf(stderr,"%d: fork/clone branch\n",pid); if (pidTable[pid].entering()) { long flags = ptrace(PTRACE_PEEKUSER,(sizeof(unsigned long) * REG_ARG1),0); fprintf(stderr,"%d: Entering fork/clone\n",pid); pidTable[pid].entering() = 0; if (ptrace(PTRACE_POKEUSER,flags | CLONE_PTRACE & ~(flags & CLONE_VFORK ? CLONE_VFORK | CLONE_VM : 0)) < 0) { perror("ptrace"); } if (ptrace(PTRACE_POKEUSER,(sizeof(unsigned long) * REG_ARG2),0) < 0) { perror("ptrace"); } } else { // int child; ptrace(PTRACE_GETREGS,®s); #ifdef __x86_64__ fprintf(stderr,"%d: Leaving fork/clone: rax = %ld\n",regs.rax); #else fprintf(stderr,"%d: Leaving fork/clone: eax = %ld\n",regs.eax); #endif pidTable[pid].entering() = 1; #ifdef __x86_64__ if (regs.rax <= 0) { #else if (regs.eax <= 0) { #endif continue; } #ifdef __x86_64__ int newpid = regs.rax; #else int newpid = regs.eax; #endif pidTable[newpid] = tcb(newpid,0); //pidTable[newpid] = tcb(newpid,1); //pidTable[newpid] = pidTable[pid]; fprintf(stderr,"%d: forked child is %d\n",newpid); } } else if (scno == SYS_exit) { fprintf(stderr,"%d: exit syscall detected\n",pid); } else if (scno < 0) { fprintf(stderr,"Negative syscall number for %d\n",pid); exit(1); } else { fprintf(stderr,"%d: Scno is %ld\n",scno); } } } return 0; }
解决方法
有ptrace PTRACE_SETOPTIONS子标志的标志:PTRACE_O_TRACEFORK,PTRACE_O_TRACEEXEC和PTRACE_O_TRACEEXIT.更多内容是ptrace的手册页.