1

First, start process B (see mt.cpp below) , it will create a thread with pthread_create(). The ppid, pid and tid of main thread and the new thread will be outputted for process A, then both of them start a for loop and raise SIGTRAP , which should be caught by waitpid() in process A.

Second, start process A (see attach.cpp below) with pid of process B. Process A will attach to process B by ptrace(PTRACE_ATTACH, ...), then wait signal event using waitpid() in while(true), call ptrace(PTRACE_CONT, ...) if get a SIGTRAP, or break the loop if get a SIGSTOP.

Now is the problem: Process A can catch the SIGTRAP raised by main thread of process B and call ptrace(PTRACE_CONT, ...) successfully, and then process B will continue to execute as expected.

BUT!!!

When the new thread of process B raised SIGTRAP, process A failed to ptrace(PTRACE_CONT, ...) with a errmsg "No such process", because process B has core dumped with a errmsg "Trace/breakpoint trap (core dumped)". In addition, WIFSTOPPED(status) turned to false and WIFSIGNALED(status) turned to true.

I know the default action of SIGTRAP is terminate the process, it seems that the SIGTRAP was transfered to process A after the termination action, NOT before, so process A had no chance to continue process B.

I have tried gdb instead of process A, both SIGTRAP can be caught and continued successfully. So there must be something wrong in the code of process A.

Here is attach.cpp executed as process A:

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

Here is mt.cpp executed as process B:

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

Here the result:

  • if you want execute the two programs by yourself, be sure that process A (attach) should be started as soon as possible after start process B.

process 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)

process 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
jxh
  • 69,070
  • 8
  • 110
  • 193
ethan.xy
  • 23
  • 6
  • I added the C flag. Although the asker says `.cpp` files, the code is straight up C. – jxh Jun 20 '17 at 20:54

1 Answers1

1

The Linux PTRACE_ATTACH request, despite its argument being named pid, will trace only that thread.

You can verify this by adding this function to your program and calling it in the two threads:

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

You'll see that the parent thread's Tracer PID is that of your "attach" process, while the child thread's Tracer PID is 0.

When the child thread raises SIGTRAP, there's no tracer for the thread, so the default action for SIGTRAP will be taken - the whole process will be killed. That's why your tracer says that waitpid returned WIFSIGNALED.

To fix this:

  • In the "mt" program, move the call to pthread_create to be after the first delay loop, which will give you enough time to attach to the process before the new thread is created.

  • Add this to the "attach" program after the ptrace(PTRACE_ATTACH, ...); waitpid(...);:

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

    The PTRACE_O_TRACECLONE option will let your program trace every thread that the target creates with clone.

  • Convert all your waitpid(pid, ...) to waitpid(-1, ...) so that your program will wait for any thread.

Mark Plotnick
  • 9,598
  • 1
  • 24
  • 40