如何使用PTRACE获得多线程的一致视图?

如何使用PTRACE获得多线程的一致视图?,c,linux,multithreading,pthreads,ptrace,C,Linux,Multithreading,Pthreads,Ptrace,在我工作时,我遇到了一个可能的想法,即使用ptrace,但我无法正确理解ptrace如何与线程交互 假设我有一个给定的多线程主进程,并且我希望附加到其中的一个特定线程(可能来自一个分叉的子进程) 我可以连接到特定的线程吗?(手册在这个问题上存在分歧。) 如果是这样的话,这是否意味着单步执行只能执行一个线程的指令?它会停止进程的所有线程吗 如果是这样,那么在我调用PTRACE\u SYSCALL或PTRACE\u SINGLESTEP时,是否所有其他线程都保持停止状态,还是所有线程都继续?有没有办

在我工作时,我遇到了一个可能的想法,即使用
ptrace
,但我无法正确理解
ptrace
如何与线程交互

假设我有一个给定的多线程主进程,并且我希望附加到其中的一个特定线程(可能来自一个分叉的子进程)

  • 我可以连接到特定的线程吗?(手册在这个问题上存在分歧。)

  • 如果是这样的话,这是否意味着单步执行只能执行一个线程的指令?它会停止进程的所有线程吗

  • 如果是这样,那么在我调用
    PTRACE\u SYSCALL
    PTRACE\u SINGLESTEP
    时,是否所有其他线程都保持停止状态,还是所有线程都继续?有没有办法只在一个线程中前进一步,但保证其他线程保持停止

  • 基本上,我希望通过强制所有线程停止来同步原始程序,然后通过单步执行一个跟踪的线程,只执行一小部分单线程指令

    到目前为止,我个人的尝试有点像这样:

    pid_t target = syscall(SYS_gettid);   // get the calling thread's ID
    pid_t pid = fork();
    
    if (pid > 0)
    {
        waitpid(pid, NULL, 0);            // synchronise main process
    
        important_instruction();
    }
    else if (pid == 0)
    {
        ptrace(target, PTRACE_ATTACH, NULL, NULL);    // does this work?
    
        // cancel parent's "waitpid" call, e.g. with a signal
    
        // single-step to execute "important_instruction()" above
    
       ptrace(target, PTRACE_DETACH, NULL, NULL);     // parent's threads resume?
    
       _Exit(0);
    }
    
    但是,我不确定,也找不到合适的引用,这是否同时正确,并且只有在所有其他线程停止时才能保证执行
    important\u指令()。我也明白,当家长从其他地方接收信号时,可能存在竞争条件,我听说我应该使用
    PTRACE\u-take
    ,但这似乎并不存在于所有地方

    如有任何澄清或参考,将不胜感激

    它会停止进程的所有线程吗

    对 它跟踪进程,该进程的所有线程都将停止。 想象一下,你怎么能在IDE中看到不同的线程呢

    从手册中:

    ptrace()系统调用提供了一种方法,通过这种方法,一个进程(“跟踪程序”)可以观察和控制另一个进程(“跟踪对象”)的执行

    要附加的示例代码:

    printf("Attaching to process %d\n",Tpid);
    if ((ptrace(PTRACE_ATTACH, Tpid, 0, 0)) != 0) {;
        printf("Attach result %d\n",res);
    }
    
    因此,是的,您被连接到一个线程,是的,它会停止进程的所有线程

    if ((res = ptrace(PTRACE_SINGLESTEP, Tpid, 0, signo)) < 0) {
    perror("Ptrace singlestep error");
    exit(1);
    }
    res = wait(&stat);
    
    if((res=ptrace(ptrace_SINGLESTEP,Tpid,0,signo))<0){
    perror(“Ptrace单步错误”);
    出口(1);
    }
    res=等待(&stat);
    

    请参见此处:

    进程中的每个线程都是单独跟踪的(每个线程都可能由不同的跟踪进程跟踪,或者不被跟踪)。当您调用ptraceattach时,您总是只连接到一个线程。只有该线程将停止,其他线程将继续运行

    ptrace()
    手册页的最新版本非常清楚地说明了这一点:

    附件和后续命令是每个线程的:在多线程中 进程中,每个线程都可以单独连接到 不同)跟踪器,或未连接,因此未调试。 因此,“tracee”总是指(一个)线程,而不是(可能的)线程 多线程)进程”。Ptrace命令始终发送到 使用表单调用的特定tracee

    ptrace(PTRACE_foo, pid, ...)
    
    其中pid是对应Linux线程的线程ID

    (请注意,在本页中,“多线程进程”是指线程 由使用克隆创建的线程组成的组(2)
    克隆线程
    标志。)

    单步执行只影响您所指向的线程。如果其他线程正在运行,它们将继续运行,如果它们处于跟踪停止状态,它们将保持在跟踪停止状态。(这意味着,如果正在单步执行的线程尝试获取另一个非运行线程所持有的互斥锁或类似同步资源,它将无法获取该互斥锁)

    如果要在单步执行一个线程时停止进程的所有线程,则需要附加到所有线程。还有一个额外的复杂性,即如果进程在您尝试附加到它时正在运行,那么在您枚举它们时可能会创建新线程

    我可以连接到特定的线程吗

    是的,至少在当前内核上是这样

    这是否意味着单步执行只能执行一个线程的指令?它会停止进程的所有线程吗

    对。它不会停止其他线程,只停止连接的线程

    有没有办法只在一个线程中前进一步,但保证其他线程保持停止

    对。将
    SIGSTOP
    发送到进程(使用
    waitpid(PID,WUNTRACED)
    等待进程停止),然后将
    PTRACE\u ATTACH
    发送到进程中的每个线程。发送
    SIGCONT
    (使用
    waitpid(PID,WCONTINUED)
    等待进程继续)

    由于附加时所有线程都已停止,并且附加会停止线程,因此在传递
    SIGCONT
    信号后,所有线程都将保持停止状态。您可以按自己喜欢的顺序单步执行线程


    我发现这很有趣,足以制作一个测试用例。(好吧,事实上,我怀疑没有人会相信我的话,所以我决定最好拿出证据证明你可以自己复制。)

    我的系统似乎遵循中所述的,Kerrisk似乎非常擅长将它们与内核行为保持同步。一般来说,我更喜欢kernel.org源码wrt。将Linux内核移植到其他源代码

    总结:

    • 附加到进程本身(TID==PID)只会停止原始线程,而不是所有线程

    • 附加到特定线程(使用
      /proc/PID/task/
      中的TID)会停止该线程。(换句话说,TID==PID的线程并不特殊。)

    • SIGSTOP
      发送到进程将停止所有线程,但
      ptrace()
      仍然可以正常工作

    • 如果您向进程发送了
      SIGSTOP
      ,请不要调用
      ptrace(ptrace\u CONT,TID)#define  GNU_SOURCE
      #include <stdlib.h>
      #include <unistd.h>
      #include <sys/wait.h>
      #include <sys/ptrace.h>
      #include <sys/syscall.h>
      #include <dirent.h>
      #include <pthread.h>
      #include <signal.h>
      #include <string.h>
      #include <errno.h>
      #include <stdio.h>
      
      #ifndef   THREADS
      #define   THREADS  3
      #endif
      
      static int tgkill(int tgid, int tid, int sig)
      {
          int retval;
      
          retval = syscall(SYS_tgkill, tgid, tid, sig);
          if (retval < 0) {
              errno = -retval;
              return -1;
          }
      
          return 0;
      }
      
      volatile unsigned long counter[THREADS + 1] = { 0UL };
      
      volatile sig_atomic_t run = 0;
      volatile sig_atomic_t done = 0;
      
      void handle_done(int signum)
      {
          done = signum;
      }
      
      int install_done(int signum)
      {
          struct sigaction act;
          sigemptyset(&act.sa_mask);
          act.sa_handler = handle_done;
          act.sa_flags = 0;
          if (sigaction(signum, &act, NULL))
              return errno;
          return 0;
      }
      
      void *worker(void *data)
      {
          volatile unsigned long *const counter = data;
      
          while (!run)
              ;
      
          while (!done)
              (*counter)++;
      
          return (void *)(*counter);
      }
      
      pid_t *gettids(const pid_t pid, size_t *const countptr)
      {
          char           dirbuf[128];
          DIR           *dir;
          struct dirent *ent;
      
          pid_t         *data = NULL, *temp;
          size_t         size = 0;
          size_t         used = 0;
      
          int            tid;
          char           dummy;
      
          if ((int)pid < 2) {
              errno = EINVAL;
              return NULL;
          }
      
          if (snprintf(dirbuf, sizeof dirbuf, "/proc/%d/task/", (int)pid) >= (int)sizeof dirbuf) {
              errno = ENAMETOOLONG;
              return NULL;
          }
      
          dir = opendir(dirbuf);
          if (!dir)
              return NULL;
      
          while (1) {
              errno = 0;
              ent = readdir(dir);
              if (!ent)
                  break;
      
              if (sscanf(ent->d_name, "%d%c", &tid, &dummy) != 1)
                  continue;
      
              if (tid < 2)
                  continue;
      
              if (used >= size) {
                  size = (used | 127) + 129;
                  temp = realloc(data, size * sizeof data[0]);
                  if (!temp) {
                      free(data);
                      closedir(dir);
                      errno = ENOMEM;
                      return NULL;
                  }
                  data = temp;
              }
      
              data[used++] = (pid_t)tid;
          }
          if (errno) {
              free(data);
              closedir(dir);
              errno = EIO;
              return NULL;
          }
          if (closedir(dir)) {
              free(data);
              errno = EIO;
              return NULL;
          }
      
          if (used < 1) {
              free(data);
              errno = ENOENT;
              return NULL;
          }
      
          size = used + 1;
          temp = realloc(data, size * sizeof data[0]);
          if (!temp) {
              free(data);
              errno = ENOMEM;
              return NULL;
          }
          data = temp;
      
          data[used] = (pid_t)0;
      
          if (countptr)
              *countptr = used;
      
          errno = 0;
          return data;
      }
      
      int child_main(void)
      {
          pthread_t   id[THREADS];
          int         i;
      
          if (install_done(SIGUSR1)) {
              fprintf(stderr, "Cannot set SIGUSR1 signal handler.\n");
              return 1;
          }
      
          for (i = 0; i < THREADS; i++)
              if (pthread_create(&id[i], NULL, worker, (void *)&counter[i])) {
                  fprintf(stderr, "Cannot create thread %d of %d: %s.\n", i + 1, THREADS, strerror(errno));
                  return 1;
              }
      
          run = 1;
      
          kill(getppid(), SIGUSR1);
      
          while (!done)
              counter[THREADS]++;
      
          for (i = 0; i < THREADS; i++)
              pthread_join(id[i], NULL);
      
          printf("Final counters:\n");
          for (i = 0; i < THREADS; i++)
              printf("\tThread %d: %lu\n", i + 1, counter[i]);
          printf("\tMain thread: %lu\n", counter[THREADS]);
      
          return 0;
      }
      
      int main(void)
      {
          pid_t   *tid = NULL;
          size_t   tids = 0;
          int      i, k;
          pid_t    child, p;
      
          if (install_done(SIGUSR1)) {
              fprintf(stderr, "Cannot set SIGUSR1 signal handler.\n");
              return 1;
          }
      
          child = fork();
          if (!child)
              return child_main();
      
          if (child == (pid_t)-1) {
              fprintf(stderr, "Cannot fork.\n");
              return 1;
          }
      
          while (!done)
              usleep(1000);
      
          tid = gettids(child, &tids);
          if (!tid) {
              fprintf(stderr, "gettids(): %s.\n", strerror(errno));
              kill(child, SIGUSR1);
              return 1;
          }
      
          fprintf(stderr, "Child process %d has %d tasks.\n", (int)child, (int)tids);
          fflush(stderr);
      
          for (k = 0; k < (int)tids; k++) {
              const pid_t t = tid[k];
      
              if (ptrace(PTRACE_ATTACH, t, (void *)0L, (void *)0L)) {
                  fprintf(stderr, "Cannot attach to TID %d: %s.\n", (int)t, strerror(errno));
                  kill(child, SIGUSR1);
                  return 1;
              }
      
              fprintf(stderr, "Attached to TID %d.\n\n", (int)t);
      
              fprintf(stderr, "Peeking the counters in the child process:\n");
              for (i = 0; i <= THREADS; i++) {
                  long v;
                  do {
                      errno = 0;
                      v = ptrace(PTRACE_PEEKDATA, t, &counter[i], NULL);
                  } while (v == -1L && (errno == EIO || errno == EFAULT || errno == ESRCH));
                  fprintf(stderr, "\tcounter[%d] = %lu\n", i, (unsigned long)v);
              }
              fprintf(stderr, "Waiting a short moment ... ");
              fflush(stderr);
      
              usleep(250000);
      
              fprintf(stderr, "and another peek:\n");
              for (i = 0; i <= THREADS; i++) {
                  long v;
                  do {
                      errno = 0;
                      v = ptrace(PTRACE_PEEKDATA, t, &counter[i], NULL);
                  } while (v == -1L && (errno == EIO || errno == EFAULT || errno == ESRCH));
                  fprintf(stderr, "\tcounter[%d] = %lu\n", i, (unsigned long)v);
              }
              fprintf(stderr, "\n");
              fflush(stderr);
      
              usleep(250000);
      
              ptrace(PTRACE_DETACH, t, (void *)0L, (void *)0L);
          }
      
          for (k = 0; k < 4; k++) {
              const pid_t t = tid[tids / 2];
      
              if (k == 0) {
                  fprintf(stderr, "Sending SIGSTOP to child process ... ");
                  fflush(stderr);
                  kill(child, SIGSTOP);
              } else
              if (k == 1) {
                  fprintf(stderr, "Sending SIGCONT to child process ... ");
                  fflush(stderr);
                  kill(child, SIGCONT);
              } else
              if (k == 2) {
                  fprintf(stderr, "Sending SIGSTOP to TID %d ... ", (int)tid[0]);
                  fflush(stderr);
                  tgkill(child, tid[0], SIGSTOP);
              } else
              if (k == 3) {
                  fprintf(stderr, "Sending SIGCONT to TID %d ... ", (int)tid[0]);
                  fflush(stderr);
                  tgkill(child, tid[0], SIGCONT);
              }
              usleep(250000);
              fprintf(stderr, "done.\n");
              fflush(stderr);
      
              if (ptrace(PTRACE_ATTACH, t, (void *)0L, (void *)0L)) {
                  fprintf(stderr, "Cannot attach to TID %d: %s.\n", (int)t, strerror(errno));
                  kill(child, SIGUSR1);
                  return 1;
              }
      
              fprintf(stderr, "Attached to TID %d.\n\n", (int)t);
      
              fprintf(stderr, "Peeking the counters in the child process:\n");
              for (i = 0; i <= THREADS; i++) {
                  long v;
                  do {
                      errno = 0;
                      v = ptrace(PTRACE_PEEKDATA, t, &counter[i], NULL);
                  } while (v == -1L && (errno == EIO || errno == EFAULT || errno == ESRCH));
                  fprintf(stderr, "\tcounter[%d] = %lu\n", i, (unsigned long)v);
              }
              fprintf(stderr, "Waiting a short moment ... ");
              fflush(stderr);
      
              usleep(250000);
      
              fprintf(stderr, "and another peek:\n");
              for (i = 0; i <= THREADS; i++) {
                  long v;
                  do {
                      errno = 0;
                      v = ptrace(PTRACE_PEEKDATA, t, &counter[i], NULL);
                  } while (v == -1L && (errno == EIO || errno == EFAULT || errno == ESRCH));
                  fprintf(stderr, "\tcounter[%d] = %lu\n", i, (unsigned long)v);
              }
              fprintf(stderr, "\n");
              fflush(stderr);
      
              usleep(250000);
      
              ptrace(PTRACE_DETACH, t, (void *)0L, (void *)0L);
          }
      
          kill(child, SIGUSR1);
      
          do {
              p = waitpid(child, NULL, 0);
              if (p == -1 && errno != EINTR)
                  break;
          } while (p != child);
      
          return 0;
      }
      
      gcc -DTHREADS=3 -W -Wall -O3 traces.c -pthread -o traces
      ./traces
      
      Child process 18514 has 4 tasks.
      Attached to TID 18514.
      
      Peeking the counters in the child process:
          counter[0] = 0
          counter[1] = 0
          counter[2] = 0
          counter[3] = 0
      Waiting a short moment ... and another peek:
          counter[0] = 18771865
          counter[1] = 6435067
          counter[2] = 54247679
          counter[3] = 0
      
      Attached to TID 18515.
      
      Peeking the counters in the child process:
          counter[0] = 25385151
          counter[1] = 13459822
          counter[2] = 103763861
          counter[3] = 560872
      Waiting a short moment ... and another peek:
          counter[0] = 25385151
          counter[1] = 69116275
          counter[2] = 120500164
          counter[3] = 9027691
      
      Attached to TID 18516.
      
      Peeking the counters in the child process:
          counter[0] = 25397582
          counter[1] = 105905400
          counter[2] = 155895025
          counter[3] = 17306682
      Waiting a short moment ... and another peek:
          counter[0] = 32358651
          counter[1] = 105905400
          counter[2] = 199601078
          counter[3] = 25023231
      
      Attached to TID 18517.
      
      Peeking the counters in the child process:
          counter[0] = 40600813
          counter[1] = 111675002
          counter[2] = 235428637
          counter[3] = 32298929
      Waiting a short moment ... and another peek:
          counter[0] = 48727731
          counter[1] = 143870702
          counter[2] = 235428637
          counter[3] = 39966259
      
      Sending SIGSTOP to child process ... done.
      Attached to TID 18516.
      
      Peeking the counters in the child process:
          counter[0] = 56887263
          counter[1] = 170646440
          counter[2] = 235452621
          counter[3] = 48077803
      Waiting a short moment ... and another peek:
          counter[0] = 56887263
          counter[1] = 170646440
          counter[2] = 235452621
      counter[3] = 48077803
      
      Sending SIGCONT to child process ... done.
      Attached to TID 18516.
      
      Peeking the counters in the child process:
          counter[0] = 64536344
          counter[1] = 182359343
          counter[2] = 253660731
          counter[3] = 56422231
      Waiting a short moment ... and another peek:
          counter[0] = 72029244
          counter[1] = 182359343
          counter[2] = 288014365
          counter[3] = 63797618
      
      Sending SIGSTOP to TID 18514 ... done.
      Attached to TID 18516.
      
      Peeking the counters in the child process:
          counter[0] = 77012930
          counter[1] = 183059526
          counter[2] = 344043770
          counter[3] = 71120227
      Waiting a short moment ... and another peek:
          counter[0] = 77012930
          counter[1] = 183059526
          counter[2] = 344043770
          counter[3] = 71120227
      
      Sending SIGCONT to TID 18514 ... done.
      Attached to TID 18516.
      
      Peeking the counters in the child process:
          counter[0] = 88082419
          counter[1] = 194059048
          counter[2] = 359342314
          counter[3] = 84887463
      Waiting a short moment ... and another peek:
          counter[0] = 100420161
          counter[1] = 194059048
          counter[2] = 392540525
          counter[3] = 111770366
      
      #include <unistd.h>
      #include <stdlib.h>
      #include <sys/types.h>
      #include <sys/ptrace.h>
      #include <sys/prctl.h>
      #include <sys/wait.h>
      #include <sys/user.h>
      #include <dirent.h>
      #include <string.h>
      #include <signal.h>
      #include <errno.h>
      #include <stdio.h>
      #ifndef   SINGLESTEPS
      #define   SINGLESTEPS 10
      #endif
      
      /* Similar to getline(), except gets process pid task IDs.
       * Returns positive (number of TIDs in list) if success,
       * otherwise 0 with errno set. */
      size_t get_tids(pid_t **const listptr, size_t *const sizeptr, const pid_t pid)
      {
          char     dirname[64];
          DIR     *dir;
          pid_t   *list;
          size_t   size, used = 0;
      
          if (!listptr || !sizeptr || pid < (pid_t)1) {
              errno = EINVAL;
              return (size_t)0;
          }
      
          if (*sizeptr > 0) {
              list = *listptr;
              size = *sizeptr;
          } else {
              list = *listptr = NULL;
              size = *sizeptr = 0;
          }
      
          if (snprintf(dirname, sizeof dirname, "/proc/%d/task/", (int)pid) >= (int)sizeof dirname) {
              errno = ENOTSUP;
              return (size_t)0;
          }
      
          dir = opendir(dirname);
          if (!dir) {
              errno = ESRCH;
              return (size_t)0;
          }
      
          while (1) {
              struct dirent *ent;
              int            value;
              char           dummy;
      
              errno = 0;
              ent = readdir(dir);
              if (!ent)
                  break;
      
              /* Parse TIDs. Ignore non-numeric entries. */
              if (sscanf(ent->d_name, "%d%c", &value, &dummy) != 1)
                  continue;
      
              /* Ignore obviously invalid entries. */
              if (value < 1)
                  continue;
      
              /* Make sure there is room for another TID. */
              if (used >= size) {
                  size = (used | 127) + 128;
                  list = realloc(list, size * sizeof list[0]);
                  if (!list) {
                      closedir(dir);
                      errno = ENOMEM;
                      return (size_t)0;
                  }
                  *listptr = list;
                  *sizeptr = size;
              }
      
              /* Add to list. */
              list[used++] = (pid_t)value;
          }
          if (errno) {
              const int saved_errno = errno;
              closedir(dir);
              errno = saved_errno;
              return (size_t)0;
          }
          if (closedir(dir)) {
              errno = EIO;
              return (size_t)0;
          }
      
          /* None? */
          if (used < 1) {
              errno = ESRCH;
              return (size_t)0;
          }
      
          /* Make sure there is room for a terminating (pid_t)0. */
          if (used >= size) {
              size = used + 1;
              list = realloc(list, size * sizeof list[0]);
              if (!list) {
                  errno = ENOMEM;
                  return (size_t)0;
              }
              *listptr = list;
              *sizeptr = size;
          }
      
          /* Terminate list; done. */
          list[used] = (pid_t)0;
          errno = 0;
          return used;
      }
      
      
      static int wait_process(const pid_t pid, int *const statusptr)
      {
          int   status;
          pid_t p;
      
          do {
              status = 0;
              p = waitpid(pid, &status, WUNTRACED | WCONTINUED);
          } while (p == (pid_t)-1 && errno == EINTR);
          if (p != pid)
              return errno = ESRCH;
      
          if (statusptr)
              *statusptr = status;
      
          return errno = 0;
      }
      
      static int continue_process(const pid_t pid, int *const statusptr)
      {
          int   status;
          pid_t p;
      
          do {
      
              if (kill(pid, SIGCONT) == -1)
                  return errno = ESRCH;
      
              do {
                  status = 0;
                  p = waitpid(pid, &status, WUNTRACED | WCONTINUED);
              } while (p == (pid_t)-1 && errno == EINTR);
      
              if (p != pid)
                  return errno = ESRCH;
      
          } while (WIFSTOPPED(status));
      
          if (statusptr)
              *statusptr = status;
      
          return errno = 0;
      }
      
      void show_registers(FILE *const out, pid_t tid, const char *const note)
      {
          struct user_regs_struct regs;
          long                    r;
      
          do {
              r = ptrace(PTRACE_GETREGS, tid, &regs, &regs);
          } while (r == -1L && errno == ESRCH);
          if (r == -1L)
              return;
      
      #if (defined(__x86_64__) || defined(__i386__)) && __WORDSIZE == 64
          if (note && *note)
              fprintf(out, "Task %d: RIP=0x%016lx, RSP=0x%016lx. %s\n", (int)tid, regs.rip, regs.rsp, note);
          else
              fprintf(out, "Task %d: RIP=0x%016lx, RSP=0x%016lx.\n", (int)tid, regs.rip, regs.rsp);
      #elif (defined(__x86_64__) || defined(__i386__)) && __WORDSIZE == 32
          if (note && *note)
              fprintf(out, "Task %d: EIP=0x%08lx, ESP=0x%08lx. %s\n", (int)tid, regs.eip, regs.esp, note);
          else
              fprintf(out, "Task %d: EIP=0x%08lx, ESP=0x%08lx.\n", (int)tid, regs.eip, regs.esp);
      #endif
      }
      
      
      int main(int argc, char *argv[])
      {
          pid_t *tid = 0;
          size_t tids = 0;
          size_t tids_max = 0;
          size_t t, s;
          long   r;
      
          pid_t child;
          int   status;
      
          if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
              fprintf(stderr, "\n");
              fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
              fprintf(stderr, "       %s COMMAND [ ARGS ... ]\n", argv[0]);
              fprintf(stderr, "\n");
              fprintf(stderr, "This program executes COMMAND in a child process,\n");
              fprintf(stderr, "and waits for it to stop (via a SIGSTOP signal).\n");
              fprintf(stderr, "When that occurs, the register state of each thread\n");
              fprintf(stderr, "is dumped to standard output, then the child process\n");
              fprintf(stderr, "is sent a SIGCONT signal.\n");
              fprintf(stderr, "\n");
              return 1;
          }
      
          child = fork();
          if (child == (pid_t)-1) {
              fprintf(stderr, "fork() failed: %s.\n", strerror(errno));
              return 1;
          }
      
          if (!child) {
              prctl(PR_SET_DUMPABLE, (long)1);
              prctl(PR_SET_PTRACER, (long)getppid());
              fflush(stdout);
              fflush(stderr);
              execvp(argv[1], argv + 1);
              fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
              return 127;
          }
      
          fprintf(stderr, "Tracer: Waiting for child (pid %d) events.\n\n", (int)child);
          fflush(stderr);
      
          while (1) {
      
              /* Wait for a child event. */
              if (wait_process(child, &status))
                  break;
      
              /* Exited? */
              if (WIFEXITED(status) || WIFSIGNALED(status)) {
                  errno = 0;
                  break;
              }
      
              /* At this point, only stopped events are interesting. */
              if (!WIFSTOPPED(status))
                  continue;
      
              /* Obtain task IDs. */
              tids = get_tids(&tid, &tids_max, child);
              if (!tids)
                  break;
      
              printf("Process %d has %d tasks,", (int)child, (int)tids);
              fflush(stdout);
      
              /* Attach to all tasks. */
              for (t = 0; t < tids; t++) {
                  do {
                      r = ptrace(PTRACE_ATTACH, tid[t], (void *)0, (void *)0);
                  } while (r == -1L && (errno == EBUSY || errno == EFAULT || errno == ESRCH));
                  if (r == -1L) {
                      const int saved_errno = errno;
                      while (t-->0)
                          do {
                              r = ptrace(PTRACE_DETACH, tid[t], (void *)0, (void *)0);
                          } while (r == -1L && (errno == EBUSY || errno == EFAULT || errno == ESRCH));
                      tids = 0;
                      errno = saved_errno;
                      break;
                  }
              }
              if (!tids) {
                  const int saved_errno = errno;
                  if (continue_process(child, &status))
                      break;
                  printf(" failed to attach (%s).\n", strerror(saved_errno));
                  fflush(stdout);
                  if (WIFCONTINUED(status))
                      continue;
                  errno = 0;
                  break;
              }
      
              printf(" attached to all.\n\n");
              fflush(stdout);
      
              /* Dump the registers of each task. */
              for (t = 0; t < tids; t++)
                  show_registers(stdout, tid[t], "");
              printf("\n");
              fflush(stdout);
      
              for (s = 0; s < SINGLESTEPS; s++) {
                  do {
                      r = ptrace(PTRACE_SINGLESTEP, tid[tids-1], (void *)0, (void *)0);
                  } while (r == -1L && errno == ESRCH);
                  if (!r) {
                      for (t = 0; t < tids - 1; t++)
                          show_registers(stdout, tid[t], "");
                      show_registers(stdout, tid[tids-1], "Advanced by one step.");
                      printf("\n");
                      fflush(stdout);
                  } else {
                      fprintf(stderr, "Single-step failed: %s.\n", strerror(errno));
                      fflush(stderr);
                  }
              }
      
              /* Detach from all tasks. */
              for (t = 0; t < tids; t++)
                  do {
                      r = ptrace(PTRACE_DETACH, tid[t], (void *)0, (void *)0);
                  } while (r == -1 && (errno == EBUSY || errno == EFAULT || errno == ESRCH));
              tids = 0;
              if (continue_process(child, &status))
                  break;
              if (WIFCONTINUED(status)) {
                  printf("Detached. Waiting for new stop events.\n\n");
                  fflush(stdout);
                  continue;
              }
              errno = 0;
              break;
          }
          if (errno)
              fprintf(stderr, "Tracer: Child lost (%s)\n", strerror(errno));
          else
          if (WIFEXITED(status))
              fprintf(stderr, "Tracer: Child exited (%d)\n", WEXITSTATUS(status));
          else
          if (WIFSIGNALED(status))
              fprintf(stderr, "Tracer: Child died from signal %d\n", WTERMSIG(status));
          else
              fprintf(stderr, "Tracer: Child vanished\n");
          fflush(stderr);
      
          return status;
      }
      
      #include <pthread.h>
      #include <signal.h>
      #include <string.h>
      #include <errno.h>
      #include <stdio.h>
      
      #ifndef   THREADS
      #define   THREADS  2
      #endif
      
      volatile sig_atomic_t   done = 0;
      
      void catch_done(int signum)
      {
          done = signum;
      }
      
      int install_done(const int signum)
      {
          struct sigaction act;
      
          sigemptyset(&act.sa_mask);
          act.sa_handler = catch_done;
          act.sa_flags = 0;
          if (sigaction(signum, &act, NULL))
              return errno;
          else
              return 0;
      }
      
      void *worker(void *data)
      {
          volatile unsigned long *const counter = data;
      
          while (!done)
              __sync_add_and_fetch(counter, 1UL);
      
          return (void *)(unsigned long)__sync_or_and_fetch(counter, 0UL);
      }
      
      int main(void)
      {
          unsigned long   counter = 0UL;
          pthread_t       thread[THREADS];
          pthread_attr_t  attrs;
          size_t          i;
      
          if (install_done(SIGHUP) ||
              install_done(SIGTERM) ||
              install_done(SIGUSR1)) {
              fprintf(stderr, "Worker: Cannot install signal handlers: %s.\n", strerror(errno));
              return 1;
          }
      
          pthread_attr_init(&attrs);
          pthread_attr_setstacksize(&attrs, 65536);
          for (i = 0; i < THREADS; i++)
              if (pthread_create(&thread[i], &attrs, worker, &counter)) {
                  done = 1;
                  fprintf(stderr, "Worker: Cannot create thread: %s.\n", strerror(errno));
                  return 1;
              }
          pthread_attr_destroy(&attrs);
      
          /* Let the original thread also do the worker dance. */
          worker(&counter);
      
          for (i = 0; i < THREADS; i++)
              pthread_join(thread[i], NULL);
      
          return 0;
      }
      
      gcc -W -Wall -O3 -fomit-frame-pointer worker.c -pthread -o worker
      gcc -W -Wall -O3 -fomit-frame-pointer tracer.c -o tracer
      
      ./tracer ./worker &
      
      Tracer: Waiting for child (pid 24275) events.
      
      kill -STOP 24275
      
      Process 24275 has 3 tasks, attached to all.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a5d, RSP=0x00007f399cfa6ee8.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a5d, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a63, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a65, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a58, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a5d, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a63, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a65, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a58, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a5d, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
      Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
      Task 24277: RIP=0x0000000000400a63, RSP=0x00007f399cfa6ee8. Advanced by one step.
      
      Detached. Waiting for new stop events.
      
      0x400a50: eb 0b                 jmp          0x400a5d
      0x400a52: 66 0f 1f 44 00 00     nopw         0x0(%rax,%rax,1)
      0x400a58: f0 48 83 07 01        lock addq    $0x1,(%rdi)          = fourth step
      0x400a5d: 8b 05 00 00 00 00     mov          0x0(%rip),%eax       = first step
      0x400a63: 85 c0                 test         %eax,%eax            = second step
      0x400a65: 74 f1                 je           0x400a58             = third step
      0x400a67: 48 8b 07              mov          (%rdi),%rax
      0x400a6a: 48 89 c2              mov          %rax,%rdx
      0x400a6d: f0 48 0f b1 07        lock cmpxchg %rax,(%rdi)
      0x400a72: 75 f6                 jne          0x400a6a
      0x400a74: 48 89 d0              mov          %rdx,%rax
      0x400a77: c3                    retq
      
      long r;
      
      do {
          r = ptrace(PTRACE_cmd, tid, ...);
      } while (r == -1L && (errno == EBUSY || errno == EFAULT || errno == ESRCH));
      
      pid_t  pid, p; /* Process owning the tasks */
      tid_t *tid;    /* Task ID array */
      size_t tids;   /* Tasks */
      long   result;
      int    status;
      size_t i;
      
      for (i = 0; i < tids; i++) {
          while (1) {
              result = ptrace(PTRACE_ATTACH, tid[i], (void *)0, (void *)0);
              if (result == -1L && (errno == ESRCH || errno == EBUSY || errno == EFAULT || errno == EIO)) {
                  /* To avoid burning up CPU for nothing: */
                  sched_yield(); /* or nanosleep(), or usleep() */
                  continue;
              }
              break;
          }       
          if (result == -1L) {
              /*
               * Fatal error. First detach from tid[0..i-1], then exit.
              */
          }
      }
      
      /* Send SIGCONT to the process. */
      if (kill(pid, SIGCONT)) {
          /*
           * Fatal error, see errno. Exit.
          */
      }
      
      /* Since we are attached to the process,
       * we can wait() on it. */
      while (1) {
          errno = 0;
          status = 0;
          p = waitpid(pid, &status, WCONTINUED);
          if (p == (pid_t)-1) {
              if (errno == EINTR)
                  continue;
              else
                  break;
          } else
          if (p != pid) {
              errno = ESRCH;
              break;
          } else
          if (WIFCONTINUED(status)) {
              errno = 0;
              break;
          }
      }
      if (errno) {
          /*
           * Fatal error. First detach from tid[0..tids-1], then exit.
          */
      }
      
      /* Single-step each task to update the task states. */
      for (i = 0; i < tids; i++) {
          while (1) {
              result = ptrace(PTRACE_SINGLESTEP, tid[i], (void *)0, (void *)0);
              if (result == -1L && errno == ESRCH) {
                  /* To avoid burning up CPU for nothing: */
                  sched_yield(); /* or nanosleep(), or usleep() */
                  continue;
              }
              break;
          }       
          if (result == -1L) {
              /*
               * Fatal error. First detach from tid[0..i-1], then exit.
              */
          }
      }
      
      /* Obtain task register structures, to make sure the single-steps
       * have completed and their states have stabilized. */
      for (i = 0; i < tids; i++) {
          struct user_regs_struct regs;
      
          while (1) {
              result = ptrace(PTRACE_GETREGS, tid[i], &regs, &regs);
              if (result == -1L && (errno == ESRCH || errno == EBUSY || errno == EFAULT || errno == EIO)) {
                  /* To avoid burning up CPU for nothing: */
                  sched_yield(); /* or nanosleep(), or usleep() */
                  continue;
              }
              break;
          }       
          if (result == -1L) {
              /*
               * Fatal error. First detach from tid[0..i-1], then exit.
              */
          }
      }