Non-working threaded version.
authorlace <>
Wed, 11 Apr 2007 20:57:31 +0000 (20:57 +0000)
committerlace <>
Wed, 11 Apr 2007 20:57:31 +0000 (20:57 +0000)
Makefile [new file with mode: 0644]
debugger.c [new file with mode: 0644]
debugger.c-childhandler.patch [new file with mode: 0644]
debugger.h [new file with mode: 0644]
testsuite.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..8929e34
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+CC=gcc -ggdb3 -Wall -Werror
+
+all: debugger check
+
+debugger: debugger.c debugger.h
+       $(CC) -o $@ $< -lthread_db -pthread -ldw
+
+testsuite: testsuite.c debugger.c debugger.h
+       $(CC) -o $@ $< -lthread_db -pthread -ldw
+
+.PHONY: check
+check: testsuite
+       ./testsuite
+       @echo OK
+
+.PHONY: still
+still:
+       FIXME ./testsuite && false
+
+.PHONY: clean
+clean:
+       $(RM) debugger testsuite
+
diff --git a/debugger.c b/debugger.c
new file mode 100644 (file)
index 0000000..f880038
--- /dev/null
@@ -0,0 +1,462 @@
+/* Copyright 2007, Red Hat Inc.  */
+
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <limits.h>
+#include <string.h>
+#include <execinfo.h>
+#include <thread_db.h>
+#include <elfutils/libdwfl.h>
+
+#include "debugger.h"
+
+
+#define USLEEP (1000000 / 2)
+
+
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
+
+void delay (void)
+{
+#ifdef USLEEP
+  int i;
+
+  i = usleep (USLEEP);
+  assert (i == 0);
+#endif
+}
+
+static __attribute__((__noreturn__)) void crash (void)
+{
+#if 0
+  void *array[0x100];
+  int count;
+#endif
+  char command[256];
+
+  fputs (">>> CRASH START\n", stderr);
+#if 0
+  count = backtrace (array, ARRAY_SIZE (array));
+  backtrace_symbols_fd (array, count, STDERR_FILENO);
+#endif
+  snprintf (command, sizeof (command), "echo -e \"bt\\nquit\""
+           " >/tmp/debugger.%d; gdb --batch -nx --command=/tmp/debugger.%d"
+           " /proc/%d/exe %d </dev/null;rm -f /tmp/debugger.%d",
+           (int) getpid(), (int) getpid(), (int) getpid(), (int) getpid(),
+           (int) getpid());
+  system (command);
+  fputs (">>> CRASH FINISH\n", stderr);
+  abort ();
+}
+
+enum state
+  {
+    /* Separate the valid `1 << enum state' and `enum state' ranges.  */
+    STATE_FIRST = 4,
+    STATE_ENOENT,
+    STATE_SLEEPING,
+    STATE_RUNNING,
+    STATE_STOPPED,
+    STATE_PTRACED,
+    STATE_ZOMBIE,
+    STATE_LAST
+  };
+
+static const char *state_to_name (enum state state)
+{
+  switch (state)
+    {
+      case STATE_ENOENT:   return "STATE_ENOENT";
+      case STATE_SLEEPING: return "STATE_SLEEPING";
+      case STATE_RUNNING:  return "STATE_RUNNING";
+      case STATE_STOPPED:  return "STATE_STOPPED";
+      case STATE_PTRACED:  return "STATE_PTRACED";
+      case STATE_ZOMBIE:   return "STATE_ZOMBIE";
+      default: crash ();
+    }
+  /* NOTREACHED */
+  crash ();
+}
+
+#define STATE(pid, expect_mask) state ((pid), (expect_mask), #expect_mask )
+
+static enum state state (pid_t pid, unsigned expect_mask, const char *expect_mask_string)
+{
+  char status_name[32];
+  char line[LINE_MAX];
+  FILE *f;
+  enum state found;
+
+  delay ();
+
+  /* Sanity check `1 << enum state' was not misplaced with `enum state'.  */
+  assert (1 << (STATE_FIRST + 1) >= STATE_LAST);
+  assert (expect_mask != 0);
+#define MASK_UNDER_EXCLUSIVE(bit) ((1 << (bit)) - 1)
+#define MASK_ABOVE_INCLUSIVE(bit) (~MASK_UNDER_EXCLUSIVE (bit))
+  assert ((expect_mask & MASK_UNDER_EXCLUSIVE (STATE_FIRST + 1)) == 0);
+  assert ((expect_mask & MASK_ABOVE_INCLUSIVE (STATE_LAST)) == 0);
+#undef MASK_ABOVE_INCLUSIVE
+#undef MASK_UNDER_EXCLUSIVE
+
+  snprintf (status_name, sizeof (status_name), "/proc/%d/status", (int) pid);
+  f = fopen (status_name, "r");
+  if (f == NULL && errno == ENOENT)
+    found = STATE_ENOENT;
+  else if (f == NULL)
+    crash ();
+  else
+    {
+      int i;
+
+      found = STATE_ENOENT;
+      while (errno = 0, fgets (line, sizeof (line), f) != NULL)
+       {
+         const char *const string = "State:\t";
+         const size_t length = sizeof "State:\t" - 1;
+
+         if (strncmp (line, string, length) != 0)
+           continue;
+         if (strcmp (line + length, "S (sleeping)\n") == 0)
+           found = STATE_SLEEPING;
+         else if (strcmp (line + length, "R (running)\n") == 0)
+           found = STATE_RUNNING;
+         else if (strcmp (line + length, "T (stopped)\n") == 0)
+           found = STATE_STOPPED;
+         else if (strcmp (line + length, "T (tracing stop)\n") == 0)
+           found = STATE_PTRACED;
+         else if (strcmp (line + length, "Z (zombie)\n") == 0)
+           found = STATE_ZOMBIE;
+         else
+           {
+             fprintf (stderr, "Found an unknown state: %s", line + length);
+             crash ();
+           }
+       }
+      assert (found != STATE_ENOENT);
+      i = fclose (f);
+      assert (i == 0);
+    }
+  if (((1 << found) & expect_mask) == 0)
+    {
+      fprintf (stderr, "Found for PID %d state %s but expecting (%s)\n",
+              (int) pid, state_to_name (found), expect_mask_string);
+      crash ();
+    }
+  return found;
+}
+
+/* Debugging only: Number of signal needing redelivery on PTRACE_ATTACH.  */
+int attach_redelivered;
+
+static int attach_single (pid_t pid)
+{
+  int i;
+  int status;
+  int stopped;
+
+  attach_redelivered = 0;
+
+  delay ();
+
+#if 1
+  stopped = (STATE (pid, (1 << STATE_SLEEPING) | (1 << STATE_RUNNING)
+                        | (1 << STATE_STOPPED)) == STATE_STOPPED);
+#endif
+
+  i = ptrace (PTRACE_ATTACH, pid, NULL, NULL);
+  assert (i == 0);
+
+  /* FIXME: Why it does not work?
+     Disable also the STATE () call above.  */
+#if 0
+  delay();
+
+  i = ptrace (PTRACE_CONT, pid, (void *) 1, (void *) SIGSTOP);
+  /* `STOPPED == 1' may be false, even if the process was not stopped.  */
+  if (i == 0)
+    stopped = 1;
+  else if (errno == ESRCH)
+    stopped = 0;
+  else
+    crash ();
+#endif
+
+  for (;;)
+    {
+      delay ();
+
+      i = waitpid (pid, &status, 0);
+      assert (i == pid);
+      if (!WIFSTOPPED (status))
+        {
+         /* Process may have exited.  */
+         fprintf (stderr, "PID %d waitpid(2) status %d\n", (int) pid,
+                  status);
+         exit (EXIT_FAILURE);
+       }
+      if (WSTOPSIG (status) == SIGSTOP)
+       break;
+
+      if (attach_redelivered == 0)
+       attach_redelivered = WSTOPSIG (status);
+      else
+       attach_redelivered = -1;
+
+      delay ();
+
+      /* Re-deliver the signal received before SIGSTOP.
+         It happens with about only 1:200000 probability.  */
+      i = ptrace (PTRACE_CONT, pid, (void *) 1,
+                 (void *) (unsigned long) WSTOPSIG (status));
+      assert (i == 0);
+    }
+
+  return stopped;
+}
+
+struct ps_prochandle_module
+  {
+    struct ps_prochandle_module *next;
+    Dwfl_Module *module;
+  };
+
+struct ps_prochandle
+  {
+    pid_t pid;
+    Dwfl *dwfl;
+    struct ps_prochandle_module *module_list;
+  };
+
+static int get_dwfl_find_debuginfo (Dwfl_Module *mod, void **userdata,
+                                   const char *modname, GElf_Addr base,
+                                   const char *file_name,
+                                   const char *debuglink_file,
+                                   GElf_Word debuglink_crc,
+                                   char **debuginfo_file_name)
+{
+  printf("modname=<%s>,file_name=<%s>\n",modname,file_name);
+  return dwfl_standard_find_debuginfo (mod, userdata, modname, base, file_name,
+                                      debuglink_file, debuglink_crc,
+                                      debuginfo_file_name);
+}
+
+static Dwfl *get_dwfl (struct ps_prochandle *proc_handle)
+{
+  static char *debuginfo_path;
+
+  static const Dwfl_Callbacks proc_callbacks =
+   {
+     .find_debuginfo = get_dwfl_find_debuginfo,
+     .debuginfo_path = &debuginfo_path,
+
+     .find_elf = dwfl_linux_proc_find_elf,
+   };
+
+  if (proc_handle->dwfl == NULL)
+    {
+      proc_handle->dwfl = dwfl_begin (&proc_callbacks);
+      assert (proc_handle->dwfl != NULL);
+
+      if (dwfl_linux_proc_report (proc_handle->dwfl, proc_handle->pid) != 0
+        || dwfl_report_end (proc_handle->dwfl, NULL, NULL) != 0)
+       {
+         dwfl_end (proc_handle->dwfl);
+         proc_handle->dwfl = NULL;
+         crash ();
+       }
+    }
+  return proc_handle->dwfl;
+}
+
+/* Functions in this interface return one of these status codes.  */
+typedef enum
+{
+  PS_OK,               /* Generic "call succeeded".  */
+  PS_ERR,              /* Generic error. */
+  PS_BADPID,           /* Bad process handle.  */
+  PS_BADLID,           /* Bad LWP identifier.  */ 
+  PS_BADADDR,          /* Bad address.  */
+  PS_NOSYM,            /* Could not find given symbol.  */
+  PS_NOFREGS           /* FPU register set not available for given LWP.  */
+} ps_err_e;
+
+ps_err_e ps_pdread (struct ps_prochandle *proc_handle, psaddr_t addr,
+                   void *buffer, size_t length)
+{
+  crash ();
+}
+
+ps_err_e ps_pdwrite (struct ps_prochandle *proc_handle, psaddr_t addr,
+                    const void *buffer, size_t length)
+{
+  crash ();
+}
+
+ps_err_e ps_lgetregs (struct ps_prochandle *proc_handle, lwpid_t lwp,
+                     prgregset_t regs)
+{
+  crash ();
+}
+
+ps_err_e ps_lsetregs (struct ps_prochandle *proc_handle, lwpid_t lwp,
+                     const prgregset_t regs)
+{
+  crash ();
+}
+
+ps_err_e ps_lgetfpregs (struct ps_prochandle *proc_handle, lwpid_t lwp,
+                       prfpregset_t *fpregs)
+{
+  crash ();
+}
+
+ps_err_e ps_lsetfpregs (struct ps_prochandle *proc_handle, lwpid_t lwp,
+                       const prfpregset_t *fpregs)
+{
+  crash ();
+}
+
+/* Return the PID of the process.  */
+pid_t ps_getpid (struct ps_prochandle *proc_handle)
+{
+  crash ();
+}
+
+/* Look up the named symbol in the named DSO in the symbol tables
+   associated with the process being debugged, filling in *SYM_ADDR
+   with the corresponding run-time address.  */
+ps_err_e ps_pglobal_lookup (struct ps_prochandle *proc_handle,
+                           const char *object_name, const char *sym_name,
+                           psaddr_t *sym_addr)
+{
+  Dwfl *dwfl = get_dwfl (proc_handle);
+  dwfl = dwfl;
+  dwfl_addrmodule (dwfl, 0x3571d00000);
+
+#if 0
+  Elf *elf = FIXME;
+  Elf_Scn *scn = NULL;
+  int sym_count, ndx;
+
+  sym_count = dwfl_module_getsymtab (Dwfl_Module *mod);
+  assert (sym_count >= 0);
+  for (ndx = 0; ndx < sym_count; ndx++)
+    {
+      GElf_Sym sym;
+      const char *name_got;
+
+      name_got = dwfl_module_getsym (Dwfl_Module *mod, ndx, &sym, NULL);
+      assert (name_got != NULL);
+      if (strcmp (name_got, sym_name) == 0)
+        break;
+    }
+  assert (ndx < sym_count);
+
+extern Elf *dwarf_getelf (Dwarf *dwarf);
+
+
+  while ((scn = elf_nextscn (elf, scn)) != NULL)
+    {
+      /* Handle the section if it is a symbol table.  */
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+
+      if (shdr != NULL && shdr->sh_type == SHT_SYMTAB)
+        handle_symtab (ebl, scn, shdr);
+    }
+
+
+  for (modulei = 0; modulei < dwfl->nmodules; modulei++)
+    {
+      Dwfl_Module *m = dwfl->modules[modulei];
+
+      puts (m->name);
+    }
+#if 0
+  if (strcmp (sym_name, "nptl_version") == 0)
+    {
+      extern const char nptl_version[];
+      *sym_addr = (psaddr_t) nptl_version;
+      return PS_OK;
+    }
+#endif
+#endif
+  crash ();
+}
+
+int attach (pid_t pid)
+{
+  int stopped;
+  td_err_e err;
+  td_thragent_t *thread_agent;
+  struct ps_prochandle proc_handle_local;
+  static int thread_db_init = 0;
+
+  stopped = attach_single (pid);
+
+  if (thread_db_init == 0)
+    {
+      err = td_init ();
+      assert (err == TD_OK);
+      thread_db_init = 1;
+    }
+
+  proc_handle_local.pid = pid;
+  proc_handle_local.dwfl = NULL;
+  err = td_ta_new (&proc_handle_local, &thread_agent);
+  if (proc_handle_local.dwfl != NULL)
+    dwfl_end (proc_handle_local.dwfl);
+  if (err == TD_NOLIBTHREAD)
+    {
+      delay ();
+      return stopped;
+    }
+  assert (err == TD_OK);
+
+  assert (0);
+}
+
+void detach (pid_t pid, int stopped)
+{
+  int i;
+
+  delay ();
+
+  i = ptrace (PTRACE_DETACH, pid, NULL,
+             (void *) (unsigned long) (stopped ? SIGSTOP : 0));
+  assert (i == 0);
+
+  delay ();
+}
+
+#ifndef LIBRARY
+
+int main (int argc, char **argv)
+{
+  pid_t pid;
+  int stopped;
+
+  if (argc != 2)
+    {
+      fprintf (stderr, "Usage: %s <PID>\n", argv[0]);
+      exit (EXIT_FAILURE);
+    }
+
+  pid = atoi (argv[1]);
+
+  stopped = attach (pid);
+
+  detach (pid, stopped);
+
+  return EXIT_SUCCESS;
+}
+
+#endif /* !LIBRARY */
diff --git a/debugger.c-childhandler.patch b/debugger.c-childhandler.patch
new file mode 100644 (file)
index 0000000..4f07a79
--- /dev/null
@@ -0,0 +1,57 @@
+--- debugger.c 2007-04-07 10:17:12.000000000 +0200
++++ debugger.c-childhandler    2007-04-07 10:16:39.000000000 +0200
+@@ -26,6 +26,13 @@
+ #endif
+ }
++static int attach_handler_count;
++
++static void attach_handler (int signo)
++{
++  attach_handler_count++;
++}
++
+ /* Debugging only: Number of signal needing redelivery on PTRACE_ATTACH.  */
+ int attach_redelivered;
+@@ -40,6 +47,17 @@
+   delay ();
++  attach_handler_count = 0;
++  /* Fill SA_SIGACTION _before_ SA_HANDLER as they may be in union!  */
++  act.sa_sigaction = NULL;
++  act.sa_handler = attach_handler;
++  i = sigemptyset (&act.sa_mask);
++  assert (i == 0);
++  act.sa_flags = 
++  act.sa_restorer = NULL;
++
++  attach_handler_orig = signal (SIGCHLD, attach_handler);
++
+   i = ptrace (PTRACE_ATTACH, pid, NULL, NULL);
+   assert (i == 0);
+@@ -84,6 +102,22 @@
+       assert (i == 0);
+     }
++  attach_handler_orig = signal (SIGCHLD, attach_handler_orig);
++  assert (attach_handler_orig == attach_handler);
++
++  if (stopped == 1 && attach_handler_count == 2)
++    stopped = 0;
++  else if (stopped == 1 && attach_handler_count == 1)
++    stopped = 1;
++  else if (stopped == 0 && attach_handler_count == 1)
++    stopped = 0;
++  else
++    {
++      fprintf (stderr, "stopped = %d, attach_handler_count = %d\n", stopped,
++             attach_handler_count);
++      abort ();
++    }
++
+   delay ();
+   return stopped;
diff --git a/debugger.h b/debugger.h
new file mode 100644 (file)
index 0000000..e64f2c6
--- /dev/null
@@ -0,0 +1,10 @@
+/* Copyright 2007, Red Hat Inc.  */
+
+#include <sys/types.h>
+
+
+extern int attach (pid_t pid);
+extern void detach (pid_t pid, int stopped);
+
+extern int attach_redelivered;
+extern void delay (void);
diff --git a/testsuite.c b/testsuite.c
new file mode 100644 (file)
index 0000000..fb07f45
--- /dev/null
@@ -0,0 +1,425 @@
+/* Copyright 2007, Red Hat Inc.  */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <linux/unistd.h>
+#include <limits.h>
+#include <string.h>
+#include <pthread.h>
+
+#include "debugger.h"
+
+#define LIBRARY 1
+#include "debugger.c"
+
+
+_syscall2(int, tkill, int, tid, int, sig)
+int tkill(int tid, int sig);
+
+
+static int attach_checked (pid_t pid, int redelivered_expect)
+{
+  int stopped;
+
+  STATE (pid, (1 << STATE_SLEEPING) | (1 << STATE_RUNNING) | (1 << STATE_STOPPED));
+  stopped = attach (pid);
+  if (attach_redelivered != redelivered_expect)
+    {
+      fprintf (stderr, "Expecting redelivery of %d but found %d\n",
+              redelivered_expect, attach_redelivered);
+      abort ();
+    }
+  /* FIXME: Why also STATE_STOPPED?  */
+  STATE (pid, (1 << STATE_PTRACED) | (1 << STATE_STOPPED));
+  return stopped;
+}
+
+static void detach_checked (pid_t pid, int stopped)
+{
+  /* FIXME: Why STATE_STOPPED?  */
+  STATE (pid, (stopped ? 1 << STATE_STOPPED : 1 << STATE_PTRACED));
+  detach (pid, stopped);
+  STATE (pid, (stopped ? 1 << STATE_STOPPED : (1 << STATE_SLEEPING) | (1 << STATE_RUNNING)));
+}
+
+struct registry
+  {
+    struct registry *next;
+    pid_t pid;
+  };
+struct registry *registry_list;
+
+static void registry_add (pid_t pid)
+{
+  struct registry *new;
+
+  new = malloc (sizeof (*new));
+  assert (new != NULL);
+  new->pid = pid;
+  new->next = registry_list;
+  registry_list = new;
+}
+
+static void registry_remove (pid_t pid)
+{
+  struct registry **iter_pointer;
+
+  for (iter_pointer = &registry_list; *iter_pointer != NULL;
+       iter_pointer = &(*iter_pointer)->next)
+    {
+      struct registry *found = *iter_pointer;
+      if (found->pid != pid)
+        continue;
+
+      *iter_pointer = found->next;
+      free (found);
+      return;
+    }
+  abort ();
+}
+
+static void registry_atexit (void)
+{
+  struct registry *iter;
+
+  for (iter = registry_list; iter != NULL; iter = iter->next)
+    kill (iter->pid, SIGKILL);
+}
+
+static void registry_handler (int signo)
+{
+  signal (signo, SIG_DFL);
+  registry_atexit ();
+  raise (signo);
+}
+
+static void child_pause (void)
+{
+  for (;;)
+    pause ();
+  /* NOTREACHED */
+  abort ();
+}
+
+static void child_alrm_handler (int signo)
+{
+  assert (signo == SIGALRM);
+  raise (SIGALRM);
+}
+
+static void child_alrm (void)
+{
+  void (*handler_orig) (int signo);
+#if 0
+  int i;
+  sigset_t oldset;
+#endif
+
+  handler_orig = signal (SIGALRM, child_alrm_handler);
+  assert (handler_orig == SIG_DFL);
+
+#if 0
+  i = sigprocmask (SIG_BLOCK, NULL, &oldset);
+  assert (i == 0);
+  printf ("sigprocmask () -> sigismember (SIGALRM) == %d\n", sigismember (&oldset, SIGALRM));
+#endif
+
+  for (;;)
+    pause ();
+  /* NOTREACHED */
+  abort ();
+}
+
+static pid_t spawn (void *(*child) (void *data, void *input), void *data,
+                   void *input, int fd_close)
+{
+  pid_t pid;
+
+  pid = fork();
+  switch (pid)
+    {
+      case -1:
+        perror ("fork()");
+       exit (EXIT_FAILURE);
+       /* NOTREACHED */
+      case 0:
+        if (fd_close != -1)
+         {
+           int i;
+
+           i = close (fd_close);
+           assert (i == 0);
+         }
+       child (data, input);
+       /* NOTREACHED */
+       abort ();
+      default:;
+       /* PASSTHRU */
+    }
+  /* Parent.  */
+
+  registry_add (pid);
+  STATE (pid, (1 << STATE_SLEEPING) | (1 << STATE_RUNNING));
+  return pid;
+}
+
+static void murder (pid_t pid)
+{
+  int i;
+  pid_t pid_got;
+  int status;
+
+  if (pid == 0)
+    return;
+
+  i = kill (pid, SIGKILL);
+  assert (i == 0);
+
+  pid_got = waitpid (pid, &status, 0);
+  if (!(pid_got == -1 && errno == ECHILD))
+    {
+      assert (pid_got == pid);
+      assert ((WIFSIGNALED (status) && WTERMSIG (status) == SIGKILL)
+             || (WIFEXITED (status) && WEXITSTATUS (status) == 0));
+      STATE (pid, 1 << STATE_ENOENT);
+    }
+  else
+    STATE (pid, (1 << STATE_ENOENT) | (1 << STATE_ZOMBIE));
+
+  registry_remove (pid);
+}
+
+
+struct child_spawner
+  {
+    void *(*child) (void *data, void *input);
+    void *data;
+    int fd;
+  };
+
+static void *child_spawner (void *param_voidpointer, void *input)
+{
+  struct child_spawner *param = param_voidpointer;
+  pid_t inferior;
+  ssize_t inferior_size;
+  int i;
+
+  inferior = spawn (param->child, param->data, input, param->fd);
+
+  inferior_size = write (param->fd, &inferior, sizeof (inferior));
+  assert (inferior_size == sizeof (inferior));
+  i = close (param->fd);
+  assert (i == 0);
+
+  waitpid (inferior, NULL, 0);
+  _exit (EXIT_SUCCESS);
+  /* NOTREACHED */
+  abort ();
+}
+
+struct spawner
+  {
+    void *(*func) (void *data, void *input);
+    void *data;
+  };
+
+static void *spawn_with_waiter (void *data, void *input)
+{
+  pid_t waiter;
+  struct child_spawner param_local;
+  int pipefds[2];
+  int i;
+  pid_t inferior;
+  ssize_t inferior_size;
+  unsigned char buf;
+  ssize_t buf_size;
+  struct spawner *param = data;
+
+  assert (data != NULL);
+
+  i = pipe (pipefds);
+  assert (i == 0);
+
+  param_local.child = param->func;
+  param_local.data = param->data;
+  param_local.fd = pipefds[1];
+  waiter = spawn (child_spawner, &param_local, input, pipefds[0]);
+
+  i = close (pipefds[1]);
+  assert (i == 0);
+  inferior_size = read (pipefds[0], &inferior, sizeof (inferior));
+  assert (inferior_size == sizeof (inferior));
+  buf_size = read (pipefds[0], &buf, sizeof (buf));
+  assert (buf_size == 0);
+  i = close (pipefds[0]);
+  assert (i == 0);
+
+  registry_add (inferior);
+
+  return (void *) (unsigned long) inferior;
+}
+
+static void *spawn_without_waiter (void *data, void *input)
+{
+  struct spawner *param = data;
+
+  return (void *) (unsigned long) spawn (param->func, param->data, input, -1);
+}
+
+static void body_spawner (void *(*child) (void *data, void *input), void *data,
+                         void *input)
+{
+  pid_t inferior;
+  int stopped;
+  int i;
+
+  assert (input == NULL);
+
+  /* Plain attach/detach.  */
+  inferior = (unsigned long) (*child) (data, child_pause);
+  stopped = attach_checked (inferior, 0);
+  assert (stopped == 0);
+  detach_checked (inferior, stopped);
+  murder (inferior);
+
+  /* Attach to a process stopped by standard kill(2).  */
+  inferior = (unsigned long) (*child) (data, child_pause);
+  delay ();
+  i = kill (inferior, SIGSTOP);
+  assert (i == 0);
+  STATE (inferior, 1 << STATE_STOPPED);
+  stopped = attach_checked (inferior, 0);
+  assert (stopped == 1);
+  detach_checked (inferior, stopped);
+  murder (inferior);
+
+  /* Attach to a process stopped by Linux specific tkill(2).  */
+  inferior = (unsigned long) (*child) (data, child_pause);
+  delay ();
+  i = tkill (inferior, SIGSTOP);
+  assert (i == 0);
+  STATE (inferior, 1 << STATE_STOPPED);
+  stopped = attach_checked (inferior, 0);
+  assert (stopped == 1);
+  detach_checked (inferior, stopped);
+  murder (inferior);
+
+  /* Attach to a stopped process with already pending SIGALRM.  */
+  inferior = (unsigned long) (*child) (data, child_alrm);
+  STATE (inferior, 1 << STATE_SLEEPING);
+  delay ();
+  i = tkill (inferior, SIGSTOP);
+  assert (i == 0);
+  delay ();
+  i = tkill (inferior, SIGALRM);
+  assert (i == 0);
+  STATE (inferior, 1 << STATE_STOPPED);
+  /* FIXME: SIGALRM did not get redelivered?  */
+#if 0
+  stopped = attach_checked (inferior, SIGALRM);
+#else
+  stopped = attach_checked (inferior, 0);
+#endif
+  assert (stopped == 1);
+  detach_checked (inferior, stopped);
+  STATE (inferior, 1 << STATE_STOPPED);
+  delay ();
+  i = tkill (inferior, SIGCONT);
+  assert (i == 0);
+  STATE (inferior, 1 << STATE_RUNNING);
+  murder (inferior);
+}
+
+static void *pass (void *data, void *input)
+{
+  struct spawner *param = data;
+  void (*input_func) (void) = input;
+
+  if (param != NULL)
+    return (*param->func) (param->data, input);
+
+  assert (input_func != NULL);
+  (*input_func) ();
+  /* NOTREACHED */
+  abort ();
+}
+
+static void *spawn_singlethreaded (void *data, void *input)
+{
+  return pass (data, input);
+}
+
+static void *spawn_threaded_parent_start (void *arg)
+{
+  for (;;)
+    pause ();
+  /* NOTREACHED */
+  abort ();
+}
+
+static void *spawn_threaded_parent (void *data, void *input)
+{
+  pthread_t thread;
+  int i;
+
+  i = pthread_create (&thread, NULL, spawn_threaded_parent_start, NULL);
+  assert (i == 0);
+  return pass (data, input);
+}
+
+struct spawn_threaded_child_start
+  {
+    void *data;
+    void *input;
+  };
+
+static void *spawn_threaded_child_start (void *arg_voidpointer)
+{
+  struct spawn_threaded_child_start *arg = arg_voidpointer;
+
+  return pass (arg->data, arg->input);
+  /* NOTREACHED */
+}
+
+static void *spawn_threaded_child (void *data, void *input)
+{
+  pthread_t thread;
+  int i;
+  struct spawn_threaded_child_start arg_local;
+
+  arg_local.data = data;
+  arg_local.input = input;
+  i = pthread_create (&thread, NULL, spawn_threaded_child_start, &arg_local);
+  assert (i == 0);
+  i = pthread_join (thread, NULL);
+  assert (i == 0);
+
+  _exit (EXIT_SUCCESS);
+}
+
+static void body_maywaiter (void *(*child) (void *data, void *input),
+                           void *data, void *input)
+{
+  struct spawner param_local;
+
+  param_local.func = child;
+  param_local.data = data;
+  body_spawner (spawn_without_waiter, &param_local, NULL);
+  body_spawner (spawn_with_waiter, &param_local, NULL);
+}
+
+int main (int argc, char **argv)
+{
+  atexit (registry_atexit);
+  signal (SIGINT, registry_handler);
+  signal (SIGABRT, registry_handler);
+
+  body_maywaiter (spawn_singlethreaded, NULL, NULL);
+  body_maywaiter (spawn_threaded_parent, NULL, NULL);
+  body_maywaiter (spawn_threaded_child, NULL, NULL);
+
+  return EXIT_SUCCESS;
+}