进程 A 中的 waitpid() 无法在 B coredump 之前进程 B 中的 pthread_create() 创建的线程中捕获 SIGTRAP

waitpid() in process A can't catch SIGTRAP in thread created by pthread_create() in process B before B coredump

首先,启动进程B(见下文mt.cpp),它将创建一个线程pthread_create()。主线程和新线程的ppidpidtid输出给进程A,然后他们都开始for循环并引发SIGTRAP,这应该在进程A中被waitpid()捕获。

其次,使用进程 B 的 pid 启动进程 A(参见下面的 attach.cpp)。进程 A 将通过 ptrace(PTRACE_ATTACH, ...) 附加到进程 B,然后使用 [= 等待信号事件21=] 在 while(true) 中,如果得到 SIGTRAP 则调用 ptrace(PTRACE_CONT, ...),如果得到 SIGSTOP.

则中断循环

现在的问题是: 进程A可以捕获进程B的主线程抛出的SIGTRAP并成功调用ptrace(PTRACE_CONT, ...),然后进程B将继续执行。

但是!!!

当进程 B 的新线程引发 SIGTRAP 时,进程 A 无法 ptrace(PTRACE_CONT, ...)errmsg "No such process" ,因为进程 B 已使用 errmsg "Trace/breakpoint trap (core dumped)" 进行核心转储。 另外,WIFSTOPPED(status)转为false,WIFSIGNALED(status)转为true。

我知道SIGTRAP的默认操作是终止进程,似乎SIGTRAP是在终止操作之后转移到进程A的,而不是之前,所以进程A没有机会继续流程 B.

我试过gdb代替进程A,都SIGTRAP都可以被捕获并继续成功。所以一定是进程A的代码有问题。

这是作为进程 A:

执行的 attach.cpp
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main(int argc, char *argv[])
{   
  pid_t pid = 0;
  int ret = 0;
  int status = 0;

  if (argc > 1) {
    pid = atoi(argv[1]);
    printf("pid=%d\n", pid);
  }

  ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
  printf("attach ret=%d\n", ret);

  waitpid(pid, &status, 0);
  ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
  printf("cont ret=%d\n", ret);

  while (true) {
    ret = waitpid(pid, &status, WUNTRACED);
    printf("\nwaitpid ret=%d.\n", ret);

    int sig = 0; 
    if (WIFSIGNALED(status)) {
      printf("WIFSIGNALED\n");
      sig = WTERMSIG(status);
    } else if (WIFSTOPPED(status)) {
      printf("WIFSTOPPED\n");
      sig = WSTOPSIG(status);
    } else {
      printf("other status %d\n", status);
    }
    if (SIGTRAP == sig) {
      ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
      printf("SIGTRAP cont ret=%d err=%s\n", ret, strerror(errno));
    } else if (SIGSTOP == sig) {
      ret = ptrace(PTRACE_DETACH, pid, NULL, NULL);
      printf("SIGSTOP detach ret=%d\n", ret);
      break;
    } else {
      printf("other signal %d\n", sig);
    }
    sleep(2);
  }

  return 0;
}

这是作为进程 B:

执行的 mt.cpp
#include <stdio.h> 
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <sys/syscall.h>

#define gettid() syscall(SYS_gettid)

void *func(void * arg)
{
  printf("child ppid=%d pid=%d tid=%d\n", getppid(), getpid(), gettid());
  int i = 0;
  for (; i < 5; i++) {
    printf("child loop i=%d\n", i);
    sleep(2);
  }

  printf("\nchild before SIGTRAP\n", gettid());
  raise(SIGTRAP);
  printf("child after SIGTRAP\n\n", gettid());

  for (; i < 8; i++) {
    printf("child loop i=%d\n", i);
    sleep(2);
  }

  return NULL;
}

int main(void)
{
  printf("parent ppid=%d pid=%d tid=%d\n", getppid(), getpid(), gettid());
  pthread_t tid;
  pthread_create(&tid, NULL, func, NULL);

  int i = 0;
  for (; i < 3; i++) {
    printf("parent loop i=%d\n", i);
    sleep(2);
  }

  printf("\nparent before SIGTRAP\n", gettid());
  raise(SIGTRAP);
  printf("parent after SIGTRAP\n\n", gettid());

  for (; i < 10; i++) {
    printf("parent loop i=%d\n", i);
    sleep(2);
  }

  pthread_join(tid, NULL);

  return 0;
}

结果如下:

进程 B:

$ ./mt
parent ppid=12238 pid=30389 tid=30389
parent loop i=0
child ppid=12238 pid=30389 tid=30390
child loop i=0
parent loop i=1
child loop i=1
parent loop i=2
child loop i=2

parent before SIGTRAP
child loop i=3
parent after SIGTRAP

parent loop i=3
child loop i=4
parent loop i=4

child before SIGTRAP
Trace/breakpoint trap (core dumped)

进程A:

$ ./attach 30389
pid=30389
attach ret=0
cont ret=0

waitpid ret=30389.
WIFSTOPPED
SIGTRAP cont ret=0 err=Success

waitpid ret=30389.
WIFSIGNALED
SIGTRAP cont ret=-1 err=No such process
^C

Linux PTRACE_ATTACH 请求,尽管其参数被命名为 pid,将仅跟踪该线程。

您可以通过将此函数添加到您的程序并在两个线程中调用它来验证这一点:

#define trprefix "TracerPid:"

int tracerpid()
{
  char stfile[100], buf[512];
  sprintf(stfile, "/proc/self/task/%d/status", (int)gettid());
  int trpid = -1;
  FILE *st = fopen(stfile, "r");
  if (st != NULL) {
    while (fgets(buf, sizeof buf, st) != NULL) {
      if (strncmp(buf, trprefix, strlen(trprefix)) == 0)
        trpid = atoi(buf+strlen(trprefix));
    }
    fclose(st);
  }
  return trpid;
}

您会看到父线程的 Tracer PID 是您的 "attach" 进程的,而子线程的 Tracer PID 是 0。

当子线程引发 SIGTRAP 时,线程没有跟踪器,因此将执行 SIGTRAP 的默认操作 - 整个进程将被终止。这就是为什么您的跟踪器说 waitpid 返回 WIFSIGNALED.

解决这个问题:

  • 在 "mt" 程序中,将对 pthread_create 的调用移动到第一个延迟循环之后,这将使您有足够的时间在新的循环之前附加到进程线程已创建。

  • 将此添加到 ptrace(PTRACE_ATTACH, ...); waitpid(...); 之后的 "attach" 程序:

    errno = 0;
    ret = ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACECLONE);
    printf("setoptions ret=%d err=%s\n", ret, strerror(errno));
    

    PTRACE_O_TRACECLONE 选项将使您的程序跟踪目标使用 clone 创建的每个线程。

  • 将所有 waitpid(pid, ...) 转换为 waitpid(-1, ...) 以便您的程序将等待任何线程。