我正在使用ptrace跟踪一些进程及其子进程.我试图打印特定的系统调用(使用通知ptrace的Seccomp过滤器,请参阅this blogpost).

在大多数情况下,我的代码(见下文)运行良好.然而,当我跟踪java程序(从默认jre包)时,后者使用CLONE_THREAD标志进行克隆.由于某种原因,我的跟踪器挂起了(我相信),因为我无法接收来自克隆过程的信号.我认为原因是(根据this discussion)子进程实际上成为原始进程父进程的子进程,而不是成为原始进程的子进程.

我使用一个简单的程序再现了这个问题,该程序只需使用标志调用clone()并执行操作.当我使用When-I-use CLONE_THREAD | CLONE_SIGHAND | CLONE_VM标志时(因为clone() documentation指定自Linux 2.6.0以来它们应该在一起),至少我能够正确地跟踪所有内容,直到两个线程中的一个完成.

我想独立跟踪这两个线程.有可能吗?

更重要的是,我需要跟踪一个Java程序,我无法更改它.这里是Java程序克隆调用的一部分:

[...]
4665  clone(child_stack=0x7fb166e95fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[4666], tls=0x7fb166e96700, child_tidptr=0x7fb166e969d0) = 4666
[...]

因此,Java似乎尊重规则.我想通过实验来理解:我排除了与线程无关的任何标志(即"CLONE\u FS | CLONE\u FILES | CLONE\u SYSVSEM).

以下是使用不同标志组合运行测试程序的结果(我知道,我真的很绝望):

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS:仅从父级获取跟踪

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_PARENT_SETTID:不一致;从两者获取跟踪,直到父级完成

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_CHILD_CLEARTID:不一致;从两者获取跟踪,直到子对象完成

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_PARENT_SETTID:仅从父级获取跟踪

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_CHILD_CLEARTID:仅从父级获取跟踪

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_PARENT_SETTID|CLONE_SETTLS:仅从父级获取跟踪

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID:不一致;从两者获取跟踪,直到子对象完成

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_CHILD_CLEARTID|CLONE_SETTLS:仅从父级获取跟踪

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_CHILD_CLEARTID|CLONE_PARENT_SETTID:不一致;从两者获取跟踪,直到子对象完成

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID:

因此,至少我从我的程序和Java程序中得到了相同的行为:它不工作.

我该怎么做?例如,strace如何成功跟踪任何类型的克隆?我试图深入研究它的代码,但我找不到他们是怎么做的.

任何帮助都将不胜感激!

跟踪程序代码(用g++ tracer.cpp -o tracer -g -lseccomp -lexplain编译):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stddef.h>

#include <sys/ptrace.h>
#include <sys/reg.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <libexplain/waitpid.h>
#include <tuple>
#include <vector>


#define DEFAULT_SIZE 1000
#define MAX_SIZE 1000

int process_signals();
int inspect(pid_t);
void read_string_into_buff(const pid_t, unsigned long long, char *, unsigned int);

int main(int argc, char **argv){
  pid_t pid;
  int status;

  if (argc < 2) {
      fprintf(stderr, "Usage: %s <prog> <arg1> ... <argN>\n", argv[0]);
      return 1;
  }

  if ((pid = fork()) == 0) {
      /* If execve syscall, trace */
      struct sock_filter filter[] = {
          BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)),
          BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_getpid, 0, 1),
          BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRACE),
          BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
      };
      struct sock_fprog prog = {
          .len = (unsigned short) (sizeof(filter)/sizeof(filter[0])),
          .filter = filter,
      };
      ptrace(PTRACE_TRACEME, 0, 0, 0);
      if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
            perror("prctl(PR_SET_NO_NEW_PRIVS)");
            return 1;
      }
      if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
          perror("when setting seccomp filter");
          return 1;
      }
      kill(getpid(), SIGSTOP);
      return execvp(argv[1], argv + 1);
  } else {
      waitpid(pid, &status, 0);
      ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESECCOMP | PTRACE_O_TRACEFORK | PTRACE_O_TRACECLONE | PTRACE_O_TRACEVFORK );
      ptrace(PTRACE_CONT, pid, 0, 0);
      process_signals();
      return 0;
  }
}


int process_signals(){
  int status;
  while (1){
    pid_t child_pid;
    // When child status changes
    if ((child_pid = waitpid(-1, &status, 0)) < 0){
      fprintf(stderr, "%s\n", explain_waitpid(child_pid, &status, 0));
      exit(EXIT_FAILURE);
    }
    //printf("Sigtrap received\n");
    // Checking if it is thanks to seccomp
    if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))){
      // Perform argument inspection with ptrace
      int syscall = inspect(child_pid);
    }
    // Resume no matter what
    ptrace(PTRACE_CONT, child_pid, 0, 0);
  }
}

int inspect(pid_t pid){
  printf("From PID: %d\n", pid);
  struct user_regs_struct regs;
  ptrace(PTRACE_GETREGS, pid, 0, &regs);
  // Get syscall number
  int syscall = regs.orig_rax;
  printf("------\nCaught syscall: %d\n", syscall);

  if (syscall == __NR_getpid){
    printf("Getpid detected\n");
  }
  return syscall;
}

void read_string_into_buff(const pid_t pid, unsigned long long addr, char * buff, unsigned int max_len){
  /* Are we aligned on the "start" front? */
  unsigned int offset=((unsigned long)addr)%sizeof(long);
  addr-=offset;
  unsigned int i=0;
  int done=0;
  int word_offset=0;

  while( !done ) {
    unsigned long word=ptrace( PTRACE_PEEKDATA, pid, addr+(word_offset++)*sizeof(long), 0 );
    // While loop to stop at the first '\0' char indicating end of string
    while( !done && offset<sizeof(long) && i<max_len ) {
      buff[i]=((char *)&word)[offset]; /* Endianity neutral copy */

      done=buff[i]=='\0';
      ++i;
      ++offset;
    }

    offset=0;
    done=done || i>=max_len;
  }
}

示 routine 序(用gcc sample.c -o sample编译):

#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>

#define FLAGS CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID

int fn(void *arg)
{
   printf("\nINFO: This code is running under child process.\n");

   int i = 0;
   int n = atoi(arg);
   for ( i = 1 ; i <= 10 ; i++ )
      printf("[%d] %d * %d = %d\n", getpid(), n, i, (n*i));

   printf("\n");

   return 0;
}

void main(int argc, char *argv[])
{
   printf("[%d] Hello, World!\n", getpid());

   void *pchild_stack = malloc(1024 * 1024);
   if ( pchild_stack == NULL ) {
      printf("ERROR: Unable to allocate memory.\n");
      exit(EXIT_FAILURE);
   }

   int pid = clone(fn, pchild_stack + (1024 * 1024), FLAGS, argv[1]);
   if ( pid < 0 ) {
        printf("ERROR: Unable to create the child process.\n");
        exit(EXIT_FAILURE);
   }

   fn(argv[1]);

   wait(NULL);

   free(pchild_stack);

   printf("INFO: Child process terminated.\n");
}

你可以通过运行./tracer ./sample来测试你想要的.您还可以测试原始测试用例./tracer java,并观察到跟踪器和java都挂起.

ANSWER:

在我的原始代码中(因为太复杂,所以没有在这里列出),我只是在进程启动后附加ptrace...我只附加到pstree列出的PID.我的错误是我忽略了线程(java是一个创建线程的程序),解释了为什么我只跟踪java的问题.

推荐答案

您的示 routine 序有一个bug:它可能会在第二个线程退出之前释放该线程的堆栈,从而导致SEGV.你的跟踪器不能很好地处理信号.

如果被跟踪的程序得到一个信号,那么跟踪器会拦截它,而不是将其传递给程序.当它继续执行程序时,它继续执行导致SEGV的相同操作,因此它再次获得SEGV.无限远.跟踪器和跟踪对象似乎都挂起,但事实上,它们处于无限循环中.

按照以下方式重写续文似乎可行:

        if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))){
            // Perform argument inspection with ptrace
            int syscall = inspect(child_pid);
            ptrace(PTRACE_CONT, child_pid, 0, 0);
        } else if (WIFSTOPPED(status)) {
            ptrace(PTRACE_CONT, child_pid, 0, WSTOPSIG(status));
        } else {
            ptrace(PTRACE_CONT, child_pid, 0, 0);
        }

不确定Java,但它似乎可以正常运行SEGV...

C++相关问答推荐

定义_MISIX_C_SAL时,在MacOS上编译失败,并出现奇怪错误

理解没有返回语句的递归C函数的行为

如果我释放其他内容,返回值就会出错

如何在不使用其他数组或字符串的情况下交换字符串中的两个单词?

如何在C语言中正确打印图形

插座打开MacOS组件

在WSL关闭/重新启动后,是什么原因导致共享对象依赖关系发生更改?

RawMotion的XInput2错误(具有较高值的XISelectEvents上的BadValue)

Boyer Moore算法的简单版本中的未定义行为

将多项式从文件.txt加载到终端时出现问题

将回调/基于事件的C API转换为非回调API

为什么我在C代码中得到一个不完整的类型?

S,在 struct 中创建匿名静态缓冲区的最佳方式是什么?

为什么我的二叉树删除删除整个左部分的树?

如何在zOS上编译共享C库

C编译和运行

I';我试着从.txt文件中读取文本,并用c计算其中的单词数量

在同一范围内对具有相同类型的变量执行的相同操作在同一C代码中花费的时间不同

将指针的地址加载到寄存器内联拇指组件中

如何修复数组数据与列标题未对齐的问题?