如何跟踪系统调用的过程?

如何跟踪系统调用的过程?

问题描述:

我正在尝试编写一个跟踪自身以进行系统调用的程序.我在进行这项工作时遇到了困难.我尝试调用fork()创建自身的实例(代码),然后监视生成的子进程.

I am trying to code a program that traces itself for system calls. I am having a difficult time making this work. I tried calling a fork() to create an instance of itself (the code), then monitor the resulting child process.

目标是使父进程返回由子进程进行的每个系统调用的索引,并将其输出到屏幕.不知何故,它无法按计划工作.

The goal is for the parent process to return the index of every system call made by the child process and output it to the screen. Somehow it is not working as planned.

这是代码:

#include <unistd.h>     /* for read(), write(), close(), fork() */
#include <fcntl.h>      /* for open() */
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/types.h>


int main(int argc, char *argv[]) {
    pid_t child;
    long orig_eax;
    child = fork();

    if (0 == child) 
    {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        if (argc != 3) {
           fprintf(stderr, "Usage: copy <filefrom> <fileto>\n"); 
           return 1;
        }

        int c;
        size_t file1_fd, file2_fd; 
        if ((file1_fd = open(argv[1], O_RDONLY)) < 0) {
           fprintf(stderr, "copy: can't open %s\n", argv[1]);
           return 1;
        }

        if ((file2_fd = open(argv[2], O_WRONLY | O_CREAT)) < 0) {
            fprintf(stderr, "copy: can't open %s\n", argv[2]);
            return 1;
        }

        while (read(file1_fd, &c, 1) > 0) 
        write(file2_fd, &c, 1);
    }
    else
    {
        wait(NULL);
        orig_eax = ptrace (PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL);
        printf("copy made a system call %ld\n", orig_eax);
        ptrace(PTRACE_CONT, child, NULL, NULL);
    }           
return 0;
}

此代码基于以下代码:

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>   /* For constants
                               ORIG_EAX etc */
int main()
{   
    pid_t child;
    long orig_eax;
    child = fork();
    if(child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
    }
    else {
        wait(NULL);
        orig_eax = ptrace(PTRACE_PEEKUSER,
                          child, 4 * ORIG_EAX,
                          NULL);
        printf("The child made a "
               "system call %ld\n", orig_eax);
        ptrace(PTRACE_CONT, child, NULL, NULL);
    }
    return 0;
}

这一个的输出是:

The child made a system call 11

这是exec系统调用的索引.

which is the index for the exec system call.

根据wait()的手册页:

According to the man pages for wait():

All of these system calls are used to wait for state changes in a child
of the calling process, and obtain information about  the  child  whose
state  has changed. A state change is considered to be: the child terminated; 
the child was stopped by a signal; or the child was resumed by
a  signal.

我的理解是,每次用户程序调用系统调用时,内核都会在执行系统调用例程之前先检查是否正在跟踪该进程,并通过信号暂停该进程并返回控制权给父母.那不是已经变成状态变更了吗?

The way I understand it is that every time a system call is invoked by a user program, the kernel will first inspect if the process is being traced prior to executing the system call routine and pauses that process with a signal and returns control to the parent. Wouldn't that be a state change already?

问题是,当子级调用ptrace(TRACEME)时,它会自行进行跟踪,但实际上并没有停止-它会一直持续直到调用(在这种情况下,它会以SIGTRAP停止),或者会收到其他信号.因此,为了让父母在不执行exec调用的情况下了解其作用,您需要安排孩子接收信号.最简单的方法可能是让孩子在调用ptrace(TRACEME)

The problem is that when the child calls ptrace(TRACEME) it sets itself up for tracing but doesn't actually stop -- it keeps going until it calls exec (in which case it stops with a SIGTRAP), or it gets some other signal. So in order for you to have the parent see what it does WITHOUT an exec call, you need to arrange for the child to receive a signal. The easiest way to do that is probably to have the child call raise(SIGCONT); (or any other signal) immediately after calling ptrace(TRACEME)

现在在父级中,您只需等待一次,并假设该子级现在已在系统调用中停止.如果它在信号处停止,则不会出现这种情况,因此您需要调用wait(&status)以获得子状态,并分别调用WIFSTOPPED(status)WSTOPSIG(status)以查看其为什么停止.如果由于系统调用而停止,则信号将为SIGTRAP.

Now in the parent you just wait (once) and assume that the child is now stopped at a system call. This won't be the case if it stopped at a signal, so you instead need to call wait(&status) to get the child status and call WIFSTOPPED(status) and WSTOPSIG(status) to see WHY it has stopped. If it has stopped due to a syscall, the signal will be SIGTRAP.

如果要在客户端中看到多个系统调用,则需要循环执行所有这些操作;像这样:

If you want to see multiple system calls in the client, you'll need to do all of this in a loop; something like:

while(1) {
    wait(&status);
    if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
        // stopped before or after a system call -- query the child and print out info
    }
    if (WIFEXITED(status) || WIFSIGNALED(status)) {
        // child has exited or terminated
        break;
    }
    ptrace(PTRACE_SYSCALL, 0, 0, 0);  // ignore any signal and continue the child
}

请注意,它将在每次系统调用时停止两次TWICE-在系统调用之前一次,在系统调用完成之后第二次.

Note that it will stop TWICE for each system call -- once before the system call and a second time just after the system call completes.