]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: LoongArch: Add support for hardware breakpoint
authorHui Li <lihui@loongson.cn>
Tue, 11 Jun 2024 11:21:26 +0000 (19:21 +0800)
committerTiezhu Yang <yangtiezhu@loongson.cn>
Mon, 24 Jun 2024 21:50:29 +0000 (05:50 +0800)
LoongArch defines hardware watchpoint functions for fetch operations.
After the software configures the watchpoints for fetch, the processor
hardware will monitor the access addresses of the fetch operations and
trigger a watchpoint exception when the watchpoint setting conditions
are met.

Hardware watchpoints for fetch operations is used to implement hardware
breakpoint function on LoongArch. Refer to the following document for
hardware breakpoint.
https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#control-and-status-registers-related-to-watchpoints

A simple test is as follows:

lihui@bogon:~$ cat test.c
  #include <stdio.h>
  int a = 0;
  int main()
  {
        printf("start test\n");
        a = 1;
        printf("a = %d\n", a);
        printf("end test\n");
        return 0;
  }
lihui@bogon:~$ gcc -g test.c -o test

without this patch:

lihui@bogon:~$ gdb test
...
(gdb) start
...
Temporary breakpoint 1, main () at test.c:5
5               printf("start test\n");
(gdb) hbreak 8
No hardware breakpoint support in the target.

with this patch:

lihui@bogon:~$ gdb test
...
(gdb) start
...

Temporary breakpoint 1, main () at test.c:5
5               printf("start test\n");
(gdb) hbreak 8
Hardware assisted breakpoint 2 at 0x1200006ec: file test.c, line 8.
(gdb) c
Continuing.
start test
a = 1

Breakpoint 2, main () at test.c:8
8               printf("end test\n");
(gdb) c
Continuing.
end test
[Inferior 1 (process 25378) exited normally]

Signed-off-by: Hui Li <lihui@loongson.cn>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
gdb/loongarch-linux-nat.c
gdb/nat/loongarch-hw-point.c
gdb/nat/loongarch-hw-point.h
gdb/nat/loongarch-linux-hw-point.c
gdb/nat/loongarch-linux-hw-point.h
gdb/nat/loongarch-linux.c
include/elf/common.h

index 9f5760066b350e4f0845dd4beed355b80f52e51d..873628775e29ab90cec78b2a100622421aa61d03 100644 (file)
@@ -54,6 +54,11 @@ public:
   bool stopped_by_watchpoint () override;
   bool stopped_data_address (CORE_ADDR *) override;
 
+  int insert_hw_breakpoint (struct gdbarch *gdbarch,
+                           struct bp_target_info *bp_tgt) override;
+  int remove_hw_breakpoint (struct gdbarch *gdbarch,
+                           struct bp_target_info *bp_tgt) override;
+
   /* Override the GNU/Linux inferior startup hook.  */
   void post_startup_inferior (ptid_t) override;
 
@@ -489,7 +494,8 @@ loongarch_linux_nat_target::can_use_hw_breakpoint (enum bptype type, int cnt,
     }
   else if (type == bp_hardware_breakpoint)
     {
-      return 0;
+      if (loongarch_num_bp_regs == 0)
+       return 0;
     }
   else
     gdb_assert_not_reached ("unexpected breakpoint type");
@@ -624,6 +630,72 @@ loongarch_linux_nat_target::stopped_by_watchpoint ()
   return stopped_data_address (&addr);
 }
 
+/* Insert a hardware-assisted breakpoint at BP_TGT->reqstd_address.
+   Return 0 on success, -1 on failure.  */
+
+int
+loongarch_linux_nat_target::insert_hw_breakpoint (struct gdbarch *gdbarch,
+                                                 struct bp_target_info *bp_tgt)
+{
+  int ret;
+  CORE_ADDR addr = bp_tgt->placed_address = bp_tgt->reqstd_address;
+  int len;
+  const enum target_hw_bp_type type = hw_execute;
+  struct loongarch_debug_reg_state *state
+       = loongarch_get_debug_reg_state (inferior_ptid.pid ());
+
+  gdbarch_breakpoint_from_pc (gdbarch, &addr, &len);
+
+  if (show_debug_regs)
+    gdb_printf (gdb_stdlog,
+               "insert_hw_breakpoint on entry (addr=0x%08lx, len=%d))\n",
+               (unsigned long) addr, len);
+
+  ret = loongarch_handle_breakpoint (type, addr, len, 1 /* is_insert */,
+                                    inferior_ptid, state);
+
+  if (show_debug_regs)
+    {
+      loongarch_show_debug_reg_state (state,
+                                     "insert_hw_breakpoint", addr, len, type);
+    }
+
+  return ret;
+}
+
+/* Remove a hardware-assisted breakpoint at BP_TGT->placed_address.
+   Return 0 on success, -1 on failure.  */
+
+int
+loongarch_linux_nat_target::remove_hw_breakpoint (struct gdbarch *gdbarch,
+                                                 struct bp_target_info *bp_tgt)
+{
+  int ret;
+  CORE_ADDR addr = bp_tgt->placed_address;
+  int len = 4;
+  const enum target_hw_bp_type type = hw_execute;
+  struct loongarch_debug_reg_state *state
+    = loongarch_get_debug_reg_state (inferior_ptid.pid ());
+
+  gdbarch_breakpoint_from_pc (gdbarch, &addr, &len);
+
+  if (show_debug_regs)
+    gdb_printf (gdb_stdlog,
+               "remove_hw_breakpoint on entry (addr=0x%08lx, len=%d))\n",
+               (unsigned long) addr, len);
+
+  ret = loongarch_handle_breakpoint (type, addr, len, 0 /* is_insert */,
+                                    inferior_ptid, state);
+
+  if (show_debug_regs)
+    {
+      loongarch_show_debug_reg_state (state,
+                                     "remove_hw_watchpoint", addr, len, type);
+    }
+
+  return ret;
+}
+
 /* Implement the virtual inf_ptrace_target::post_startup_inferior method.  */
 
 void
index 44af61abd00b908831a2860229a3947e6a058917..089f3bd49a9377582d1fbe54ea4e9ef7f6880674 100644 (file)
@@ -27,6 +27,7 @@
 /* Number of hardware breakpoints/watchpoints the target supports.
    They are initialized with values obtained via ptrace.  */
 
+int loongarch_num_bp_regs;
 int loongarch_num_wp_regs;
 
 /* Given the hardware breakpoint or watchpoint type TYPE and its
@@ -112,7 +113,10 @@ loongarch_dr_state_insert_one_point (ptid_t ptid,
     }
   else
     {
-      return -1;
+      num_regs = loongarch_num_bp_regs;
+      dr_addr_p = state->dr_addr_bp;
+      dr_ctrl_p = state->dr_ctrl_bp;
+      dr_ref_count = state->dr_ref_count_bp;
     }
 
   ctrl = loongarch_point_encode_ctrl_reg (type, len);
@@ -184,7 +188,10 @@ loongarch_dr_state_remove_one_point (ptid_t ptid,
     }
   else
     {
-      return -1;
+      num_regs = loongarch_num_bp_regs;
+      dr_addr_p = state->dr_addr_bp;
+      dr_ctrl_p = state->dr_ctrl_bp;
+      dr_ref_count = state->dr_ref_count_bp;
     }
 
   ctrl = loongarch_point_encode_ctrl_reg (type, len);
@@ -214,6 +221,20 @@ loongarch_dr_state_remove_one_point (ptid_t ptid,
   return 0;
 }
 
+int
+loongarch_handle_breakpoint (enum target_hw_bp_type type, CORE_ADDR addr,
+                          int len, int is_insert, ptid_t ptid,
+                          struct loongarch_debug_reg_state *state)
+{
+  if (is_insert)
+    return loongarch_dr_state_insert_one_point (ptid, state, type, addr,
+                                               len, -1);
+  else
+    return loongarch_dr_state_remove_one_point (ptid, state, type, addr,
+                                               len, -1);
+}
+
+
 int
 loongarch_handle_watchpoint (enum target_hw_bp_type type, CORE_ADDR addr,
                             int len, int is_insert, ptid_t ptid,
@@ -234,12 +255,12 @@ bool
 loongarch_any_set_debug_regs_state (loongarch_debug_reg_state *state,
                                    bool watchpoint)
 {
-  int count = watchpoint ? loongarch_num_wp_regs : 0;
+  int count = watchpoint ? loongarch_num_wp_regs : loongarch_num_bp_regs;
   if (count == 0)
     return false;
 
-  const CORE_ADDR *addr = watchpoint ? state->dr_addr_wp : 0;
-  const unsigned int *ctrl = watchpoint ? state->dr_ctrl_wp : 0;
+  const CORE_ADDR *addr = watchpoint ? state->dr_addr_wp : state->dr_addr_bp;
+  const unsigned int *ctrl = watchpoint ? state->dr_ctrl_wp : state->dr_ctrl_bp;
 
   for (int i = 0; i < count; i++)
     if (addr[i] != 0 || ctrl[i] != 0)
@@ -268,6 +289,12 @@ loongarch_show_debug_reg_state (struct loongarch_debug_reg_state *state,
                           : "??unknown??"))));
   debug_printf (":\n");
 
+  debug_printf ("\tBREAKPOINTs:\n");
+  for (i = 0; i < loongarch_num_bp_regs; i++)
+    debug_printf ("\tBP%d: addr=%s, ctrl=0x%08x, ref.count=%d\n",
+                 i, core_addr_to_string_nz (state->dr_addr_bp[i]),
+                 state->dr_ctrl_bp[i], state->dr_ref_count_bp[i]);
+
   debug_printf ("\tWATCHPOINTs:\n");
   for (i = 0; i < loongarch_num_wp_regs; i++)
     debug_printf ("\tWP%d: addr=%s, ctrl=0x%08x, ref.count=%d\n",
index 86e5aa3f4528340b4fd417dbec0d2b2be9c5fc56..fd79285a046fecae166ef0d85ba8eeb6459d9c83 100644 (file)
@@ -33,6 +33,7 @@
    Neither of these values may exceed the width of dr_changed_t
    measured in bits.  */
 
+#define LOONGARCH_HBP_MAX_NUM 8
 #define LOONGARCH_HWP_MAX_NUM 8
 
 
 
 struct loongarch_debug_reg_state
 {
+  /* hardware breakpoint */
+  CORE_ADDR dr_addr_bp[LOONGARCH_HBP_MAX_NUM];
+  unsigned int dr_ctrl_bp[LOONGARCH_HBP_MAX_NUM];
+  unsigned int dr_ref_count_bp[LOONGARCH_HBP_MAX_NUM];
+
   /* hardware watchpoint */
   CORE_ADDR dr_addr_wp[LOONGARCH_HWP_MAX_NUM];
   unsigned int dr_ctrl_wp[LOONGARCH_HWP_MAX_NUM];
   unsigned int dr_ref_count_wp[LOONGARCH_HWP_MAX_NUM];
 };
 
+extern int loongarch_num_bp_regs;
 extern int loongarch_num_wp_regs;
 
 /* Invoked when IDXth breakpoint/watchpoint register pair needs to be
@@ -68,6 +75,10 @@ void loongarch_notify_debug_reg_change (ptid_t ptid, int is_watchpoint,
                                      unsigned int idx);
 
 
+int loongarch_handle_breakpoint (enum target_hw_bp_type type, CORE_ADDR addr,
+                                int len, int is_insert, ptid_t ptid,
+                                struct loongarch_debug_reg_state *state);
+
 int loongarch_handle_watchpoint (enum target_hw_bp_type type, CORE_ADDR addr,
                                 int len, int is_insert, ptid_t ptid,
                                 struct loongarch_debug_reg_state *state);
index cced5a388ab20ba8f9ee837d115409b487e55136..fbc80fdc2a1565c9ba8be38889a1605a73a0808f 100644 (file)
@@ -62,9 +62,6 @@ loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint,
   dr_changed_t *dr_changed_ptr;
   dr_changed_t dr_changed;
 
-  if (!is_watchpoint)
-    return -1;
-
   if (info == NULL)
     {
       info = XCNEW (struct arch_lwp_info);
@@ -74,14 +71,19 @@ loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint,
   if (show_debug_regs)
     {
       debug_printf ("loongarch_dr_change_callback: \n\tOn entry:\n");
-      debug_printf ("\ttid%d, dr_changed_wp=0x%s\n",
-                   tid, phex (info->dr_changed_wp, 8));
+      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 = &info->dr_changed_wp;
+  dr_changed_ptr = is_watchpoint ? &info->dr_changed_wp
+                  : &info->dr_changed_bp;
   dr_changed = *dr_changed_ptr;
 
-  gdb_assert (idx >= 0 && idx <= loongarch_num_wp_regs);
+  gdb_assert (idx >= 0
+             && (idx <= (is_watchpoint ? loongarch_num_wp_regs
+                         : loongarch_num_bp_regs)));
 
   /* The actual update is done later just before resuming the lwp,
      we just mark that one register pair needs updating.  */
@@ -95,8 +97,10 @@ loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint,
 
   if (show_debug_regs)
     {
-      debug_printf ("\tOn exit:\n\ttid%d, dr_changed_wp=0x%s\n",
-                   tid, phex (info->dr_changed_wp, 8));
+      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;
@@ -136,9 +140,9 @@ loongarch_linux_set_debug_regs (struct loongarch_debug_reg_state *state,
 
   memset (&regs, 0, sizeof (regs));
   iov.iov_base = &regs;
-  count = watchpoint ? loongarch_num_wp_regs : 0;
-  addr = watchpoint ? state->dr_addr_wp : 0;
-  ctrl = watchpoint ? state->dr_ctrl_wp : 0;
+  count = watchpoint ? loongarch_num_wp_regs : loongarch_num_bp_regs;
+  addr = watchpoint ? state->dr_addr_wp : state->dr_addr_bp;
+  ctrl = watchpoint ? state->dr_ctrl_wp : state->dr_ctrl_bp;
 
   if (count == 0)
     return;
@@ -151,7 +155,9 @@ loongarch_linux_set_debug_regs (struct loongarch_debug_reg_state *state,
       regs.dbg_regs[i].ctrl = ctrl[i];
     }
 
-  if (ptrace(PTRACE_SETREGSET, tid, NT_LOONGARCH_HW_WATCH, (void *) &iov))
+  if (ptrace(PTRACE_SETREGSET, tid,
+            watchpoint ? NT_LOONGARCH_HW_WATCH : NT_LOONGARCH_HW_BREAK,
+            (void *) &iov))
     {
       if (errno == EINVAL)
        error (_("Invalid argument setting hardware debug registers"));
@@ -194,6 +200,25 @@ loongarch_linux_get_debug_reg_capacity (int tid)
       loongarch_num_wp_regs = 0;
     }
 
+  /* Get hardware breakpoint register info.  */
+  result = ptrace (PTRACE_GETREGSET, tid, NT_LOONGARCH_HW_BREAK, &iov);
+  if ( result == 0)
+    {
+      loongarch_num_bp_regs = LOONGARCH_DEBUG_NUM_SLOTS (dreg_state.dbg_info);
+      if (loongarch_num_bp_regs > LOONGARCH_HBP_MAX_NUM)
+       {
+         warning (_("Unexpected number of hardware breakpoint registers"
+                    " reported by ptrace, got %d, expected %d."),
+                  loongarch_num_bp_regs, LOONGARCH_HBP_MAX_NUM);
+         loongarch_num_bp_regs = LOONGARCH_HBP_MAX_NUM;
+       }
+    }
+  else
+    {
+      warning (_("Unable to determine the number of hardware breakpoints"
+                " available."));
+      loongarch_num_bp_regs = 0;
+    }
 }
 
 /* Return the debug register state for process PID.  If no existing
index 4086907e3c054738d11691a2382cd67ce5ba7bb3..8d96443f5e0a81bfd6a9c29384d16088b7c9b523 100644 (file)
@@ -93,6 +93,7 @@ struct arch_lwp_info
   /* When bit N is 1, it indicates the Nth hardware breakpoint or
      watchpoint register pair needs to be updated when the thread is
      resumed; see loongarch_linux_prepare_to_resume.  */
+  dr_changed_t dr_changed_bp;
   dr_changed_t dr_changed_wp;
 };
 
index 088d0fcbf346bc954ce9041b053d98c6a65c43a7..03a0aaf9fd9629be8a3f389307837c1c2fff86a1 100644 (file)
@@ -43,7 +43,8 @@ loongarch_linux_prepare_to_resume (struct lwp_info *lwp)
   if (info == NULL)
     return;
 
-  if (DR_HAS_CHANGED (info->dr_changed_wp))
+  if (DR_HAS_CHANGED (info->dr_changed_bp)
+      || DR_HAS_CHANGED (info->dr_changed_wp))
     {
       ptid_t ptid = ptid_of_lwp (lwp);
       int tid = ptid.lwp ();
@@ -53,9 +54,19 @@ loongarch_linux_prepare_to_resume (struct lwp_info *lwp)
       if (show_debug_regs)
        debug_printf ("prepare_to_resume thread %d\n", tid);
 
-      loongarch_linux_set_debug_regs (state, tid, 1);
-      DR_CLEAR_CHANGED (info->dr_changed_wp);
-
+      /* Watchpoints.  */
+      if (DR_HAS_CHANGED (info->dr_changed_wp))
+       {
+         loongarch_linux_set_debug_regs (state, tid, 1);
+         DR_CLEAR_CHANGED (info->dr_changed_wp);
+       }
+
+      /* Breakpoints.  */
+      if (DR_HAS_CHANGED (info->dr_changed_bp))
+       {
+         loongarch_linux_set_debug_regs (state, tid, 0);
+         DR_CLEAR_CHANGED (info->dr_changed_bp);
+       }
     }
 }
 
@@ -72,6 +83,8 @@ loongarch_linux_new_thread (struct lwp_info *lwp)
   /* If there are hardware breakpoints/watchpoints in the process then mark that
      all the hardware breakpoint/watchpoint register pairs for this thread need
      to be initialized (with data from arch_process_info.debug_reg_state).  */
+  if (loongarch_any_set_debug_regs_state (state, false))
+    DR_MARK_ALL_CHANGED (info->dr_changed_bp, loongarch_num_bp_regs);
   if (loongarch_any_set_debug_regs_state (state, true))
     DR_MARK_ALL_CHANGED (info->dr_changed_wp, loongarch_num_wp_regs);
 
index 2d183342b49e6a91312bff8ff996a7a58e0a685d..c9920e7731a7a4c23cb620310053da90a8619b66 100644 (file)
                                        /*   note name must be "LINUX".  */
 #define NT_LARCH_LBT    0xa04          /* LoongArch Binary Translation registers */
                                        /*   note name must be "CORE".  */
+#define NT_LOONGARCH_HW_BREAK   0xa05  /* LoongArch hardware breakpoint registers */
+                                       /*   note name must be "LINUX".  */
 #define NT_LOONGARCH_HW_WATCH   0xa06  /* LoongArch hardware watchpoint registers */
                                        /*   note name must be "LINUX".  */
 #define NT_RISCV_CSR    0x900          /* RISC-V Control and Status Registers */