我正在尝试在Linux CentOS上编写一个工具来跟踪所有产生的进程以及所运行的程序。本质上,我对遍历所有fork/lone并从execve()发出所有命令行感兴趣。 Strace已经做到了(部分),但是它也截断了调用和参数。我还想更好地了解ptrace()的工作原理。

因此,第一个障碍是弄清楚如何使用ptrace()进行派生/克隆而无需跟踪程序要求派生其自身的副本。我挖了一下,发现strace是如何做到的。由于fork是在Linux上用克隆实现的,因此我注意到strace在克隆syscall中增加了一些位,以使子跟踪没有任何额外的麻烦。

因此,从本质上讲,代码只是很大的一部分:

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, pid, (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, 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, pid, 0, 0) < 0) {
                    perror("ptrace(PTRACE_SYSCALL,...");
                    exit(1);
                }
            }

            // waitpid(pid, &status, 0);
            // pid = waitpid(-1, &status, 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, pid, 0, 0) < 0) {
                        perror("ptrace");
                    } */

                    pid = -1;

                    continue;
                }
            }

            ptrace(PTRACE_GETREGS, pid, 0, &regs);

#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, pid, 0, &regs);

                    #ifdef __x86_64__
                    ptr = regs.rdi;
                    #else
                    ptr = regs.ebx;
                    #endif /* __x86_64__ */

                    fprintf(stderr, "%d: exec(", pid);

                    if (ptr) {
                        get_string(pid, ptr, 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, pid, (void *) ptr, 0);

                        if (!ptr)
                            break;

                        get_string(pid, ptr, bufstr, sizeof(bufstr));
                        fprintf(stderr, ", %s", bufstr);
                    }
                    fprintf(stderr, ")\n");

                    pidTable[pid].entering() = 0;
                }
                else {
                    long acc = ptrace(PTRACE_PEEKUSER, pid, sizeof(unsigned long) * REG_ACC, 0);
                    pidTable[pid].entering() = 1;
                    fprintf(stderr, "%d: Leaving exec: eax is %ld\n", pid, 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, pid, (sizeof(unsigned long) * REG_ARG1), 0);

                    fprintf(stderr, "%d: Entering fork/clone\n", pid);
                    pidTable[pid].entering() = 0;

                    if (ptrace(PTRACE_POKEUSER, pid, (sizeof(unsigned long) * REG_ARG1), flags | CLONE_PTRACE &
                                                                                         ~(flags & CLONE_VFORK ?
                                                                                         CLONE_VFORK | CLONE_VM : 0)) < 0) {
                        perror("ptrace");
                    }

                    if (ptrace(PTRACE_POKEUSER, pid, (sizeof(unsigned long) * REG_ARG2), 0) < 0) {
                        perror("ptrace");
                    }

                } else {
                    // int child;

                    ptrace(PTRACE_GETREGS, pid, 0, &regs);

                    #ifdef __x86_64__
                    fprintf(stderr, "%d: Leaving fork/clone: rax = %ld\n", pid, regs.rax);
                    #else
                    fprintf(stderr, "%d: Leaving fork/clone: eax = %ld\n", pid, 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", pid, 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", pid, scno);
            }
        }
    }
    return 0;
}

最佳答案

顺便一提。 strace -f -s99999 -e trace=clone,execve似乎可以提供高质量的结果。
要查看strace自身操作的痕迹,您可以尝试使用systemtap,即。
# stap -e 'probe syscall.ptrace {if (execname()=="strace") log(argstr)}' -c 'strace COMMAND'
(当前的systemtap不能正确打印ptrace参数。)

或者您可以strace strace:
strace -e trace=ptrace strace -f -s99999 -e trace=clone,execve COMMAND

关于linux - 使用ptrace跟踪子进程中的所有execve()调用,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5317261/

10-13 07:31