如何跟踪一个进程的系统调用?

6
我正在尝试编写一个可以追踪系统调用的程序。但是我在让它正常工作方面遇到了困难。我尝试使用fork()来创建一个实例(即代码),然后监视生成的子进程。
目标是让父进程返回子进程执行的每个系统调用的索引并将其输出到屏幕上。但不知道为什么计划没有按照预期进行。
以下是代码:
#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系统调用的索引。

根据wait()的man页面:

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.

我理解的方式是每当用户程序调用系统调用时,内核将首先检查进程是否正在被跟踪,然后执行系统调用例程并使用信号暂停该进程并返回控制权给父进程。这难道不已经导致状态改变了吗?

2
不知何故它没有按计划工作。能详细说明一下吗?您期望发生什么,实际上又发生了什么?请编辑问题添加,而不是作为评论。 - Some programmer dude
7
此外,父进程要做的第一件事情就是调用wait函数。该函数会一直等待,直到子进程结束,这意味着ptrace调用试图跟踪一个已经不存在的进程。 - Some programmer dude
请参考此链接,我认为它可能会有所帮助。 - Manik Sidana
2
@ManikSidana:那并不是很相关。 - Asherah
4个回答

9
问题在于,当子进程调用ptrace(TRACEME)时,它会设置自己进行跟踪,但实际上没有停止。 它会继续执行直到调用exec(在这种情况下,它使用SIGTRAP停止),或者它接收到其他信号。 因此,为了让父进程看到它的操作,而不需要调用exec,您需要安排子进程接收一个信号。 最简单的方法可能是让子进程在调用ptrace(TRACEME)之后立即调用raise(SIGCONT);(或任何其他信号)

现在,在父进程中,您只需等待一次,并假定子进程现在已经停在一个系统调用处。如果它停在信号处,则不是这种情况,因此您需要调用wait(&status)以获取子进程的状态,并调用WIFSTOPPED(status)WSTOPSIG(status)来查看它为什么停止。如果它停止是由于系统调用,则信号将为SIGTRAP。

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

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, childpid, 0, 0);  // ignore any signal and continue the child
}

请注意,每次系统调用都会停止两次——一次在系统调用之前,一次在系统调用完成后。

如果我没错的话,应该是 ptrace(PTRACE_SYSCALL, childpid, 0, 0);,而不是 ptrace(PTRACE_SYSCALL, 0, 0, 0); - Abhishek Ghosh
1
@AbhishekGhosh:你是正确的。 - Chris Dodd

2

你基本上是在尝试编写Linux下的strace二进制文件,该文件跟踪进程所进行的系统调用。Linux为此提供了ptrace(2)系统调用。ptrace系统调用需要4个参数,第一个参数告诉您需要执行什么操作。操作系统通过信号与父进程通信,通过发送SIGSTOP信号来停止子进程。大体上,您需要遵循以下步骤。

if(fork() == 0 )

{
    //child process

    ptrace(PTRACE_TRACEME, 0,0, 0);
    exec(...); 
}
else
{

 start:

    wait4(...);

    if (WIFSIGNALED(status)) {
        //done
    }
    if (WIFEXITED(status)) {
       //done
    }
    if(flag == startup)
    {
        flag = startupdone;

        ptrace(PTRACE_SYSCALL, pid,0, 0) ;
        goto start;
    }
    if (if (WSTOPSIG(status) == SIGTRAP) {) {
          //extract the register
          ptrace(PTRACE_GETREGS,pid,(char *)&regs,0) 

    }

请注意,寄存器的读取和解释取决于您的架构。上面的代码只是一个示例,要正确理解需要深入挖掘。请查看strace代码以进一步了解。

根据man页面,PTRACEME只使用第一个参数,其余的被忽略了。那么(char *)1是什么意思? - 1der

1
在您的父级中,您想要监视多少个呼叫?如果您想要多于一个,您需要某种循环方式。
请注意示例中的行,这很重要:
ptrace(PTRACE_TRACEME, 0, NULL, NULL);

查看man page,子进程需要执行PTRACE_TRACEMEexec,或者父进程需要使用PTRACE_ATTACH进行跟踪。我在你的代码中没有看到这两个操作:

父进程可以通过调用fork(2)来启动跟踪,并让生成的子进程执行PTRACE_TRACEME,然后(通常)执行exec(3)。另外,父进程也可以使用PTRACE_ATTACH开始跟踪一个已经存在的进程。


0

只是总结一下 Chris Dodd 说的话:

#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;
int status;
long orig_eax;
child = fork();

if (0 == child) 
{
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    raise(SIGCONT);
    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
{
    while(1){
        wait(&status);
        if(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP){
            orig_eax = ptrace(PTRACE_PEEKUSER, child, sizeof(long) * ORIG_EAX, NULL);
            printf("copy made a system call %ld\n", orig_eax);
        }
        if(WIFEXITED(status) || WIFSIGNALED(status)){
            break;
        }

        ptrace(PTRACE_SYSCALL, child, 0, 0);
    }           
}
return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接