[2/2] gdb: xtensa: support hardware breakpoints/watchpoints

Message ID 1457560106-5830-3-git-send-email-jcmvbkbc@gmail.com
State New, archived
Headers

Commit Message

Max Filippov March 9, 2016, 9:48 p.m. UTC
  2016-03-10  Max Filippov  <jcmvbkbc@gmail.com>
gdb/
	* xtensa-linux-nat.c (PTRACE_GETHBPREGS, PTRACE_SETHBPREGS,
	XTENSA_MAX_WP_LENGTH, MAX_BPTS, MAX_WPTS, DBREAKC_LOAD_MASK,
	DBREAKC_STOR_MASK): New definitions.
	(xtensa_linux_hwbp_cap, xtensa_linux_hw_point,
	xtensa_linux_process_info, arch_lwp_info,
	update_registers_data): New structures.
	(xtensa_linux_process_list): New static variable.
	(xtensa_linux_find_process_pid, xtensa_linux_add_process,
	xtensa_linux_process_info_get, xtensa_linux_forget_process,
	xtensa_linux_get_hwbp_cap, xtensa_linux_get_hw_breakpoint_count,
	xtensa_linux_get_hw_watchpoint_count,
	xtensa_linux_can_use_hw_breakpoint,
	xtensa_linux_hw_breakpoint_initialize,
	xtensa_linux_hw_watchpoint_initialize,
	update_registers_callback,
	xtensa_linux_insert_process_hw_point,
	xtensa_linux_remove_process_hw_point,
	xtensa_linux_insert_hw_breakpoint,
	xtensa_linux_remove_hw_breakpoint,
	xtensa_linux_region_ok_for_hw_watchpoint,
	xtensa_linux_insert_watchpoint,	xtensa_linux_remove_watchpoint,
	xtensa_linux_stopped_data_address,
       	xtensa_linux_stopped_by_watchpoint,
	xtensa_linux_watchpoint_addr_within_range,
	xtensa_linux_new_thread, xtensa_linux_set_hw_point,
	xtensa_linux_prepare_to_resume, xtensa_linux_new_fork): New
	functions.
	(_initialize_xtensa_linux_nat): Register thr following target
	callbacks: to_can_use_hw_breakpoint, to_insert_hw_breakpoint,
	to_remove_hw_breakpoint, to_region_ok_for_hw_watchpoint,
	to_insert_watchpoint, to_remove_watchpoint,
	to_stopped_by_watchpoint, to_stopped_data_address,
	to_watchpoint_addr_within_range. Register handlers for thread
	and process creation and exit.
---
 gdb/xtensa-linux-nat.c | 601 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 601 insertions(+)
  

Patch

diff --git a/gdb/xtensa-linux-nat.c b/gdb/xtensa-linux-nat.c
index 4247f70..1e9a2ca 100644
--- a/gdb/xtensa-linux-nat.c
+++ b/gdb/xtensa-linux-nat.c
@@ -290,6 +290,586 @@  xtensa_linux_store_inferior_registers (struct target_ops *ops,
     store_xtregs (regcache, regnum);
 }
 
+#ifndef PTRACE_GETHBPREGS
+#define PTRACE_GETHBPREGS   20
+#endif
+#ifndef PTRACE_SETHBPREGS
+#define PTRACE_SETHBPREGS   21
+#endif
+
+#define XTENSA_MAX_WP_LENGTH 64
+#define MAX_BPTS 2
+#define MAX_WPTS 2
+
+#define DBREAKC_LOAD_MASK 0x40000000
+#define DBREAKC_STOR_MASK 0x80000000
+
+/* Information describing the hardware breakpoint capabilities.  */
+struct xtensa_linux_hwbp_cap
+{
+  gdb_byte wp_count;
+  gdb_byte bp_count;
+};
+
+struct xtensa_linux_hw_point
+{
+  uint32_t addr;
+  uint32_t type;
+};
+
+/* Per-process arch-specific data we want to keep.  */
+struct xtensa_linux_process_info
+{
+  /* Linked list.  */
+  struct xtensa_linux_process_info *next;
+  /* The process identifier.  */
+  pid_t pid;
+  /* Hardware breakpoints state information.  */
+  struct xtensa_linux_hw_point bpt[MAX_BPTS];
+  /* Hardware watchpoints state information.  */
+  struct xtensa_linux_hw_point wpt[MAX_WPTS];
+};
+
+/* 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.  */
+  char bpts_changed[MAX_BPTS];
+  char wpts_changed[MAX_WPTS];
+};
+
+static struct xtensa_linux_process_info *xtensa_linux_process_list = NULL;
+
+/* Find process data for process PID.  */
+
+static struct xtensa_linux_process_info *
+xtensa_linux_find_process_pid (pid_t pid)
+{
+  struct xtensa_linux_process_info *proc;
+
+  for (proc = xtensa_linux_process_list; proc; proc = proc->next)
+    if (proc->pid == pid)
+      return proc;
+
+  return NULL;
+}
+
+/* Add process data for process PID.  Returns newly allocated info
+   object.  */
+
+static struct xtensa_linux_process_info *
+xtensa_linux_add_process (pid_t pid)
+{
+  struct xtensa_linux_process_info *proc;
+
+  proc = xcalloc (1, sizeof (*proc));
+  proc->pid = pid;
+
+  proc->next = xtensa_linux_process_list;
+  xtensa_linux_process_list = proc;
+
+  return proc;
+}
+
+/* Get data specific info for process PID, creating it if necessary.
+   Never returns NULL.  */
+
+static struct xtensa_linux_process_info *
+xtensa_linux_process_info_get (pid_t pid)
+{
+  struct xtensa_linux_process_info *proc;
+
+  proc = xtensa_linux_find_process_pid (pid);
+  if (proc == NULL)
+    proc = xtensa_linux_add_process (pid);
+
+  return proc;
+}
+
+/* Called whenever GDB is no longer debugging process PID.  It deletes
+   data structures that keep track of debug register state.  */
+
+static void
+xtensa_linux_forget_process (pid_t pid)
+{
+  struct xtensa_linux_process_info *proc, **proc_link;
+
+  proc = xtensa_linux_process_list;
+  proc_link = &xtensa_linux_process_list;
+
+  while (proc != NULL)
+    {
+      if (proc->pid == pid)
+	{
+	  *proc_link = proc->next;
+
+	  xfree (proc);
+	  return;
+	}
+
+      proc_link = &proc->next;
+      proc = *proc_link;
+    }
+}
+
+/* Get hold of the Hardware Breakpoint information for the target we are
+   attached to.  Returns NULL if the kernel doesn't support Hardware
+   breakpoints at all, or a pointer to the information structure.  */
+static const struct xtensa_linux_hwbp_cap *
+xtensa_linux_get_hwbp_cap (void)
+{
+  /* The info structure we return.  */
+  static struct xtensa_linux_hwbp_cap info;
+
+  /* Is INFO in a good state?  0 means that no attempt has been made to
+     initialize INFO; 1 means INFO is in an initialized state.  */
+  static int available;
+
+  if (!available)
+    {
+      int i;
+      int tid;
+      uint32_t v[2];
+
+      tid = GET_THREAD_ID (inferior_ptid);
+      for (i = 0; i < MAX_WPTS; ++i)
+	{
+	  if (ptrace (PTRACE_GETHBPREGS, tid, i << 1 | 1, v) < 0)
+	    break;
+	}
+      info.wp_count = i;
+
+      for (i = 0; i < MAX_BPTS; ++i)
+	{
+	  if (ptrace (PTRACE_GETHBPREGS, tid, i << 1, v) < 0)
+	    break;
+	}
+      info.bp_count = i;
+
+      available = 1;
+    }
+
+  return &info;
+}
+
+static int
+xtensa_linux_get_hw_breakpoint_count (void)
+{
+  const struct xtensa_linux_hwbp_cap *cap = xtensa_linux_get_hwbp_cap ();
+  return cap != NULL ? cap->bp_count : 0;
+}
+
+static int
+xtensa_linux_get_hw_watchpoint_count (void)
+{
+  const struct xtensa_linux_hwbp_cap *cap = xtensa_linux_get_hwbp_cap ();
+  return cap != NULL ? cap->wp_count : 0;
+}
+
+/* Have we got a free break-/watch-point available for use?  Returns -1 if
+   there is not an appropriate resource available, otherwise returns 1.  */
+static int
+xtensa_linux_can_use_hw_breakpoint (struct target_ops *self, int type,
+				    int cnt, int ot)
+{
+  if (type == bp_hardware_watchpoint || type == bp_read_watchpoint
+      || type == bp_access_watchpoint || type == bp_watchpoint)
+    {
+      int count = xtensa_linux_get_hw_watchpoint_count ();
+
+      if (count == 0)
+	return 0;
+      else if (cnt > count)
+	return -1;
+    }
+  else if (type == bp_hardware_breakpoint)
+    {
+      int count = xtensa_linux_get_hw_breakpoint_count ();
+
+      if (count == 0)
+	return 0;
+      else if (cnt > count)
+	return -1;
+    }
+  else
+    gdb_assert (FALSE);
+
+  return 1;
+}
+
+/* Initialise the hardware breakpoint structure P.  The breakpoint will be
+   enabled, and will point to the placed address of BP_TGT.  */
+static void
+xtensa_linux_hw_point_initialize (struct gdbarch *gdbarch,
+				  struct bp_target_info *bp_tgt,
+				  struct xtensa_linux_hw_point *p)
+{
+  CORE_ADDR address = bp_tgt->placed_address = bp_tgt->reqstd_address;
+
+  p->addr = (unsigned int) address;
+  p->type = 1;
+}
+
+/* Initialize the hardware breakpoint structure P for a watchpoint at ADDR
+   to LEN.  The type of watchpoint is given in RW.  */
+static void
+xtensa_linux_hw_watchpoint_initialize (CORE_ADDR addr, int len, int rw,
+				       struct xtensa_linux_hw_point *p)
+{
+  p->addr = (unsigned int) addr;
+  p->type = len;
+  if (rw == hw_read)
+    p->type |= DBREAKC_LOAD_MASK;
+  else if (rw == hw_write)
+    p->type |= DBREAKC_STOR_MASK;
+  else
+    p->type |= DBREAKC_LOAD_MASK | DBREAKC_STOR_MASK;
+}
+
+/* Callback to mark a watch-/breakpoint to be updated in all threads of
+   the current process.  */
+
+struct update_registers_data
+{
+  int watch;
+  int index;
+};
+
+static int
+update_registers_callback (struct lwp_info *lwp, void *arg)
+{
+  struct update_registers_data *data = (struct update_registers_data *) arg;
+
+  if (lwp->arch_private == NULL)
+    lwp->arch_private = XCNEW (struct arch_lwp_info);
+
+  /* The actual update is done later just before resuming the lwp,
+     we just mark that the registers need updating.  */
+  if (data->watch)
+    lwp->arch_private->wpts_changed[data->index] = 1;
+  else
+    lwp->arch_private->bpts_changed[data->index] = 1;
+
+  /* If the lwp isn't stopped, force it to momentarily pause, so
+     we can update its breakpoint registers.  */
+  if (!lwp->stopped)
+    linux_stop_lwp (lwp);
+
+  return 0;
+}
+
+/* Insert the hardware breakpoint (WATCHPOINT = 0) or watchpoint (WATCHPOINT
+   =1) BPT for the process.  */
+static int
+xtensa_linux_insert_process_hw_point (const struct xtensa_linux_hw_point *bpt,
+				      int watchpoint)
+{
+  int pid;
+  ptid_t pid_ptid;
+  struct xtensa_linux_hw_point *pt;
+  int max_slot_count;
+  int slot;
+
+  pid = ptid_get_pid (inferior_ptid);
+  pid_ptid = pid_to_ptid (pid);
+
+  if (watchpoint)
+    {
+      max_slot_count = xtensa_linux_get_hw_watchpoint_count();
+      pt = xtensa_linux_process_info_get (pid)->wpt;
+    }
+  else
+    {
+      max_slot_count = xtensa_linux_get_hw_breakpoint_count();
+      pt = xtensa_linux_process_info_get (pid)->bpt;
+    }
+
+  for (slot = 0; slot < max_slot_count; ++slot)
+    if (pt[slot].type == 0)
+      {
+	struct update_registers_data data =
+	  {
+	    .watch = watchpoint,
+	    .index = slot,
+	  };
+	pt[slot] = *bpt;
+	iterate_over_lwps (pid_ptid, update_registers_callback, &data);
+	return 0;
+      }
+  return -1;
+}
+
+/* Remove the hardware breakpoint (WATCHPOINT = 0) or watchpoint
+   (WATCHPOINT = 1) BPT for the process.  */
+static int
+xtensa_linux_remove_process_hw_point (const struct xtensa_linux_hw_point *bpt,
+				      int watchpoint)
+{
+  int pid;
+  ptid_t pid_ptid;
+  struct xtensa_linux_hw_point *pt;
+  int max_slot_count;
+  int slot;
+
+  pid = ptid_get_pid (inferior_ptid);
+  pid_ptid = pid_to_ptid (pid);
+
+  if (watchpoint)
+    {
+      max_slot_count = xtensa_linux_get_hw_watchpoint_count();
+      pt = xtensa_linux_process_info_get (pid)->wpt;
+    }
+  else
+    {
+      max_slot_count = xtensa_linux_get_hw_breakpoint_count();
+      pt = xtensa_linux_process_info_get (pid)->bpt;
+    }
+
+  for (slot = 0; slot < max_slot_count; ++slot)
+    if (pt[slot].addr == bpt->addr &&
+	pt[slot].type == bpt->type)
+      {
+	struct update_registers_data data =
+	  {
+	    .watch = watchpoint,
+	    .index = slot,
+	  };
+	pt[slot].type = 0;
+	iterate_over_lwps (pid_ptid, update_registers_callback, &data);
+	return 0;
+      }
+  return -1;
+}
+
+/* Insert a Hardware breakpoint.  */
+static int
+xtensa_linux_insert_hw_breakpoint (struct target_ops *self,
+				   struct gdbarch *gdbarch,
+				   struct bp_target_info *bp_tgt)
+{
+  struct xtensa_linux_hw_point p;
+
+  if (xtensa_linux_get_hw_breakpoint_count () == 0)
+    return -1;
+
+  xtensa_linux_hw_point_initialize (gdbarch, bp_tgt, &p);
+  return xtensa_linux_insert_process_hw_point (&p, 0);
+}
+
+/* Remove a hardware breakpoint.  */
+static int
+xtensa_linux_remove_hw_breakpoint (struct target_ops *self,
+				   struct gdbarch *gdbarch,
+				   struct bp_target_info *bp_tgt)
+{
+  struct xtensa_linux_hw_point p;
+
+  if (xtensa_linux_get_hw_breakpoint_count () == 0)
+    return -1;
+
+  xtensa_linux_hw_point_initialize (gdbarch, bp_tgt, &p);
+  return xtensa_linux_remove_process_hw_point (&p, 0);
+}
+
+/* Are we able to use a hardware watchpoint for the LEN bytes starting at
+   ADDR?  */
+static int
+xtensa_linux_region_ok_for_hw_watchpoint (struct target_ops *self,
+					  CORE_ADDR addr, int len)
+{
+  CORE_ADDR aligned_addr;
+
+  /* Can not set watchpoints for zero or negative lengths.  */
+  if (len <= 0)
+    return 0;
+
+  /* Test that the range [ADDR, ADDR + LEN) fits into the largest address
+     range covered by a watchpoint.  */
+  aligned_addr = addr & ~(XTENSA_MAX_WP_LENGTH - 1);
+
+  if (aligned_addr + XTENSA_MAX_WP_LENGTH < addr + len)
+    return 0;
+
+  /* The current ptrace interface can only handle watchpoint lengths that
+     are a power of 2.  */
+  if ((len & (len - 1)) != 0)
+    return 0;
+
+  /* All tests passed so we must be able to set a watchpoint.  */
+  return 1;
+}
+
+/* Insert a Hardware watchpoint.  */
+static int
+xtensa_linux_insert_watchpoint (struct target_ops *self,
+				CORE_ADDR addr, int len, int rw,
+				struct expression *cond)
+{
+  struct xtensa_linux_hw_point p;
+  int slot;
+
+  if (xtensa_linux_get_hw_watchpoint_count () == 0)
+    return -1;
+
+  xtensa_linux_hw_watchpoint_initialize (addr, len, rw, &p);
+  return xtensa_linux_insert_process_hw_point (&p, 1);
+}
+
+/* Remove a hardware watchpoint.  */
+static int
+xtensa_linux_remove_watchpoint (struct target_ops *self,
+				CORE_ADDR addr, int len, int rw,
+				struct expression *cond)
+{
+  struct xtensa_linux_hw_point p;
+  int slot;
+
+  if (xtensa_linux_get_hw_watchpoint_count () == 0)
+    return -1;
+
+  xtensa_linux_hw_watchpoint_initialize (addr, len, rw, &p);
+  return xtensa_linux_remove_process_hw_point (&p, 1);
+}
+
+/* What was the data address the target was stopped on accessing.  */
+static int
+xtensa_linux_stopped_data_address (struct target_ops *target,
+				   CORE_ADDR *addr_p)
+{
+  siginfo_t siginfo;
+  int slot;
+
+  if (!linux_nat_get_siginfo (inferior_ptid, &siginfo))
+    return 0;
+
+  /* This must be a hardware breakpoint.  */
+  if (siginfo.si_signo != SIGTRAP
+      || (siginfo.si_code & 0xffff) != 0x0004 /* TRAP_HWBKPT */)
+    return 0;
+
+  /* We must be able to set hardware watchpoints.  */
+  if (xtensa_linux_get_hw_watchpoint_count () == 0)
+    return 0;
+
+  slot = siginfo.si_errno;
+
+  /* If we are in an even slot then we're looking at a breakpoint and not
+     a watchpoint.  */
+  if ((slot & 0x1) == 0)
+    return 0;
+
+  *addr_p = (CORE_ADDR) (uintptr_t) siginfo.si_addr;
+  return 1;
+}
+
+/* Has the target been stopped by hitting a watchpoint?  */
+static int
+xtensa_linux_stopped_by_watchpoint (struct target_ops *ops)
+{
+  CORE_ADDR addr;
+  return xtensa_linux_stopped_data_address (ops, &addr);
+}
+
+static int
+xtensa_linux_watchpoint_addr_within_range (struct target_ops *target,
+					   CORE_ADDR addr,
+					   CORE_ADDR start, int length)
+{
+  return start <= addr && start + length - 1 >= addr;
+}
+
+/* Handle thread creation.  We need to copy the breakpoints and watchpoints
+   in the parent thread to the child thread.  */
+static void
+xtensa_linux_new_thread (struct lwp_info *lp)
+{
+  int i;
+  struct arch_lwp_info *info = XCNEW (struct arch_lwp_info);
+
+  /* Mark that all the hardware breakpoints/watchpoints
+     for this thread need to be initialized.  */
+
+  for (i = 0; i < MAX_BPTS; i++)
+    info->bpts_changed[i] = 1;
+  for (i = 0; i < MAX_WPTS; i++)
+    info->wpts_changed[i] = 1;
+
+  lp->arch_private = info;
+}
+
+static long
+xtensa_linux_set_hw_point (const struct xtensa_linux_hw_point *bpt,
+			   int pid, int slot, int watchpoint)
+{
+  uint32_t bp[2] = { bpt->addr, bpt->type };
+
+  return ptrace (PTRACE_SETHBPREGS, pid, slot << 1 | watchpoint, bp);
+}
+
+/* Called when resuming a thread.
+   The hardware debug registers are updated when there is any change.  */
+static void
+xtensa_linux_prepare_to_resume (struct lwp_info *lwp)
+{
+  int pid, i;
+  struct xtensa_linux_hw_point *bpts, *wpts;
+  struct arch_lwp_info *lwp_info = lwp->arch_private;
+
+  pid = ptid_get_lwp (lwp->ptid);
+  bpts = xtensa_linux_process_info_get (ptid_get_pid (lwp->ptid))->bpt;
+  wpts = xtensa_linux_process_info_get (ptid_get_pid (lwp->ptid))->wpt;
+
+  if (lwp_info == NULL)
+    return;
+
+  for (i = 0; i < xtensa_linux_get_hw_breakpoint_count (); i++)
+    if (lwp_info->bpts_changed[i])
+      {
+	errno = 0;
+
+	if (xtensa_linux_set_hw_point (bpts + i, pid, i, 0) < 0)
+	  perror_with_name (_("Unexpected error setting breakpoint"));
+
+	lwp_info->bpts_changed[i] = 0;
+      }
+
+  for (i = 0; i < xtensa_linux_get_hw_watchpoint_count (); i++)
+    if (lwp_info->wpts_changed[i])
+      {
+	errno = 0;
+
+	if (xtensa_linux_set_hw_point (wpts + i, pid, i, 1) < 0)
+	  perror_with_name (_("Unexpected error setting watchpoint"));
+
+	lwp_info->wpts_changed[i] = 0;
+      }
+}
+
+static void
+xtensa_linux_new_fork (struct lwp_info *parent, pid_t child_pid)
+{
+  pid_t parent_pid;
+  struct xtensa_linux_process_info *parent_state;
+  struct xtensa_linux_process_info *child_state;
+
+  /* NULL means no watchpoint has ever been set in the parent.  In
+     that case, there's nothing to do.  */
+  if (parent->arch_private == NULL)
+    return;
+
+  /* GDB core assumes the child inherits the watchpoints/hw
+     breakpoints of the parent, and will remove them all from the
+     forked off process.  Copy the debug registers mirrors into the
+     new process so that all breakpoints and watchpoints can be
+     removed together.  */
+
+  parent_pid = ptid_get_pid (parent->ptid);
+  parent_state = xtensa_linux_process_info_get (parent_pid);
+  child_state = xtensa_linux_process_info_get (child_pid);
+  memcpy (child_state->bpt, parent_state->bpt, sizeof (parent_state->bpt));
+  memcpy (child_state->wpt, parent_state->wpt, sizeof (parent_state->wpt));
+}
+
 void _initialize_xtensa_linux_nat (void);
 
 void
@@ -316,5 +896,26 @@  _initialize_xtensa_linux_nat (void)
   t->to_fetch_registers = xtensa_linux_fetch_inferior_registers;
   t->to_store_registers = xtensa_linux_store_inferior_registers;
 
+  /* Add our hardware breakpoint and watchpoint implementation.  */
+  t->to_can_use_hw_breakpoint = xtensa_linux_can_use_hw_breakpoint;
+  t->to_insert_hw_breakpoint = xtensa_linux_insert_hw_breakpoint;
+  t->to_remove_hw_breakpoint = xtensa_linux_remove_hw_breakpoint;
+
+  t->to_region_ok_for_hw_watchpoint = xtensa_linux_region_ok_for_hw_watchpoint;
+  t->to_insert_watchpoint = xtensa_linux_insert_watchpoint;
+  t->to_remove_watchpoint = xtensa_linux_remove_watchpoint;
+  t->to_stopped_by_watchpoint = xtensa_linux_stopped_by_watchpoint;
+  t->to_stopped_data_address = xtensa_linux_stopped_data_address;
+  t->to_watchpoint_addr_within_range =
+    xtensa_linux_watchpoint_addr_within_range;
+
   linux_nat_add_target (t);
+
+  /* Handle thread creation and exit.  */
+  linux_nat_set_new_thread (t, xtensa_linux_new_thread);
+  linux_nat_set_prepare_to_resume (t, xtensa_linux_prepare_to_resume);
+
+  /* Handle process creation and exit.  */
+  linux_nat_set_new_fork (t, xtensa_linux_new_fork);
+  linux_nat_set_forget_process (t, xtensa_linux_forget_process);
 }