New state: STATE_DISK_SLEEP
[debugger.git] / debugger.c
index f880038..fd6e0f7 100644 (file)
@@ -1,5 +1,8 @@
 /* Copyright 2007, Red Hat Inc.  */
 
+#define _GNU_SOURCE 1
+#define ps_prochandle attach_state_struct
+
 #include <unistd.h>
 #include <assert.h>
 #include <stdlib.h>
 #include <sys/wait.h>
 #include <limits.h>
 #include <string.h>
-#include <execinfo.h>
-#include <thread_db.h>
+#include <time.h>
+#include <sys/reg.h>
+#include <linux/ptrace.h>
+#include <asm/prctl.h>
 #include <elfutils/libdwfl.h>
+#include <thread_db.h>
+#include <fcntl.h>
 
 #include "debugger.h"
 
 
+/* Insert constant delay for before each critical check?  */
+#if 0
 #define USLEEP (1000000 / 2)
+#endif
+
+#if 1  /* Standard run.  */
+#define TIMEOUT_SECS 4
+#define LOOPS_MIN 100000
+#else  /* QEMU?  */
+/* LOOPS_MIN is a safety as QEMU clock time sucks.
+   100000 is 4s natively and 53s in QEMU.  */
+#define TIMEOUT_SECS 20
+#define LOOPS_MIN 500000
+#endif /* QEMU?  */
 
 
 #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
@@ -60,12 +80,15 @@ enum state
   {
     /* Separate the valid `1 << enum state' and `enum state' ranges.  */
     STATE_FIRST = 4,
+    STATE_INSTABLE,
     STATE_ENOENT,
     STATE_SLEEPING,
     STATE_RUNNING,
     STATE_STOPPED,
     STATE_PTRACED,
     STATE_ZOMBIE,
+    STATE_DEAD,
+    STATE_DISK_SLEEP,
     STATE_LAST
   };
 
@@ -73,21 +96,22 @@ 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";
+      case STATE_INSTABLE:   return "STATE_INSTABLE";
+      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";
+      case STATE_DEAD:       return "STATE_DEAD";
+      case STATE_DISK_SLEEP: return "STATE_DISK_SLEEP";
       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)
+static enum state state_get (pid_t pid)
 {
   char status_name[32];
   char line[LINE_MAX];
@@ -96,27 +120,20 @@ static enum state state (pid_t pid, unsigned expect_mask, const char *expect_mas
 
   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 ();
+    {
+      fprintf (stderr, "errno = %d\n", errno);
+      crash ();
+    }
   else
     {
       int i;
 
-      found = STATE_ENOENT;
+      found = STATE_INSTABLE;
       while (errno = 0, fgets (line, sizeof (line), f) != NULL)
        {
          const char *const string = "State:\t";
@@ -134,42 +151,114 @@ static enum state state (pid_t pid, unsigned expect_mask, const char *expect_mas
            found = STATE_PTRACED;
          else if (strcmp (line + length, "Z (zombie)\n") == 0)
            found = STATE_ZOMBIE;
+         /* FIXME: What does it mean?  */
+         else if (strcmp (line + length, "X (dead)\n") == 0)
+           found = STATE_DEAD;
+         else if (strcmp (line + length, "D (disk sleep)\n") == 0)
+           found = STATE_DISK_SLEEP;
          else
            {
              fprintf (stderr, "Found an unknown state: %s", line + length);
              crash ();
            }
        }
-      assert (found != STATE_ENOENT);
+      assert (found != STATE_INSTABLE || errno == ESRCH);
       i = fclose (f);
       assert (i == 0);
     }
-  if (((1 << found) & expect_mask) == 0)
+  return found;
+}
+
+#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)
+{
+  enum state found;
+  time_t timeout = time (NULL) + TIMEOUT_SECS;
+  unsigned loops = 0;
+
+  /* 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
+
+  do
     {
-      fprintf (stderr, "Found for PID %d state %s but expecting (%s)\n",
-              (int) pid, state_to_name (found), expect_mask_string);
-      crash ();
+      found = state_get (pid);
+
+      if (((1 << found) & expect_mask) != 0)
+        return found;
     }
-  return found;
+  while (loops++ < LOOPS_MIN || time (NULL) < timeout);
+
+  fprintf (stderr, "Found for PID %d state %s but expecting (%s)\n",
+          (int) pid, state_to_name (found), expect_mask_string);
+  crash ();
+}
+
+struct attach_state_struct
+  {
+    pid_t pid;
+    Dwfl *dwfl;
+    int stopped;
+    td_thragent_t *thread_agent;
+    /* Debugging only: Number of signal needing redelivery on PTRACE_ATTACH.  */
+    int redelivered;
+    int threads_count;
+  };
+
+int attach_state_redelivered_get (struct attach_state_struct *attach_state)
+{
+  assert (attach_state != NULL);
+
+  return attach_state->redelivered;
+}
+
+int attach_state_threads_count_get (struct attach_state_struct *attach_state)
+{
+  assert (attach_state != NULL);
+
+  return attach_state->threads_count;
 }
 
-/* Debugging only: Number of signal needing redelivery on PTRACE_ATTACH.  */
-int attach_redelivered;
+int attach_state_stopped_get (struct attach_state_struct *attach_state)
+{
+  assert (attach_state != NULL);
+
+  return attach_state->stopped;
+}
+
+pid_t attach_state_pid_get (struct attach_state_struct *attach_state)
+{
+  assert (attach_state != NULL);
 
-static int attach_single (pid_t pid)
+  return attach_state->pid;
+}
+
+struct attach_state_struct *attach_single (pid_t pid)
 {
   int i;
   int status;
-  int stopped;
+  struct attach_state_struct *attach_state;
 
-  attach_redelivered = 0;
+  attach_state = malloc (sizeof (*attach_state));
+  assert (attach_state != NULL);
+  attach_state->pid = pid;
+  attach_state->dwfl = NULL;
+  attach_state->redelivered = 0;
+  attach_state->threads_count = -1;
 
   delay ();
 
-#if 1
-  stopped = (STATE (pid, (1 << STATE_SLEEPING) | (1 << STATE_RUNNING)
-                        | (1 << STATE_STOPPED)) == STATE_STOPPED);
-#endif
+  attach_state->stopped = (STATE (pid, (1 << STATE_SLEEPING)
+                                      | (1 << STATE_RUNNING)
+                                      | (1 << STATE_STOPPED))
+                          == STATE_STOPPED);
 
   i = ptrace (PTRACE_ATTACH, pid, NULL, NULL);
   assert (i == 0);
@@ -182,9 +271,9 @@ static int attach_single (pid_t pid)
   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;
+    attach_state->stopped = 1;
   else if (errno == ESRCH)
-    stopped = 0;
+    attach_state->stopped = 0;
   else
     crash ();
 #endif
@@ -205,10 +294,10 @@ static int attach_single (pid_t pid)
       if (WSTOPSIG (status) == SIGSTOP)
        break;
 
-      if (attach_redelivered == 0)
-       attach_redelivered = WSTOPSIG (status);
+      if (attach_state->redelivered == 0)
+       attach_state->redelivered = WSTOPSIG (status);
       else
-       attach_redelivered = -1;
+       attach_state->redelivered = -1;
 
       delay ();
 
@@ -219,33 +308,20 @@ static int attach_single (pid_t pid)
       assert (i == 0);
     }
 
-  return stopped;
+  return attach_state;
 }
 
-struct ps_prochandle_module
-  {
-    struct ps_prochandle_module *next;
-    Dwfl_Module *module;
-  };
+void detach_single (struct attach_state_struct *attach_state)
+{
+  int i;
 
-struct ps_prochandle
-  {
-    pid_t pid;
-    Dwfl *dwfl;
-    struct ps_prochandle_module *module_list;
-  };
+  delay ();
 
-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);
+  i = ptrace (PTRACE_DETACH, attach_state->pid, NULL,
+             (void *) (unsigned long) (attach_state->stopped ? SIGSTOP : 0));
+  assert (i == 0);
+
+  delay ();
 }
 
 static Dwfl *get_dwfl (struct ps_prochandle *proc_handle)
@@ -254,7 +330,7 @@ static Dwfl *get_dwfl (struct ps_prochandle *proc_handle)
 
   static const Dwfl_Callbacks proc_callbacks =
    {
-     .find_debuginfo = get_dwfl_find_debuginfo,
+     .find_debuginfo = dwfl_standard_find_debuginfo,
      .debuginfo_path = &debuginfo_path,
 
      .find_elf = dwfl_linux_proc_find_elf,
@@ -265,12 +341,12 @@ static Dwfl *get_dwfl (struct ps_prochandle *proc_handle)
       proc_handle->dwfl = dwfl_begin (&proc_callbacks);
       assert (proc_handle->dwfl != NULL);
 
+      errno = 0;
       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 ();
+         fprintf (stderr, "dwfl reporting: %m\n");
+         abort ();
        }
     }
   return proc_handle->dwfl;
@@ -291,7 +367,48 @@ typedef enum
 ps_err_e ps_pdread (struct ps_prochandle *proc_handle, psaddr_t addr,
                    void *buffer, size_t length)
 {
-  crash ();
+  pid_t pid = proc_handle->pid;
+  char filename[64];
+  int fd;
+
+  if (pid == getpid ())
+    {
+      memcpy (buffer, addr, length);
+      return PS_OK;
+    }
+
+/* Already under PTRACE_ATTACH.  */
+#if 0
+  if (ptrace (PTRACE_ATTACH, pid, NULL, NULL) != 0)
+    abort ();
+  if (waitpid (pid, NULL, 0) != pid)
+    abort ();
+#endif
+
+  snprintf (filename, sizeof (filename), "/proc/%ld/mem", (long) pid);
+  fd = open (filename, O_RDONLY);
+  assert (fd != -1);
+  if (lseek64 (fd, (off64_t) addr, SEEK_SET) != (off64_t) addr)
+    abort ();
+  if (read (fd, buffer, length) != length)
+    {
+      /* It occurs for singlethreaded processes.  */
+      if ((off64_t) addr == 0)
+        return PS_ERR;
+      fprintf (stderr, "read() error @0x%lx length %lu: %m\n",
+              (unsigned long) addr, (unsigned long) length);
+      abort ();
+    }
+  if (close (fd) != 0)
+    abort ();
+
+/* Already under PTRACE_ATTACH.  */
+#if 0
+  if (ptrace (PTRACE_DETACH, pid, NULL, NULL) != 0)
+    abort ();
+#endif
+
+  return PS_OK;
 }
 
 ps_err_e ps_pdwrite (struct ps_prochandle *proc_handle, psaddr_t addr,
@@ -327,114 +444,170 @@ ps_err_e ps_lsetfpregs (struct ps_prochandle *proc_handle, lwpid_t lwp,
 /* Return the PID of the process.  */
 pid_t ps_getpid (struct ps_prochandle *proc_handle)
 {
-  crash ();
+  return proc_handle->pid;
 }
 
-/* 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);
+struct getmodules_callback_arg
+  {
+    const char *sym_name;
+    psaddr_t *sym_addr;
+    ps_err_e retval;
+  };
 
-#if 0
-  Elf *elf = FIXME;
-  Elf_Scn *scn = NULL;
+static int getmodules_callback (Dwfl_Module *module,
+                               void **module_userdata_pointer,
+                               const char *module_name,
+                               Dwarf_Addr module_low_addr, void *arg)
+{
+  struct getmodules_callback_arg *getmodules_callback_arg = arg;
   int sym_count, ndx;
+  GElf_Sym sym;
 
-  sym_count = dwfl_module_getsymtab (Dwfl_Module *mod);
+  sym_count = dwfl_module_getsymtab (module);
+  if (sym_count == -1)
+    return DWARF_CB_OK;
   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);
+      name_got = dwfl_module_getsym (module, ndx, &sym,
+                                    NULL);
       assert (name_got != NULL);
-      if (strcmp (name_got, sym_name) == 0)
+      if (strcmp (name_got, getmodules_callback_arg->sym_name) == 0)
         break;
     }
-  assert (ndx < sym_count);
+  if (ndx == sym_count)
+    return DWARF_CB_OK;
 
-extern Elf *dwarf_getelf (Dwarf *dwarf);
+  *getmodules_callback_arg->sym_addr = (psaddr_t) sym.st_value;
+  getmodules_callback_arg->retval = PS_OK;
 
+  return DWARF_CB_OK;
+}
 
-  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);
+/* 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);
+  struct getmodules_callback_arg getmodules_callback_arg;
+  ptrdiff_t err_ptrdiff;
+
+  /* FIXME: `object_name' ignored due to missing unresolving of shared
+     libraries symbolic links.  */
+  getmodules_callback_arg.sym_name = sym_name;
+  getmodules_callback_arg.sym_addr = sym_addr;
+  getmodules_callback_arg.retval = PS_NOSYM;
+  err_ptrdiff = dwfl_getmodules (dwfl, getmodules_callback,
+                                &getmodules_callback_arg, 0);
+  assert (err_ptrdiff == 0);
+  return getmodules_callback_arg.retval;
+}
 
-      if (shdr != NULL && shdr->sh_type == SHT_SYMTAB)
-        handle_symtab (ebl, scn, shdr);
-    }
+#ifdef __x86_64__
+ps_err_e ps_get_thread_area (const struct ps_prochandle *ph, lwpid_t lwpid,
+                            int idx, void **base)
+{
+  long val;
 
+  assert (idx == FS || idx == GS);
 
-  for (modulei = 0; modulei < dwfl->nmodules; modulei++)
-    {
-      Dwfl_Module *m = dwfl->modules[modulei];
+  if (ptrace (PTRACE_ATTACH, lwpid, NULL, NULL) != 0)
+    abort ();
+  if (waitpid (lwpid, NULL, 0) != lwpid)
+    abort ();
 
-      puts (m->name);
-    }
-#if 0
-  if (strcmp (sym_name, "nptl_version") == 0)
+  val = ptrace (PTRACE_ARCH_PRCTL, lwpid, base, (idx == FS ? ARCH_GET_FS
+                                                          : ARCH_GET_GS));
+
+  if (ptrace (PTRACE_DETACH, lwpid, NULL, NULL) != 0)
+    abort ();
+
+  if (val != 0)
     {
-      extern const char nptl_version[];
-      *sym_addr = (psaddr_t) nptl_version;
-      return PS_OK;
+      fprintf (stderr, "PTRACE_ARCH_PRCTL (%s): %m\n", (idx == FS ? "FS"
+                                                                 : "GS"));
+      return PS_ERR;
     }
+
+  return PS_OK;
+}
+#else
+#error "Unsupported ps_get_thread_area ()!"
 #endif
-#endif
-  crash ();
+
+static int find_new_threads_callback (const td_thrhandle_t *th_p, void *data)
+{
+  td_err_e err;
+  td_thrinfo_t info;
+  struct attach_state_struct *attach_state = data;
+
+  err = td_thr_get_info (th_p, &info);
+  assert (err == TD_OK);
+  /* printf ("LWP = %ld TID = 0x%lx\n", (long) info.ti_lid, info.ti_tid);  */
+
+  attach_state->threads_count++;
+
+  return 0;
 }
 
-int attach (pid_t pid)
+static void attach_multi (struct attach_state_struct *attach_state)
 {
-  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);
+  err = td_init ();
+  assert (err == TD_OK);
 
-  if (thread_db_init == 0)
+  err = td_ta_new (attach_state, &attach_state->thread_agent);
+  assert (err == TD_OK || err == TD_NOLIBTHREAD);
+  attach_state->threads_count = 0;
+  if (err == TD_NOLIBTHREAD)
     {
-      err = td_init ();
-      assert (err == TD_OK);
-      thread_db_init = 1;
+      attach_state->thread_agent = NULL;
+      return;
     }
-
-  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)
+  assert (attach_state->thread_agent != NULL);
+  /* Multithreading test:  */
+  err = td_ta_thr_iter (attach_state->thread_agent, find_new_threads_callback,
+                       attach_state, TD_THR_ANY_STATE, TD_THR_LOWEST_PRIORITY,
+                       TD_SIGNO_MASK, TD_THR_ANY_USER_FLAGS);
+  if (err != TD_OK)
     {
-      delay ();
-      return stopped;
+      fprintf (stderr, "err = %d\n", err);
+      abort ();
     }
-  assert (err == TD_OK);
-
-  assert (0);
+  /* Do not: assert (attach_state->threads_count > 0);
+     as during early initialization we really may get 0.  */
 }
 
-void detach (pid_t pid, int stopped)
+struct attach_state_struct *attach (pid_t pid)
 {
-  int i;
+  struct attach_state_struct *attach_state;
 
-  delay ();
+  attach_state = attach_single (pid);
+  attach_multi (attach_state);
 
-  i = ptrace (PTRACE_DETACH, pid, NULL,
-             (void *) (unsigned long) (stopped ? SIGSTOP : 0));
-  assert (i == 0);
+  return attach_state;
+}
 
-  delay ();
+void detach (struct attach_state_struct *attach_state)
+{
+  if (attach_state->thread_agent != NULL)
+    {
+      td_err_e err;
+
+      err = td_ta_delete (attach_state->thread_agent);
+      assert (err == TD_OK);
+    }
+  if (attach_state->dwfl != NULL);
+    dwfl_end (attach_state->dwfl);
+
+  detach_single (attach_state);
 }
 
 #ifndef LIBRARY
@@ -442,7 +615,7 @@ void detach (pid_t pid, int stopped)
 int main (int argc, char **argv)
 {
   pid_t pid;
-  int stopped;
+  struct attach_state_struct *attach_state;
 
   if (argc != 2)
     {
@@ -452,9 +625,8 @@ int main (int argc, char **argv)
 
   pid = atoi (argv[1]);
 
-  stopped = attach (pid);
-
-  detach (pid, stopped);
+  attach_state = attach (pid);
+  detach (attach_state);
 
   return EXIT_SUCCESS;
 }