Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tracing syscalls of a process and all forked processes

I'm using ptrace to trace the syscalls of a process. After forking the process, I use PTRACE_TRACEME to start trace the the process. The code looks like this:

while (true) {
    int status;
    int gotPid;
    gotPid = waitpid(pid, &status, 0);

    if (WIFEXITED(status) || WIFSIGNALED(status)) {
        break;
    }

    if (WIFSTOPPED(status)) {
        handleTrace();
    }
}

Then there is the handleTrace function, which looks like this.

long syscall;
syscall = ptrace(PTRACE_PEEKUSER,
     pid, 8 * ORIG_RAX, NULL);

// do something with the syscall

// continue program
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

This is all good, but if the program forks (or creates a new thread) I also want to trace the child processes the traced process creates (and also the threads created by the process). I know that it can be done using PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK and PTRACE_O_TRACECLONE, but from the man documentation, it is very hard to figure out how exactly it is done. I need some examples on this.

Edit:

I found a similar question here: How to ptrace a multi-threaded application? I tried it with the following code. This code tracks the system calls of the started process and it is supposed to track the forked processes too. It is run after a fork() in the parent process (the child calls a PTRACE_TRACEME and an exec()).

Edit2:

I made some more modifications on the code, with some more progress.

long orig_eax;
int status;
int numPrograms = 1;

while(1) {
    int pid;
    CHECK_ERROR_VALUE(pid = waitpid(-1, &status, __WALL));
    std::cout << pid << ": Got event." << std::endl;
    if(WIFEXITED(status) || WIFSIGNALED(status)) {
        std::cout << pid << ": Program exited." << std::endl;
        if (--numPrograms == 0) {
            break;
        }
        continue;
    }

    if (status >> 16 == PTRACE_EVENT_FORK || status >> 16 == PTRACE_EVENT_VFORK ||
            status >> 16 == PTRACE_EVENT_CLONE) {
        int newpid;
        CHECK_ERROR_VALUE(ptrace(PTRACE_GETEVENTMSG, child, NULL, (long) &newpid));
        std::cout << pid << ": Attached to offspring " << newpid << std::endl;
        boost::this_thread::sleep(boost::posix_time::millisec(100));
        CHECK_ERROR_VALUE(ptrace(PTRACE_SETOPTIONS,
                newpid, NULL, PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE));
        CHECK_ERROR_VALUE(ptrace(PTRACE_SYSCALL, newpid, NULL, NULL));
        ++numPrograms;
    } else {
        CHECK_ERROR_VALUE(ptrace(PTRACE_SETOPTIONS,
                pid, NULL, PTRACE_O_TRACEFORK  | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE));
        CHECK_ERROR_VALUE(orig_eax = ptrace(PTRACE_PEEKUSER,
                pid, 8 * ORIG_RAX, NULL));
        std::cout << pid << ": Syscall called: " <<
                SyscallMap::instance().get().right.at(orig_eax) <<
                std::endl;
        CHECK_ERROR_VALUE(ptrace(PTRACE_SYSCALL,
                pid, NULL, NULL));
    }

}

CHECK_ERROR_VALUE is just a macro that checks the result code and throw an exception with the description of errno in it.

Apparently, when I get the event of a fork/clone the new process doesn't exist yet, and I get a "Process does not exist" error message if I try to ptrace it. If I put a sleep before trying to ptrace the new process, I don't get an error message. Now when the program gets to the point of fork/clone, it starts to trace the new process, but it never gets to the return point of the clone() syscall in the parent process, which means that the child finishes successfully, but the parent hangs at its fork point.

like image 347
petersohn Avatar asked Nov 23 '12 15:11

petersohn


1 Answers

Strace does this and its README-linux-ptrace file has some information on the subject:

https://github.com/strace/strace/blob/master/README-linux-ptrace

It does seem to be complaining about kernel bugs in this platform, so YMMV.

The code explains how to get the child's pid. However it may be possible that the child gets another userid due to setuid or setgid bits set on the binaries it calls. So the answer is that you call ptrace on the child PID and see if you get access.

Here's the relevant section:

PTRACE_EVENT stops are observed by tracer as waitpid returning with WIFSTOPPED(status) == true, WSTOPSIG(status) == SIGTRAP. Additional bit is set in a higher byte of status word: value ((status >> 8) & 0xffff) will be (SIGTRAP | PTRACE_EVENT_foo << 8). The following events exist:

  • PTRACE_EVENT_VFORK - stop before return from vfork/clone+CLONE_VFORK. When tracee is continued after this, it will wait for child to exit/exec before continuing its execution (IOW: usual behavior on vfork).
  • PTRACE_EVENT_FORK - stop before return from fork/clone+SIGCHLD
  • PTRACE_EVENT_CLONE - stop before return from clone
  • PTRACE_EVENT_VFORK_DONE - stop before return from vfork/clone+CLONE_VFORK, but after vfork child unblocked this tracee by exiting or exec'ing.

For all four stops described above: stop occurs in parent, not in newly created thread. PTRACE_GETEVENTMSG can be used to retrieve new thread's tid.

like image 179
christopher Avatar answered Oct 12 '22 23:10

christopher