merge from gcc
[external/binutils.git] / gdb / gdbserver / linux-x86-low.c
index ffb70dc..37fe60f 100644 (file)
@@ -1,6 +1,6 @@
 /* GNU/Linux/x86-64 specific low level interface, for the remote server
    for GDB.
-   Copyright (C) 2002, 2004, 2005, 2006, 2007, 2008, 2009
+   Copyright (C) 2002, 2004, 2005, 2006, 2007, 2008, 2009, 2010
    Free Software Foundation, Inc.
 
    This file is part of GDB.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-#include <assert.h>
+#include <stddef.h>
 #include <signal.h>
 #include "server.h"
 #include "linux-low.h"
 #include "i387-fp.h"
+#include "i386-low.h"
 
 #include "gdb_proc_service.h"
 
-/* NOTE: gdb_proc_service.h may include linux/elf.h.
-   We need Elf32_Phdr.  If we don't get linux/elf.h we could include
-   elf.h like linux-ppc-low.c does.  */
-
-/* Defined in auto-generated file reg-i386-linux.c.  */
+/* Defined in auto-generated file i386-linux.c.  */
 void init_registers_i386_linux (void);
-/* Defined in auto-generated file reg-x86-64-linux.c.  */
-void init_registers_x86_64_linux (void);
+/* Defined in auto-generated file amd64-linux.c.  */
+void init_registers_amd64_linux (void);
 
 #include <sys/reg.h>
 #include <sys/procfs.h>
@@ -57,6 +54,21 @@ void init_registers_x86_64_linux (void);
 #define ARCH_GET_GS 0x1004
 #endif
 
+/* Per-process arch-specific data we want to keep.  */
+
+struct arch_process_info
+{
+  struct i386_debug_reg_state debug_reg_state;
+};
+
+/* Per-thread arch-specific data we want to keep.  */
+
+struct arch_lwp_info
+{
+  /* Non-zero if our copy differs from what's recorded in the thread.  */
+  int debug_registers_changed;
+};
+
 #ifdef __x86_64__
 
 /* Mapping between the general-purpose registers in `struct user'
@@ -161,7 +173,7 @@ i386_cannot_fetch_register (int regno)
 }
 
 static void
-x86_fill_gregset (void *buf)
+x86_fill_gregset (struct regcache *regcache, void *buf)
 {
   int i;
 
@@ -170,19 +182,20 @@ x86_fill_gregset (void *buf)
     {
       for (i = 0; i < X86_64_NUM_REGS; i++)
        if (x86_64_regmap[i] != -1)
-         collect_register (i, ((char *) buf) + x86_64_regmap[i]);
+         collect_register (regcache, i, ((char *) buf) + x86_64_regmap[i]);
       return;
     }
 #endif
 
   for (i = 0; i < I386_NUM_REGS; i++)
-    collect_register (i, ((char *) buf) + i386_regmap[i]);
+    collect_register (regcache, i, ((char *) buf) + i386_regmap[i]);
 
-  collect_register_by_name ("orig_eax", ((char *) buf) + ORIG_EAX * 4);
+  collect_register_by_name (regcache, "orig_eax",
+                           ((char *) buf) + ORIG_EAX * 4);
 }
 
 static void
-x86_store_gregset (const void *buf)
+x86_store_gregset (struct regcache *regcache, const void *buf)
 {
   int i;
 
@@ -191,49 +204,50 @@ x86_store_gregset (const void *buf)
     {
       for (i = 0; i < X86_64_NUM_REGS; i++)
        if (x86_64_regmap[i] != -1)
-         supply_register (i, ((char *) buf) + x86_64_regmap[i]);
+         supply_register (regcache, i, ((char *) buf) + x86_64_regmap[i]);
       return;
     }
 #endif
 
   for (i = 0; i < I386_NUM_REGS; i++)
-    supply_register (i, ((char *) buf) + i386_regmap[i]);
+    supply_register (regcache, i, ((char *) buf) + i386_regmap[i]);
 
-  supply_register_by_name ("orig_eax", ((char *) buf) + ORIG_EAX * 4);
+  supply_register_by_name (regcache, "orig_eax",
+                          ((char *) buf) + ORIG_EAX * 4);
 }
 
 static void
-x86_fill_fpregset (void *buf)
+x86_fill_fpregset (struct regcache *regcache, void *buf)
 {
 #ifdef __x86_64__
-  i387_cache_to_fxsave (buf);
+  i387_cache_to_fxsave (regcache, buf);
 #else
-  i387_cache_to_fsave (buf);
+  i387_cache_to_fsave (regcache, buf);
 #endif
 }
 
 static void
-x86_store_fpregset (const void *buf)
+x86_store_fpregset (struct regcache *regcache, const void *buf)
 {
 #ifdef __x86_64__
-  i387_fxsave_to_cache (buf);
+  i387_fxsave_to_cache (regcache, buf);
 #else
-  i387_fsave_to_cache (buf);
+  i387_fsave_to_cache (regcache, buf);
 #endif
 }
 
 #ifndef __x86_64__
 
 static void
-x86_fill_fpxregset (void *buf)
+x86_fill_fpxregset (struct regcache *regcache, void *buf)
 {
-  i387_cache_to_fxsave (buf);
+  i387_cache_to_fxsave (regcache, buf);
 }
 
 static void
-x86_store_fpxregset (const void *buf)
+x86_store_fpxregset (struct regcache *regcache, const void *buf)
 {
-  i387_fxsave_to_cache (buf);
+  i387_fxsave_to_cache (regcache, buf);
 }
 
 #endif
@@ -268,38 +282,38 @@ struct regset_info target_regsets[] =
 };
 
 static CORE_ADDR
-x86_get_pc (void)
+x86_get_pc (struct regcache *regcache)
 {
   int use_64bit = register_size (0) == 8;
 
   if (use_64bit)
     {
       unsigned long pc;
-      collect_register_by_name ("rip", &pc);
+      collect_register_by_name (regcache, "rip", &pc);
       return (CORE_ADDR) pc;
     }
   else
     {
       unsigned int pc;
-      collect_register_by_name ("eip", &pc);
+      collect_register_by_name (regcache, "eip", &pc);
       return (CORE_ADDR) pc;
     }
 }
 
 static void
-x86_set_pc (CORE_ADDR pc)
+x86_set_pc (struct regcache *regcache, CORE_ADDR pc)
 {
   int use_64bit = register_size (0) == 8;
 
   if (use_64bit)
     {
       unsigned long newpc = pc;
-      supply_register_by_name ("rip", &newpc);
+      supply_register_by_name (regcache, "rip", &newpc);
     }
   else
     {
       unsigned int newpc = pc;
-      supply_register_by_name ("eip", &newpc);
+      supply_register_by_name (regcache, "eip", &newpc);
     }
 }
 \f
@@ -311,13 +325,213 @@ x86_breakpoint_at (CORE_ADDR pc)
 {
   unsigned char c;
 
-  read_inferior_memory (pc, &c, 1);
+  (*the_target->read_memory) (pc, &c, 1);
   if (c == 0xCC)
     return 1;
 
   return 0;
 }
 \f
+/* Support for debug registers.  */
+
+static unsigned long
+x86_linux_dr_get (ptid_t ptid, int regnum)
+{
+  int tid;
+  unsigned long value;
+
+  tid = ptid_get_lwp (ptid);
+
+  errno = 0;
+  value = ptrace (PTRACE_PEEKUSER, tid,
+                 offsetof (struct user, u_debugreg[regnum]), 0);
+  if (errno != 0)
+    error ("Couldn't read debug register");
+
+  return value;
+}
+
+static void
+x86_linux_dr_set (ptid_t ptid, int regnum, unsigned long value)
+{
+  int tid;
+
+  tid = ptid_get_lwp (ptid);
+
+  errno = 0;
+  ptrace (PTRACE_POKEUSER, tid,
+         offsetof (struct user, u_debugreg[regnum]), value);
+  if (errno != 0)
+    error ("Couldn't write debug register");
+}
+
+/* Update the inferior's debug register REGNUM from STATE.  */
+
+void
+i386_dr_low_set_addr (const struct i386_debug_reg_state *state, int regnum)
+{
+  struct inferior_list_entry *lp;
+  CORE_ADDR addr;
+  /* Only need to update the threads of this process.  */
+  int pid = pid_of (get_thread_lwp (current_inferior));
+
+  if (! (regnum >= 0 && regnum <= DR_LASTADDR - DR_FIRSTADDR))
+    fatal ("Invalid debug register %d", regnum);
+
+  addr = state->dr_mirror[regnum];
+
+  for (lp = all_lwps.head; lp; lp = lp->next)
+    {
+      struct lwp_info *lwp = (struct lwp_info *) lp;
+
+      /* The actual update is done later, we just mark that the register
+        needs updating.  */
+      if (pid_of (lwp) == pid)
+       lwp->arch_private->debug_registers_changed = 1;
+    }
+}
+
+/* Update the inferior's DR7 debug control register from STATE.  */
+
+void
+i386_dr_low_set_control (const struct i386_debug_reg_state *state)
+{
+  struct inferior_list_entry *lp;
+  /* Only need to update the threads of this process.  */
+  int pid = pid_of (get_thread_lwp (current_inferior));
+
+  for (lp = all_lwps.head; lp; lp = lp->next)
+    {
+      struct lwp_info *lwp = (struct lwp_info *) lp;
+
+      /* The actual update is done later, we just mark that the register
+        needs updating.  */
+      if (pid_of (lwp) == pid)
+       lwp->arch_private->debug_registers_changed = 1;
+    }
+}
+
+/* Get the value of the DR6 debug status register from the inferior
+   and record it in STATE.  */
+
+void
+i386_dr_low_get_status (struct i386_debug_reg_state *state)
+{
+  struct lwp_info *lwp = get_thread_lwp (current_inferior);
+  ptid_t ptid = ptid_of (lwp);
+
+  state->dr_status_mirror = x86_linux_dr_get (ptid, DR_STATUS);
+}
+\f
+/* Watchpoint support.  */
+
+static int
+x86_insert_point (char type, CORE_ADDR addr, int len)
+{
+  struct process_info *proc = current_process ();
+  switch (type)
+    {
+    case '0':
+      return set_gdb_breakpoint_at (addr);
+    case '2':
+    case '3':
+    case '4':
+      return i386_low_insert_watchpoint (&proc->private->arch_private->debug_reg_state,
+                                        type, addr, len);
+    default:
+      /* Unsupported.  */
+      return 1;
+    }
+}
+
+static int
+x86_remove_point (char type, CORE_ADDR addr, int len)
+{
+  struct process_info *proc = current_process ();
+  switch (type)
+    {
+    case '0':
+      return delete_gdb_breakpoint_at (addr);
+    case '2':
+    case '3':
+    case '4':
+      return i386_low_remove_watchpoint (&proc->private->arch_private->debug_reg_state,
+                                        type, addr, len);
+    default:
+      /* Unsupported.  */
+      return 1;
+    }
+}
+
+static int
+x86_stopped_by_watchpoint (void)
+{
+  struct process_info *proc = current_process ();
+  return i386_low_stopped_by_watchpoint (&proc->private->arch_private->debug_reg_state);
+}
+
+static CORE_ADDR
+x86_stopped_data_address (void)
+{
+  struct process_info *proc = current_process ();
+  CORE_ADDR addr;
+  if (i386_low_stopped_data_address (&proc->private->arch_private->debug_reg_state,
+                                    &addr))
+    return addr;
+  return 0;
+}
+\f
+/* Called when a new process is created.  */
+
+static struct arch_process_info *
+x86_linux_new_process (void)
+{
+  struct arch_process_info *info = xcalloc (1, sizeof (*info));
+
+  i386_low_init_dregs (&info->debug_reg_state);
+
+  return info;
+}
+
+/* Called when a new thread is detected.  */
+
+static struct arch_lwp_info *
+x86_linux_new_thread (void)
+{
+  struct arch_lwp_info *info = xcalloc (1, sizeof (*info));
+
+  info->debug_registers_changed = 1;
+
+  return info;
+}
+
+/* Called when resuming a thread.
+   If the debug regs have changed, update the thread's copies.  */
+
+static void
+x86_linux_prepare_to_resume (struct lwp_info *lwp)
+{
+  ptid_t ptid = ptid_of (lwp);
+
+  if (lwp->arch_private->debug_registers_changed)
+    {
+      int i;
+      int pid = ptid_get_pid (ptid);
+      struct process_info *proc = find_process_pid (pid);
+      struct i386_debug_reg_state *state = &proc->private->arch_private->debug_reg_state;
+
+      for (i = DR_FIRSTADDR; i <= DR_LASTADDR; i++)
+       x86_linux_dr_set (ptid, i, state->dr_mirror[i]);
+
+      x86_linux_dr_set (ptid, DR_CONTROL, state->dr_control_mirror);
+
+      lwp->arch_private->debug_registers_changed = 0;
+    }
+
+  if (lwp->stopped_by_watchpoint)
+    x86_linux_dr_set (ptid, DR_STATUS, 0);
+}
+\f
 /* When GDBSERVER is built as a 64-bit application on linux, the
    PTRACE_GETSIGINFO data is always presented in 64-bit layout.  Since
    debugging a 32-bit inferior with a 64-bit GDBSERVER should look the same
@@ -551,7 +765,8 @@ x86_siginfo_fixup (struct siginfo *native, void *inf, int direction)
   /* Is the inferior 32-bit?  If so, then fixup the siginfo object.  */
   if (register_size (0) == 4)
     {
-      assert (sizeof (struct siginfo) == sizeof (compat_siginfo_t));
+      if (sizeof (struct siginfo) != sizeof (compat_siginfo_t))
+       fatal ("unexpected difference in siginfo");
 
       if (direction == 0)
        compat_siginfo_from_siginfo ((struct compat_siginfo *) inf, native);
@@ -565,7 +780,7 @@ x86_siginfo_fixup (struct siginfo *native, void *inf, int direction)
   return 0;
 }
 \f
-/* Return non-zero if the target is 64-bit.  */
+/* Initialize gdbserver for the architecture of the inferior.  */
 
 static void
 x86_arch_setup (void)
@@ -585,7 +800,7 @@ x86_arch_setup (void)
     }
   else if (use_64bit)
     {
-      init_registers_x86_64_linux ();
+      init_registers_amd64_linux ();
 
       /* Amd64 doesn't have HAVE_LINUX_USRREGS.  */
       the_low_target.num_regs = -1;
@@ -630,10 +845,10 @@ struct linux_target_ops the_low_target =
   NULL,
   1,
   x86_breakpoint_at,
-  NULL,
-  NULL,
-  NULL,
-  NULL,
+  x86_insert_point,
+  x86_remove_point,
+  x86_stopped_by_watchpoint,
+  x86_stopped_data_address,
   /* collect_ptrace_register/supply_ptrace_register are not needed in the
      native i386 case (no registers smaller than an xfer unit), and are not
      used in the biarch case (HAVE_LINUX_USRREGS is not defined).  */
@@ -641,4 +856,7 @@ struct linux_target_ops the_low_target =
   NULL,
   /* need to fix up i386 siginfo if host is amd64 */
   x86_siginfo_fixup,
+  x86_linux_new_process,
+  x86_linux_new_thread,
+  x86_linux_prepare_to_resume
 };