[aarch64] Check region OK for HW watchpoint in GDBserver
[binutils-gdb.git] / gdb / nat / aarch64-linux-hw-point.c
index f58e0c5ff36f5ab17e0193e2b58f3e1634d8f655..bca6ec1ce7e7bdfe2818162dcd63d22140bec56d 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "common-defs.h"
 #include "break-common.h"
+#include "nat/linux-nat.h"
 #include "aarch64-linux-hw-point.h"
 
 #include <sys/uio.h>
@@ -220,6 +221,93 @@ aarch64_align_watchpoint (CORE_ADDR addr, int len, CORE_ADDR *aligned_addr_p,
     *next_len_p = len;
 }
 
+struct aarch64_dr_update_callback_param
+{
+  int is_watchpoint;
+  unsigned int idx;
+};
+
+/* Callback for iterate_over_lwps.  Records the
+   information about the change of one hardware breakpoint/watchpoint
+   setting for the thread LWP.
+   The information is passed in via PTR.
+   N.B.  The actual updating of hardware debug registers is not
+   carried out until the moment the thread is resumed.  */
+
+static int
+debug_reg_change_callback (struct lwp_info *lwp, void *ptr)
+{
+  struct aarch64_dr_update_callback_param *param_p
+    = (struct aarch64_dr_update_callback_param *) ptr;
+  int tid = ptid_get_lwp (ptid_of_lwp (lwp));
+  int idx = param_p->idx;
+  int is_watchpoint = param_p->is_watchpoint;
+  struct arch_lwp_info *info = lwp_arch_private_info (lwp);
+  dr_changed_t *dr_changed_ptr;
+  dr_changed_t dr_changed;
+
+  if (info == NULL)
+    {
+      info = XCNEW (struct arch_lwp_info);
+      lwp_set_arch_private_info (lwp, info);
+    }
+
+  if (show_debug_regs)
+    {
+      debug_printf ("debug_reg_change_callback: \n\tOn entry:\n");
+      debug_printf ("\ttid%d, dr_changed_bp=0x%s, "
+                   "dr_changed_wp=0x%s\n", tid,
+                   phex (info->dr_changed_bp, 8),
+                   phex (info->dr_changed_wp, 8));
+    }
+
+  dr_changed_ptr = is_watchpoint ? &info->dr_changed_wp
+    : &info->dr_changed_bp;
+  dr_changed = *dr_changed_ptr;
+
+  gdb_assert (idx >= 0
+             && (idx <= (is_watchpoint ? aarch64_num_wp_regs
+                         : aarch64_num_bp_regs)));
+
+  /* The actual update is done later just before resuming the lwp,
+     we just mark that one register pair needs updating.  */
+  DR_MARK_N_CHANGED (dr_changed, idx);
+  *dr_changed_ptr = dr_changed;
+
+  /* If the lwp isn't stopped, force it to momentarily pause, so
+     we can update its debug registers.  */
+  if (!lwp_is_stopped (lwp))
+    linux_stop_lwp (lwp);
+
+  if (show_debug_regs)
+    {
+      debug_printf ("\tOn exit:\n\ttid%d, dr_changed_bp=0x%s, "
+                   "dr_changed_wp=0x%s\n", tid,
+                   phex (info->dr_changed_bp, 8),
+                   phex (info->dr_changed_wp, 8));
+    }
+
+  return 0;
+}
+
+/* Notify each thread that their IDXth breakpoint/watchpoint register
+   pair needs to be updated.  The message will be recorded in each
+   thread's arch-specific data area, the actual updating will be done
+   when the thread is resumed.  */
+
+static void
+aarch64_notify_debug_reg_change (const struct aarch64_debug_reg_state *state,
+                                int is_watchpoint, unsigned int idx)
+{
+  struct aarch64_dr_update_callback_param param;
+  ptid_t pid_ptid = pid_to_ptid (ptid_get_pid (current_lwp_ptid ()));
+
+  param.is_watchpoint = is_watchpoint;
+  param.idx = idx;
+
+  iterate_over_lwps (pid_ptid, debug_reg_change_callback, (void *) &param);
+}
+
 /* Record the insertion of one breakpoint/watchpoint, as represented
    by ADDR and CTRL, in the process' arch-specific data area *STATE.  */
 
@@ -504,3 +592,96 @@ aarch64_show_debug_reg_state (struct aarch64_debug_reg_state *state,
                  i, core_addr_to_string_nz (state->dr_addr_wp[i]),
                  state->dr_ctrl_wp[i], state->dr_ref_count_wp[i]);
 }
+
+/* Get the hardware debug register capacity information from the
+   process represented by TID.  */
+
+void
+aarch64_linux_get_debug_reg_capacity (int tid)
+{
+  struct iovec iov;
+  struct user_hwdebug_state dreg_state;
+
+  iov.iov_base = &dreg_state;
+  iov.iov_len = sizeof (dreg_state);
+
+  /* Get hardware watchpoint register info.  */
+  if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_HW_WATCH, &iov) == 0
+      && AARCH64_DEBUG_ARCH (dreg_state.dbg_info) == AARCH64_DEBUG_ARCH_V8)
+    {
+      aarch64_num_wp_regs = AARCH64_DEBUG_NUM_SLOTS (dreg_state.dbg_info);
+      if (aarch64_num_wp_regs > AARCH64_HWP_MAX_NUM)
+       {
+         warning (_("Unexpected number of hardware watchpoint registers"
+                    " reported by ptrace, got %d, expected %d."),
+                  aarch64_num_wp_regs, AARCH64_HWP_MAX_NUM);
+         aarch64_num_wp_regs = AARCH64_HWP_MAX_NUM;
+       }
+    }
+  else
+    {
+      warning (_("Unable to determine the number of hardware watchpoints"
+                " available."));
+      aarch64_num_wp_regs = 0;
+    }
+
+  /* Get hardware breakpoint register info.  */
+  if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_HW_BREAK, &iov) == 0
+      && AARCH64_DEBUG_ARCH (dreg_state.dbg_info) == AARCH64_DEBUG_ARCH_V8)
+    {
+      aarch64_num_bp_regs = AARCH64_DEBUG_NUM_SLOTS (dreg_state.dbg_info);
+      if (aarch64_num_bp_regs > AARCH64_HBP_MAX_NUM)
+       {
+         warning (_("Unexpected number of hardware breakpoint registers"
+                    " reported by ptrace, got %d, expected %d."),
+                  aarch64_num_bp_regs, AARCH64_HBP_MAX_NUM);
+         aarch64_num_bp_regs = AARCH64_HBP_MAX_NUM;
+       }
+    }
+  else
+    {
+      warning (_("Unable to determine the number of hardware breakpoints"
+                " available."));
+      aarch64_num_bp_regs = 0;
+    }
+}
+
+/* Return true if we can watch a memory region that starts address
+   ADDR and whose length is LEN in bytes.  */
+
+int
+aarch64_linux_region_ok_for_watchpoint (CORE_ADDR addr, int len)
+{
+  CORE_ADDR aligned_addr;
+
+  /* Can not set watchpoints for zero or negative lengths.  */
+  if (len <= 0)
+    return 0;
+
+  /* Must have hardware watchpoint debug register(s).  */
+  if (aarch64_num_wp_regs == 0)
+    return 0;
+
+  /* We support unaligned watchpoint address and arbitrary length,
+     as long as the size of the whole watched area after alignment
+     doesn't exceed size of the total area that all watchpoint debug
+     registers can watch cooperatively.
+
+     This is a very relaxed rule, but unfortunately there are
+     limitations, e.g. false-positive hits, due to limited support of
+     hardware debug registers in the kernel.  See comment above
+     aarch64_align_watchpoint for more information.  */
+
+  aligned_addr = addr & ~(AARCH64_HWP_MAX_LEN_PER_REG - 1);
+  if (aligned_addr + aarch64_num_wp_regs * AARCH64_HWP_MAX_LEN_PER_REG
+      < addr + len)
+    return 0;
+
+  /* All tests passed so we are likely to be able to set the watchpoint.
+     The reason that it is 'likely' rather than 'must' is because
+     we don't check the current usage of the watchpoint registers, and
+     there may not be enough registers available for this watchpoint.
+     Ideally we should check the cached debug register state, however
+     the checking is costly.  */
+  return 1;
+}