【问题标题】:How to trace a process for system calls?如何跟踪系统调用的进程?
【发布时间】:2012-06-20 08:44:49
【问题描述】:

我正在尝试编写一个跟踪系统调用的程序。我很难完成这项工作。我尝试调用 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() 的手册页:

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.

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

【问题讨论】:

  • “不知怎的,它没有按计划工作。”需要详细说明吗?您期望会发生什么,实际会发生什么?请编辑问题以添加它,而不是作为评论。
  • 此外,您在父进程中做的第一件事就是调用wait。这个函数正是这样做的,等待子进程完成,这意味着ptrace 调用试图跟踪一个不再存在的进程。
  • [参考此][1] 我认为,它可能会有所帮助。 [1]:stackoverflow.com/questions/6468896/…
  • @ManikSidana:这并不重要。

标签: c linux system-calls ptrace


【解决方案1】:

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

现在在父级中,您只需等待(一次)并假设子级现在在系统调用处停止。如果它在某个信号处停止,则不会出现这种情况,因此您需要调用wait(&amp;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, 0, 0, 0);  // ignore any signal and continue the child
}

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

【讨论】:

    【解决方案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 代码以进一步了解。

    【讨论】:

    • 根据手册页,只使用 PTRACEME 的第一个参数,其余的被忽略。 (char *)1 是怎么回事?
    【解决方案3】:

    您想监控父母的多少电话?如果你想要不止一个,你将需要某种循环。

    请注意示例中的这一行,这很重要:

    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    

    查看man page,孩子需要执行PTRACE_TRACEMEexec,或者父母需要使用PTRACE_ATTACH 进行跟踪。我在您的代码中都没有看到:

    父进程可以通过调用 fork(2) 启动跟踪,并让生成的子进程执行 PTRACE_TRACEME,然后(通常)执行 exec(3)。或者,父进程可以使用 PTRACE_ATTACH 开始跟踪现有进程。

    【讨论】:

      【解决方案4】:

      把 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;
      }
      

      【讨论】:

        猜你喜欢
        • 2011-08-02
        • 2012-04-03
        • 2012-11-11
        • 2012-04-03
        • 2015-07-02
        • 2016-02-28
        • 2017-11-04
        • 2017-01-04
        • 2015-09-11
        相关资源
        最近更新 更多