Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH v2 0/2] RISC-V hardware breakpoint/watchpoint support
@ 2026-04-09  2:45 Zane Leung
  2026-04-09  2:45 ` [PATCH v2 1/2] gdb: riscv: Add support for hardware breakpoints/watchpoints Zane Leung
  2026-04-09  2:45 ` [PATCH v2 2/2] gdbserver: " Zane Leung
  0 siblings, 2 replies; 4+ messages in thread
From: Zane Leung @ 2026-04-09  2:45 UTC (permalink / raw)
  To: gdb-patches; +Cc: zhuangqiubin

From: liangzhen <zhen.liang@spacemit.com>

This series implements hardware breakpoint and watchpoint support for RISC-V
architecture on Linux. It requires corresponding Linux kernel support.

Based on Himanshu's work:
https://lore.kernel.org/all/20260223044918.1359983-2-himanshu.chauhan@oss.qualcomm.com

Changes since v1:
- Fixed some code style.
- Add gdbserver support.

liangzhen (2):
  gdb: riscv: Add support for hardware breakpoints/watchpoints
  gdbserver: riscv: Add support for hardware breakpoints/watchpoints

 gdb/Makefile.in                |   3 +
 gdb/configure.nat              |   3 +-
 gdb/nat/riscv-hw-point.c       | 244 ++++++++++++++++++++++++
 gdb/nat/riscv-hw-point.h       |  89 +++++++++
 gdb/nat/riscv-linux-hw-point.c | 192 +++++++++++++++++++
 gdb/nat/riscv-linux-hw-point.h | 122 ++++++++++++
 gdb/nat/riscv-linux.c          |  82 ++++++++
 gdb/nat/riscv-linux.h          |  39 ++++
 gdb/riscv-linux-nat.c          | 333 +++++++++++++++++++++++++++++++++
 gdbserver/configure.srv        |   3 +
 gdbserver/linux-riscv-low.cc   | 239 +++++++++++++++++++++++
 include/elf/common.h           |   2 +
 12 files changed, 1350 insertions(+), 1 deletion(-)
 create mode 100644 gdb/nat/riscv-hw-point.c
 create mode 100644 gdb/nat/riscv-hw-point.h
 create mode 100644 gdb/nat/riscv-linux-hw-point.c
 create mode 100644 gdb/nat/riscv-linux-hw-point.h
 create mode 100644 gdb/nat/riscv-linux.c
 create mode 100644 gdb/nat/riscv-linux.h

-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v2 1/2] gdb: riscv: Add support for hardware breakpoints/watchpoints
  2026-04-09  2:45 [PATCH v2 0/2] RISC-V hardware breakpoint/watchpoint support Zane Leung
@ 2026-04-09  2:45 ` Zane Leung
  2026-04-09  2:45 ` [PATCH v2 2/2] gdbserver: " Zane Leung
  1 sibling, 0 replies; 4+ messages in thread
From: Zane Leung @ 2026-04-09  2:45 UTC (permalink / raw)
  To: gdb-patches; +Cc: zhuangqiubin

From: liangzhen <zhen.liang@spacemit.com>

Implement hardware breakpoint and watchpoint support for RISC-V
architecture.
---
 gdb/Makefile.in                |   3 +
 gdb/configure.nat              |   3 +-
 gdb/nat/riscv-hw-point.c       | 244 ++++++++++++++++++++++++
 gdb/nat/riscv-hw-point.h       |  89 +++++++++
 gdb/nat/riscv-linux-hw-point.c | 192 +++++++++++++++++++
 gdb/nat/riscv-linux-hw-point.h | 122 ++++++++++++
 gdb/nat/riscv-linux.c          |  82 ++++++++
 gdb/nat/riscv-linux.h          |  39 ++++
 gdb/riscv-linux-nat.c          | 333 +++++++++++++++++++++++++++++++++
 include/elf/common.h           |   2 +
 10 files changed, 1108 insertions(+), 1 deletion(-)
 create mode 100644 gdb/nat/riscv-hw-point.c
 create mode 100644 gdb/nat/riscv-hw-point.h
 create mode 100644 gdb/nat/riscv-linux-hw-point.c
 create mode 100644 gdb/nat/riscv-linux-hw-point.h
 create mode 100644 gdb/nat/riscv-linux.c
 create mode 100644 gdb/nat/riscv-linux.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 2488d789580..8998c3fdfbf 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1571,6 +1571,9 @@ HFILES_NO_SRCDIR = \
 	nat/linux-procfs.h \
 	nat/linux-ptrace.h \
 	nat/linux-waitpid.h \
+	nat/riscv-hw-point.h \
+	nat/riscv-linux.h \
+	nat/riscv-linux-hw-point.h \
 	nat/loongarch-hw-point.h \
 	nat/loongarch-linux.h \
 	nat/loongarch-linux-hw-point.h \
diff --git a/gdb/configure.nat b/gdb/configure.nat
index 38dd4179511..aee8541f486 100644
--- a/gdb/configure.nat
+++ b/gdb/configure.nat
@@ -325,7 +325,8 @@ case ${gdb_host} in
 	    riscv*)
 		# Host: RISC-V, running Linux
 		NATDEPFILES="${NATDEPFILES} riscv-linux-nat.o \
-		nat/riscv-linux-tdesc.o"
+		nat/riscv-linux-tdesc.o nat/riscv-hw-point.o \
+		nat/riscv-linux.o nat/riscv-linux-hw-point.o"
 		;;
 	    s390)
 		# Host: S390, running Linux
diff --git a/gdb/nat/riscv-hw-point.c b/gdb/nat/riscv-hw-point.c
new file mode 100644
index 00000000000..8c13895c668
--- /dev/null
+++ b/gdb/nat/riscv-hw-point.c
@@ -0,0 +1,244 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   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 "gdbsupport/break-common.h"
+#include "gdbsupport/common-regcache.h"
+#include "riscv-hw-point.h"
+#include "riscv-linux-hw-point.h"
+
+/* Number of hardware breakpoints/watchpoints the target supports.
+   They are initialized with values obtained via ptrace.  */
+
+int riscv_num_hwbp_regs;
+
+/* Record the insertion of one breakpoint/watchpoint, as represented by
+   ADDR, TYPE and LEN, in the process' arch-specific data area *STATE.
+   Return TRUE if the operation succeeds.  */
+
+static bool
+riscv_dr_state_insert_one_point (ptid_t ptid,
+				 struct riscv_debug_reg_state *state,
+				 enum target_hw_bp_type type, CORE_ADDR addr,
+				 int len)
+{
+  int i, idx, num_regs;
+  unsigned int *dr_type_p, *dr_len_p, *dr_ref_count;
+  CORE_ADDR *dr_addr_p;
+
+  /* Set up state pointers.  */
+  num_regs = riscv_num_hwbp_regs;
+  dr_addr_p = state->dr_addr_hwbp;
+  dr_type_p = state->dr_type_hwbp;
+  dr_len_p = state->dr_len_hwbp;
+  dr_ref_count = state->dr_ref_count_hwbp;
+
+  /* Find an existing or free register in our cache.  */
+  idx = -1;
+  for (i = 0; i < num_regs; ++i)
+    {
+      if (dr_ref_count[i] == 0)
+	{
+	  idx = i;
+	  /* no break; continue hunting for an exising one.  */
+	}
+      else if (dr_addr_p[i] == addr && dr_type_p[i] == type
+	       && dr_len_p[i]  == len)
+	{
+	  idx = i;
+	  break;
+	}
+    }
+
+  /* No space.  */
+  if (idx == -1)
+    return false;
+
+  /* Update our cache.  */
+  if (dr_ref_count[idx] == 0)
+    {
+      /* new entry  */
+      dr_addr_p[idx] = addr;
+      dr_type_p[idx] = type;
+      dr_len_p[idx] = len;
+      dr_ref_count[idx] = 1;
+
+      /* Notify the change.  */
+      riscv_notify_debug_reg_change (ptid, idx);
+    }
+  else
+    {
+      /* existing entry  */
+      dr_ref_count[idx]++;
+    }
+
+  return true;
+}
+
+/* Record the removal of one breakpoint/watchpoint, as represented by
+   ADDR, TYPE and LEN, in the process' arch-specific data area *STATE.
+   Return TRUE if the operation succeeds.  */
+
+static bool
+riscv_dr_state_remove_one_point (ptid_t ptid,
+				 struct riscv_debug_reg_state *state,
+				 enum target_hw_bp_type type, CORE_ADDR addr,
+				 int len)
+{
+  int i, num_regs;
+  unsigned int *dr_type_p, *dr_len_p, *dr_ref_count;
+  CORE_ADDR *dr_addr_p;
+
+  /* Set up state pointers.  */
+  num_regs = riscv_num_hwbp_regs;
+  dr_addr_p = state->dr_addr_hwbp;
+  dr_type_p = state->dr_type_hwbp;
+  dr_len_p = state->dr_len_hwbp;
+  dr_ref_count = state->dr_ref_count_hwbp;
+
+  /* Find the entry that matches the ADDR, TYPE and LEN.  */
+  for (i = 0; i < num_regs; ++i)
+    if (dr_addr_p[i] == addr && dr_type_p[i] == type &&
+	dr_len_p[i]  == len)
+      {
+	gdb_assert (dr_ref_count[i] != 0);
+	break;
+      }
+
+  /* Not found.  */
+  if (i == num_regs)
+    return false;
+
+  /* Clear our cache.  */
+  if (--dr_ref_count[i] == 0)
+    {
+      dr_addr_p[i] = 0;
+      dr_type_p[i] = 0;
+      dr_len_p[i] = 0;
+
+      /* Notify the change.  */
+      riscv_notify_debug_reg_change (ptid, i);
+    }
+
+  return true;
+}
+
+/* Handle insertion or removal of a hardware breakpoint/watchpoint.
+   If IS_INSERT is 1, insert the point; otherwise remove it.
+   Return TRUE if the operation succeeds.  */
+
+bool
+riscv_handle_point (enum target_hw_bp_type type, CORE_ADDR addr,
+		    int len, int is_insert, ptid_t ptid,
+		    struct riscv_debug_reg_state *state)
+{
+  if (is_insert)
+    return riscv_dr_state_insert_one_point (ptid, state, type, addr, len);
+  else
+    return riscv_dr_state_remove_one_point (ptid, state, type, addr, len);
+}
+
+/* Return TRUE if there are any hardware breakpoints/watchpoints.  */
+
+bool
+riscv_any_set_debug_regs_state (riscv_debug_reg_state *state)
+{
+  int count = riscv_num_hwbp_regs;
+  if (count == 0)
+    return false;
+
+  const CORE_ADDR *addr = state->dr_addr_hwbp;
+  const unsigned int *len = state->dr_len_hwbp;
+
+  for (int i = 0; i < count; i++)
+    if (addr[i] != 0 || len[i] != 0)
+      return true;
+
+  return false;
+}
+
+/* Print the values of the cached breakpoint/watchpoint registers.  */
+
+void
+riscv_show_debug_reg_state (struct riscv_debug_reg_state *state,
+			    const char *func, CORE_ADDR addr,
+			    int len, enum target_hw_bp_type type)
+{
+  debug_printf ("%s", func);
+  if (addr != 0 || len != 0)
+    debug_printf (" (addr=0x%08lx, len=%d, type=%s)",
+		  (unsigned long) addr, len,
+		  type == hw_write ? "hw-write-watchpoint"
+		  : (type == hw_read ? "hw-read-watchpoint"
+		     : (type == hw_access ? "hw-access-watchpoint"
+			: (type == hw_execute ? "hw-breakpoint"
+			   : "??unknown??"))));
+  debug_printf (":\n");
+
+  debug_printf ("\tHWPOINTs:\n");
+  for (int i = 0; i < riscv_num_hwbp_regs; i++)
+    debug_printf ("\t%cP%d: addr=%s, len=%d, type=%s, ref.count=%d\n",
+		  state->dr_type_hwbp[i] == hw_execute ? 'B' : 'W',
+		  i, core_addr_to_string_nz (state->dr_addr_hwbp[i]),
+		  state->dr_len_hwbp[i],
+		  type == hw_write ? "hw-write-watchpoint"
+		  : (type == hw_read ? "hw-read-watchpoint"
+		     : (type == hw_access ? "hw-access-watchpoint"
+			: (type == hw_execute ? "hw-breakpoint"
+			   : "??unknown??"))),
+		  state->dr_ref_count_hwbp[i]);
+}
+
+/* Return true if we can watch a memory region that starts address
+   ADDR and whose length is LEN in bytes.  */
+
+int
+riscv_region_ok_for_watchpoint (CORE_ADDR addr, int len)
+{
+  /* Can not set watchpoints for zero or negative lengths.  */
+  if (len <= 0)
+    return 0;
+
+  /* Must have hardware watchpoint debug register(s).  */
+  if (riscv_num_hwbp_regs == 0)
+    return 0;
+
+  return 1;
+}
+
+/* Helper for the "stopped_data_address/low_stopped_data_address" target
+   method.  Returns TRUE if a hardware watchpoint trap at ADDR_TRAP matches
+   a set watchpoint.  The address of the matched watchpoint is returned in
+   *ADDR_P.  */
+
+bool
+riscv_stopped_data_address (const struct riscv_debug_reg_state *state,
+			    CORE_ADDR addr_trap, CORE_ADDR *addr_p)
+{
+  for (int i = 0; i < riscv_num_hwbp_regs; i++)
+    {
+      const CORE_ADDR addr = state->dr_addr_hwbp[i];
+      if (state->dr_ref_count_hwbp[i]
+	  && addr_trap == addr
+	  && state->dr_type_hwbp[i] != hw_execute)
+	{
+	  *addr_p = addr;
+	  return true;
+	}
+    }
+
+  return false;
+}
diff --git a/gdb/nat/riscv-hw-point.h b/gdb/nat/riscv-hw-point.h
new file mode 100644
index 00000000000..0a6d3795d64
--- /dev/null
+++ b/gdb/nat/riscv-hw-point.h
@@ -0,0 +1,89 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef GDB_NAT_RISCV_HW_POINT_H
+#define GDB_NAT_RISCV_HW_POINT_H
+
+/* Macro definitions, data structures, and code for the hardware
+   breakpoint and hardware watchpoint support follow.  We use the
+   following abbreviations throughout the code:
+
+   hw - hardware
+   bp - breakpoint
+   wp - watchpoint  */
+
+/* Maximum number of hardware breakpoint + watchpoint registers. */
+
+#define RISCV_HWBP_MAX_NUM 16
+
+
+/* Structure for managing the hardware breakpoint/watchpoint resources.
+   DR_ADDR_* stores the address, DR_CTRL_* stores the control register
+   content, and DR_REF_COUNT_* counts the numbers of references to the
+   corresponding hwbp, by which way the limited hardware resources are
+   not wasted on duplicated bp/wp settings (though so far gdb has done
+   a good job by not sending duplicated bp/wp requests).  */
+
+struct riscv_debug_reg_state
+{
+  /* hardware/hardware breakpoint */
+  CORE_ADDR dr_addr_hwbp[RISCV_HWBP_MAX_NUM];
+  unsigned int dr_type_hwbp[RISCV_HWBP_MAX_NUM];
+  unsigned int dr_len_hwbp[RISCV_HWBP_MAX_NUM];
+  unsigned int dr_ref_count_hwbp[RISCV_HWBP_MAX_NUM];
+};
+
+extern int riscv_num_hwbp_regs;
+
+/* Invoked when IDXth breakpoint/watchpoint register pair needs to be
+   updated.  */
+
+void riscv_notify_debug_reg_change (ptid_t ptid, unsigned int idx);
+
+
+/* Handle insertion or removal of a hardware breakpoint/watchpoint.
+   If IS_INSERT is 1, insert the point; otherwise remove it.
+   Return TRUE if the operation succeeds.  */
+
+bool riscv_handle_point (enum target_hw_bp_type type, CORE_ADDR addr,
+			 int len, int is_insert, ptid_t ptid,
+			 struct riscv_debug_reg_state *state);
+
+/* Return TRUE if there are any hardware breakpoints/watchpoints.  */
+
+bool riscv_any_set_debug_regs_state (riscv_debug_reg_state *state);
+
+/* Print the values of the cached breakpoint/watchpoint registers.  */
+
+void riscv_show_debug_reg_state (struct riscv_debug_reg_state *state,
+				 const char *func, CORE_ADDR addr,
+				 int len, enum target_hw_bp_type type);
+
+/* Return true if we can watch a memory region that starts address
+   ADDR and whose length is LEN in bytes.  */
+
+int riscv_region_ok_for_watchpoint (CORE_ADDR addr, int len);
+
+/* Helper for the "stopped_data_address/low_stopped_data_address" target
+   method.  Returns TRUE if a hardware watchpoint trap at ADDR_TRAP matches
+   a set watchpoint.  The address of the matched watchpoint is returned in
+   *ADDR_P.  */
+
+bool riscv_stopped_data_address (const struct riscv_debug_reg_state *state,
+				 CORE_ADDR addr_trap, CORE_ADDR *addr_p);
+
+#endif /* GDB_NAT_RISCV_HW_POINT_H */
diff --git a/gdb/nat/riscv-linux-hw-point.c b/gdb/nat/riscv-linux-hw-point.c
new file mode 100644
index 00000000000..e7f3865bb66
--- /dev/null
+++ b/gdb/nat/riscv-linux-hw-point.c
@@ -0,0 +1,192 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   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 "gdbsupport/break-common.h"
+#include "gdbsupport/common-regcache.h"
+#include "nat/linux-nat.h"
+#include "riscv-linux-hw-point.h"
+
+#include <sys/uio.h>
+
+/* The order in which <sys/ptrace.h> and <asm/ptrace.h> are included
+   can be important.  <sys/ptrace.h> often declares various PTRACE_*
+   enums.  <asm/ptrace.h> often defines preprocessor constants for
+   these very same symbols.  When that's the case, build errors will
+   result when <asm/ptrace.h> is included before <sys/ptrace.h>.  */
+
+#include <sys/ptrace.h>
+#include <asm/ptrace.h>
+
+#include "elf/common.h"
+
+/* Helper for riscv_notify_debug_reg_change.  Records the
+   information about the change of one hardware breakpoint/watchpoint
+   setting for the thread LWP.
+   N.B.  The actual updating of hardware debug registers is not
+   carried out until the moment the thread is resumed.  */
+
+static int
+riscv_dr_change_callback (struct lwp_info *lwp, unsigned int idx)
+{
+  int tid = ptid_of_lwp (lwp).lwp ();
+  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 ("riscv_dr_change_callback: \n\tOn entry:\n");
+      debug_printf ("\ttid%d, dr_changed_hwbp=0x%s\n", tid,
+		    phex (info->dr_changed_hwbp, 8));
+    }
+
+  dr_changed_ptr = &info->dr_changed_hwbp;
+  dr_changed = *dr_changed_ptr;
+
+  gdb_assert (idx >= 0 && idx <= riscv_num_hwbp_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_hwbp=0x%s\n", tid,
+		    phex (info->dr_changed_hwbp, 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.  */
+
+void
+riscv_notify_debug_reg_change (ptid_t ptid, unsigned int idx)
+{
+  ptid_t pid_ptid = ptid_t (ptid.pid ());
+
+  iterate_over_lwps (pid_ptid, [=] (struct lwp_info *info)
+			       {
+				 return riscv_dr_change_callback (info,
+								  idx);
+			       });
+}
+
+/* Convert GDB hardware breakpoint/watchpoint type to RISC-V hardware type.  */
+
+static unsigned int
+riscv_hwbp_type (unsigned int type)
+{
+  switch (type)
+    {
+    case hw_execute:
+      return RISCV_HW_EXECUTE;
+    case hw_read:
+      return RISCV_HW_READ;
+    case hw_write:
+      return RISCV_HW_WRITE;
+    case hw_access:
+      return RISCV_HW_ACCESS;
+    }
+
+  gdb_assert_not_reached ("unexpected hwbp type");
+}
+
+/* Call ptrace to set the thread TID's hardware breakpoint/watchpoint
+   registers with data from *STATE.  */
+
+void
+riscv_linux_set_debug_regs (struct riscv_debug_reg_state *state,
+			    int tid)
+{
+  int i, count;
+  struct iovec iov;
+  struct riscv_hwdebug_state regs;
+
+  memset (&regs, 0, sizeof (regs));
+  iov.iov_base = &regs;
+  count = riscv_num_hwbp_regs;
+
+  if (count == 0)
+    return;
+
+  iov.iov_len = (offsetof (struct riscv_hwdebug_state, dbg_regs)
+		 + count * sizeof (regs.dbg_regs[0]));
+  for (i = 0; i < count; i++)
+    {
+      regs.dbg_regs[i].addr = state->dr_addr_hwbp[i];
+      regs.dbg_regs[i].type = riscv_hwbp_type(state->dr_type_hwbp[i]);
+      regs.dbg_regs[i].len = state->dr_len_hwbp[i];
+    }
+
+  if (ptrace(PTRACE_SETREGSET, tid, NT_RISCV_HW_BREAK, (void *) &iov))
+    {
+      if (errno == EINVAL)
+	error (_("Invalid argument setting hardware debug registers"));
+      else
+	error (_("Unexpected error setting hardware debug registers"));
+    }
+}
+
+/* Get the hardware debug register capacity information from the
+   process represented by TID.  */
+
+void
+riscv_linux_get_debug_reg_capacity (int tid)
+{
+  struct iovec iov;
+  struct riscv_hwdebug_state dbg_state;
+  int result;
+  iov.iov_base = &dbg_state;
+  iov.iov_len = sizeof (dbg_state);
+
+  /* Get hardware breakpoint/watchpoint register info.  */
+  result = ptrace (PTRACE_GETREGSET, tid, NT_RISCV_HW_BREAK, &iov);
+
+  if (result == 0)
+    {
+      riscv_num_hwbp_regs = RISCV_DEBUG_NUM_SLOTS (dbg_state.dbg_info);
+      if (riscv_num_hwbp_regs > RISCV_HWBP_MAX_NUM)
+	{
+	  warning (_("Unexpected number of hardware breakpoint/watchpoint"
+		     " registers reported by ptrace, got %d, expected %d."),
+		   riscv_num_hwbp_regs, RISCV_HWBP_MAX_NUM);
+	  riscv_num_hwbp_regs = RISCV_HWBP_MAX_NUM;
+	}
+    }
+  else
+    {
+      warning (_("Unable to determine the number of hardware"
+		 " breakpoints/watchpoints available."));
+      riscv_num_hwbp_regs = 0;
+    }
+}
diff --git a/gdb/nat/riscv-linux-hw-point.h b/gdb/nat/riscv-linux-hw-point.h
new file mode 100644
index 00000000000..0f0f8f79577
--- /dev/null
+++ b/gdb/nat/riscv-linux-hw-point.h
@@ -0,0 +1,122 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDB_NAT_RISCV_LINUX_HW_POINT_H
+#define GDB_NAT_RISCV_LINUX_HW_POINT_H
+
+#include "gdbsupport/break-common.h" /* For enum target_hw_bp_type.  */
+
+#include "nat/riscv-hw-point.h"
+
+struct riscv_hwdebug_state {
+  uint64_t dbg_info;
+  struct {
+    uint64_t addr;
+    uint64_t type;
+    uint64_t len;
+  } dbg_regs[16];
+};
+
+enum riscv_hwbp_type {
+  RISCV_HW_EXECUTE = 0,
+  RISCV_HW_READ = 1,
+  RISCV_HW_WRITE = 2,
+  RISCV_HW_ACCESS = 3,
+};
+
+/* Macros to extract fields from the hardware debug information word.  */
+#define RISCV_DEBUG_NUM_SLOTS(x) ((x) & 0xffff)
+
+/* Each bit of a variable of this type is used to indicate whether a
+   hardware breakpoint or watchpoint setting has been changed since
+   the last update.
+
+   Bit N corresponds to the Nth hardware breakpoint or watchpoint
+   setting which is managed in riscv_debug_reg_state, where N is
+   valid between 0 and the total number of the hardware breakpoint or
+   watchpoint debug registers minus 1.
+
+   When bit N is 1, the corresponding breakpoint or watchpoint setting
+   has changed, and therefore the corresponding hardware debug
+   register needs to be updated via the ptrace interface.
+
+   In the per-thread arch-specific data area, we define two such
+   variables for per-thread hardware breakpoint and watchpoint
+   settings respectively.
+
+   This type is part of the mechanism which helps reduce the number of
+   ptrace calls to the kernel, i.e. avoid asking the kernel to write
+   to the debug registers with unchanged values.  */
+
+typedef ULONGEST dr_changed_t;
+
+/* Set each of the lower M bits of X to 1; assert X is wide enough.  */
+
+#define DR_MARK_ALL_CHANGED(x, m)					\
+  do									\
+    {									\
+      gdb_assert (sizeof ((x)) * 8 >= (m));				\
+      (x) = (((dr_changed_t)1 << (m)) - 1);				\
+    } while (0)
+
+#define DR_MARK_N_CHANGED(x, n)						\
+  do									\
+    {									\
+      (x) |= ((dr_changed_t)1 << (n));					\
+    } while (0)
+
+#define DR_CLEAR_CHANGED(x)						\
+  do									\
+    {									\
+      (x) = 0;								\
+    } while (0)
+
+#define DR_HAS_CHANGED(x) ((x) != 0)
+#define DR_N_HAS_CHANGED(x, n) ((x) & ((dr_changed_t)1 << (n)))
+
+/* Per-thread arch-specific data we want to keep.  */
+
+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 riscv_linux_prepare_to_resume.  */
+  dr_changed_t dr_changed_hwbp;
+};
+
+/* Call ptrace to set the thread TID's hardware breakpoint/watchpoint
+   registers with data from *STATE.  */
+
+void riscv_linux_set_debug_regs (struct riscv_debug_reg_state *state,
+				 int tid);
+
+/* Get the hardware debug register capacity information from the
+   process represented by TID.  */
+
+void riscv_linux_get_debug_reg_capacity (int tid);
+
+/* Return the debug register state for process PID.  If no existing
+   state is found for this process, return nullptr.  */
+
+struct riscv_debug_reg_state *riscv_lookup_debug_reg_state (pid_t pid);
+
+/* Return the debug register state for process PID.  If no existing
+   state is found for this process, create new state.  */
+
+struct riscv_debug_reg_state *riscv_get_debug_reg_state (pid_t pid);
+
+#endif /* GDB_NAT_RISCV_LINUX_HW_POINT_H */
diff --git a/gdb/nat/riscv-linux.c b/gdb/nat/riscv-linux.c
new file mode 100644
index 00000000000..65183fd3dbd
--- /dev/null
+++ b/gdb/nat/riscv-linux.c
@@ -0,0 +1,82 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   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 "gdbsupport/break-common.h"
+#include "nat/linux-nat.h"
+#include "nat/riscv-linux-hw-point.h"
+#include "nat/riscv-linux.h"
+
+#include "elf/common.h"
+#include "nat/gdb_ptrace.h"
+#include <asm/ptrace.h>
+#include <sys/uio.h>
+
+/* Called when resuming a thread LWP.
+   The hardware debug registers are updated when there is any change.  */
+
+void
+riscv_linux_prepare_to_resume (struct lwp_info *lwp)
+{
+  struct arch_lwp_info *info = lwp_arch_private_info (lwp);
+
+  /* NULL means this is the main thread still going through the shell,
+     or, no watchpoint has been set yet.  In that case, there's
+     nothing to do.  */
+  if (info == NULL)
+    return;
+
+  if (DR_HAS_CHANGED (info->dr_changed_hwbp))
+    {
+      ptid_t ptid = ptid_of_lwp (lwp);
+      int tid = ptid.lwp ();
+      struct riscv_debug_reg_state *state
+	= riscv_get_debug_reg_state (ptid.pid ());
+
+      if (show_debug_regs)
+	debug_printf ("prepare_to_resume thread %d\n", tid);
+
+      riscv_linux_set_debug_regs (state, tid);
+      DR_CLEAR_CHANGED (info->dr_changed_hwbp);
+    }
+}
+
+/* Function to call when a new thread is detected.  */
+
+void
+riscv_linux_new_thread (struct lwp_info *lwp)
+{
+  ptid_t ptid = ptid_of_lwp (lwp);
+  struct riscv_debug_reg_state *state
+	= riscv_get_debug_reg_state (ptid.pid ());
+  struct arch_lwp_info *info = XCNEW (struct arch_lwp_info);
+
+  /* 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 (riscv_any_set_debug_regs_state (state))
+    DR_MARK_ALL_CHANGED (info->dr_changed_hwbp, riscv_num_hwbp_regs);
+
+  lwp_set_arch_private_info (lwp, info);
+}
+
+/* See nat/riscv-linux.h.  */
+
+void
+riscv_linux_delete_thread (struct arch_lwp_info *arch_lwp)
+{
+  xfree (arch_lwp);
+}
diff --git a/gdb/nat/riscv-linux.h b/gdb/nat/riscv-linux.h
new file mode 100644
index 00000000000..b147ba93319
--- /dev/null
+++ b/gdb/nat/riscv-linux.h
@@ -0,0 +1,39 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDB_NAT_RISCV_LINUX_H
+#define GDB_NAT_RISCV_LINUX_H
+
+#include <signal.h>
+
+/* Defines ps_err_e, struct ps_prochandle.  */
+#include "gdb_proc_service.h"
+
+/* Called when resuming a thread LWP.
+   The hardware debug registers are updated when there is any change.  */
+
+void riscv_linux_prepare_to_resume (struct lwp_info *lwp);
+
+/* Function to call when a new thread is detected.  */
+
+void riscv_linux_new_thread (struct lwp_info *lwp);
+
+/* Function to call when a thread is being deleted.  */
+
+void riscv_linux_delete_thread (struct arch_lwp_info *arch_lwp);
+
+#endif /* GDB_NAT_RISCV_LINUX_H */
diff --git a/gdb/riscv-linux-nat.c b/gdb/riscv-linux-nat.c
index e1e81ad50c4..77b8e868590 100644
--- a/gdb/riscv-linux-nat.c
+++ b/gdb/riscv-linux-nat.c
@@ -22,9 +22,13 @@
 #include "riscv-tdep.h"
 #include "inferior.h"
 
+#include "cli/cli-cmds.h"
 #include "elf/common.h"
 
 #include "nat/riscv-linux-tdesc.h"
+#include "nat/riscv-hw-point.h"
+#include "nat/riscv-linux.h"
+#include "nat/riscv-linux-hw-point.h"
 
 #include <sys/ptrace.h>
 
@@ -33,6 +37,44 @@
 # define NFPREG 33
 #endif
 
+/* Hash table storing per-process data.  We don't bind this to a
+   per-inferior registry because of targets like GNU/Linux that
+   need to keep track of processes that aren't bound to any inferior
+   (e.g., fork children, checkpoints).  */
+
+static std::unordered_map<pid_t, riscv_debug_reg_state>
+    riscv_debug_process_state;
+
+/* Return the debug register state for process PID.  If no existing
+   state is found for this process, return nullptr.  */
+
+struct riscv_debug_reg_state *
+riscv_lookup_debug_reg_state (pid_t pid)
+{
+  auto it = riscv_debug_process_state.find (pid);
+  if (it != riscv_debug_process_state.end ())
+    return &it->second;
+
+  return nullptr;
+}
+
+/* Return the debug register state for process PID.  If no existing
+   state is found for this process, create new state.  */
+
+struct riscv_debug_reg_state *
+riscv_get_debug_reg_state (pid_t pid)
+{
+  return &riscv_debug_process_state[pid];
+}
+
+/* Remove any existing per-process debug state for process PID.  */
+
+static void
+riscv_remove_debug_reg_state (pid_t pid)
+{
+  riscv_debug_process_state.erase (pid);
+}
+
 /* RISC-V Linux native additions to the default linux support.  */
 
 class riscv_linux_nat_target final : public linux_nat_target
@@ -42,6 +84,37 @@ class riscv_linux_nat_target final : public linux_nat_target
   void fetch_registers (struct regcache *regcache, int regnum) override;
   void store_registers (struct regcache *regcache, int regnum) override;
 
+  /* Add our hardware breakpoint and watchpoint implementation.  */
+  int can_use_hw_breakpoint (enum bptype type, int cnt, int othertype) override;
+  int region_ok_for_hw_watchpoint (CORE_ADDR addr, int len) 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;
+  int insert_watchpoint (CORE_ADDR addr, int len, enum target_hw_bp_type type,
+			 struct expression *cond) override;
+  int remove_watchpoint (CORE_ADDR addr, int len, enum target_hw_bp_type type,
+			 struct expression *cond) override;
+  bool stopped_by_watchpoint () override;
+  std::vector<CORE_ADDR> stopped_data_addresses () override;
+
+  /* Override the GNU/Linux inferior startup hook. */
+  void post_startup_inferior (ptid_t ptid) override;
+
+  /* Override the GNU/Linux post attach hook. */
+  void post_attach (int pid) override;
+
+  /* These three defer to common nat/ code. */
+  void low_new_thread (struct lwp_info *lp) override
+  { riscv_linux_new_thread (lp); }
+  void low_delete_thread (struct arch_lwp_info *lp) override
+  { riscv_linux_delete_thread (lp); }
+  void low_prepare_to_resume (struct lwp_info *lp) override
+  { riscv_linux_prepare_to_resume (lp); }
+
+  void low_new_fork (struct lwp_info *parent, pid_t child_pid) override;
+  void low_forget_process (pid_t pid) override;
+
   /* Read suitable target description.  */
   const struct target_desc *read_description () override;
 };
@@ -327,6 +400,251 @@ riscv_linux_nat_target::store_registers (struct regcache *regcache, int regnum)
      now.  */
 }
 
+/* Check if nat target can use hardware breakpoints/watchpoints. */
+
+int
+riscv_linux_nat_target::can_use_hw_breakpoint (enum bptype type, int cnt,
+					       int othertype)
+{
+  switch (type)
+    {
+    case bp_hardware_breakpoint:
+    case bp_hardware_watchpoint:
+    case bp_read_watchpoint:
+    case bp_access_watchpoint:
+    case bp_watchpoint:
+      if (riscv_num_hwbp_regs == 0)
+	return 0;
+      break;
+    default:
+      gdb_assert_not_reached ("unexpected breakpoint type");
+    }
+  return 1;
+}
+
+int
+riscv_linux_nat_target::region_ok_for_hw_watchpoint (CORE_ADDR addr,
+						     int len)
+{
+  return riscv_region_ok_for_watchpoint (addr, len);
+}
+
+/* Insert a hardware breakpoint at BP_TGT->placed_address.
+   Return 0 if point is inserted, -1 if not. */
+
+int
+riscv_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 riscv_debug_reg_state *state
+	= riscv_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 = riscv_handle_point (type, addr, len, 1 /* is_insert */,
+			    inferior_ptid, state) ? 0 : -1;
+
+  if (show_debug_regs)
+    {
+      riscv_show_debug_reg_state (state,
+				  "insert_hw_breakpoint", addr, len, type);
+    }
+
+  return ret;
+}
+
+/* Remove a hardware breakpoint at BP_TGT->placed_address.
+   Return 0 if point is removeed, -1 if not. */
+
+int
+riscv_linux_nat_target::remove_hw_breakpoint (struct gdbarch *gdbarch,
+					      struct bp_target_info *bp_tgt)
+{
+  int ret, len;
+  CORE_ADDR addr = bp_tgt->placed_address;
+  const enum target_hw_bp_type type = hw_execute;
+  struct riscv_debug_reg_state *state
+    = riscv_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 = riscv_handle_point (type, addr, len, 0 /* is_insert */,
+			    inferior_ptid, state) ? 0 : -1;
+
+  if (show_debug_regs)
+    {
+      riscv_show_debug_reg_state (state,
+				  "remove_hw_watchpoint", addr, len, type);
+    }
+
+  return ret;
+}
+
+/* Insert a hardware watchpoint of type TYPE on region starting at address ADDR
+   with LEN legth. Return 0 if point is inserted, -1 if not. */
+
+int
+riscv_linux_nat_target::insert_watchpoint (CORE_ADDR addr, int len,
+					   enum target_hw_bp_type type,
+					   struct expression *cond)
+{
+  int ret;
+  struct riscv_debug_reg_state *state
+	= riscv_get_debug_reg_state (inferior_ptid.pid ());
+
+  if (show_debug_regs)
+    gdb_printf (gdb_stdlog,
+		"insert_watchpoint on entry (addr=0x%08lx, len=%d)\n",
+		(unsigned long) addr, len);
+
+  gdb_assert (type != hw_execute);
+
+  ret = riscv_handle_point (type, addr, len, 1 /* is_insert */,
+			    inferior_ptid, state) ? 0 : -1;
+
+  if (show_debug_regs)
+    {
+      riscv_show_debug_reg_state (state,
+				  "insert_watchpoint", addr, len, type);
+    }
+
+  return ret;
+}
+
+/* Remove a hardware watchpoint of type TYPE on region starting at address ADDR
+   with LEN legth. Return 0 if point is removed, -1 if not. */
+
+int
+riscv_linux_nat_target::remove_watchpoint (CORE_ADDR addr, int len,
+					   enum target_hw_bp_type type,
+					   struct expression *cond)
+{
+  int ret;
+  struct riscv_debug_reg_state *state
+	= riscv_get_debug_reg_state (inferior_ptid.pid ());
+
+  if (show_debug_regs)
+    gdb_printf (gdb_stdlog,
+		"remove_watchpoint on entry (addr=0x%08lx, len=%d)\n",
+		(unsigned long) addr, len);
+
+  gdb_assert (type != hw_execute);
+
+  ret = riscv_handle_point (type, addr, len, 0 /* is_insert */,
+			    inferior_ptid, state) ? 0 : -1;
+
+  if (show_debug_regs)
+    {
+      riscv_show_debug_reg_state (state,
+				  "remove_watchpoint", addr, len, type);
+    }
+
+  return ret;
+}
+
+/* Implement the "stopped_data_addresses" target_ops method. */
+
+std::vector<CORE_ADDR>
+riscv_linux_nat_target::stopped_data_addresses ()
+{
+  siginfo_t siginfo;
+  struct riscv_debug_reg_state *state;
+
+  if (!linux_nat_get_siginfo (inferior_ptid, &siginfo))
+    return {};
+
+  /* This must be a hardware breakpoint.  */
+  if (siginfo.si_signo != SIGTRAP || (siginfo.si_code & 0xffff) != TRAP_HWBKPT)
+    return {};
+
+  state = riscv_get_debug_reg_state (inferior_ptid.pid ());
+
+  CORE_ADDR addr;
+  if (riscv_stopped_data_address (state, (CORE_ADDR) siginfo.si_addr,
+				  &addr))
+    return { addr };
+
+  return {};
+}
+
+/* Implement the "stopped_by_watchpoint" target_ops method.  */
+
+bool
+riscv_linux_nat_target::stopped_by_watchpoint ()
+{
+  return !stopped_data_addresses ().empty ();
+}
+
+/* Implement the virtual inf_ptrace_target::post_startup_inferior method. */
+
+void
+riscv_linux_nat_target::post_startup_inferior (ptid_t ptid)
+{
+  low_forget_process (ptid.pid ());
+  riscv_linux_get_debug_reg_capacity (ptid.pid ());
+  linux_nat_target::post_startup_inferior (ptid);
+}
+
+/* Implement the "post_attach" target_ops method.  */
+
+void
+riscv_linux_nat_target::post_attach (int pid)
+{
+  low_forget_process (pid);
+  riscv_linux_get_debug_reg_capacity (pid);
+  linux_nat_target::post_attach (pid);
+}
+
+/* linux_nat_new_fork hook.   */
+
+void
+riscv_linux_nat_target::low_new_fork (struct lwp_info *parent,
+				      pid_t child_pid)
+{
+  pid_t parent_pid;
+  struct riscv_debug_reg_state *parent_state;
+  struct riscv_debug_reg_state *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 = parent->ptid.pid ();
+  parent_state = riscv_get_debug_reg_state (parent_pid);
+  child_state = riscv_get_debug_reg_state (child_pid);
+  *child_state = *parent_state;
+}
+
+/* Called whenever GDB is no longer debugging process PID.  It deletes
+   data structures that keep track of debug register state.  */
+
+void
+riscv_linux_nat_target::low_forget_process (pid_t pid)
+{
+  riscv_remove_debug_reg_state (pid);
+}
+
 /* Initialize RISC-V Linux native support.  */
 
 INIT_GDB_FILE (riscv_linux_nat)
@@ -334,4 +652,19 @@ INIT_GDB_FILE (riscv_linux_nat)
   /* Register the target.  */
   linux_target = &the_riscv_linux_nat_target;
   add_inf_child_target (&the_riscv_linux_nat_target);
+
+  /* Add a maintenance command to enable printing the RISC-V internal
+     debug registers mirror variables. */
+  add_setshow_boolean_cmd ("show-debug-regs", class_maintenance,
+			   &show_debug_regs, _("\
+Set whether to show the RISC-V debug registers state."), _("\
+Show whether to show the RISC-V debug registers state."), _("\
+Use \"on\" to enable, \"off\" to disable.\n\
+If enabled, the debug registers values are shown when GDB inserts\n\
+or removes a hardware breakpoint or watchpoint, and when the inferior\n\
+triggers a breakpoint or watchpoint."),
+			   NULL,
+			   NULL,
+			   &maintenance_set_cmdlist,
+			   &maintenance_show_cmdlist);
 }
diff --git a/include/elf/common.h b/include/elf/common.h
index 1ae68221a89..5d27dcb14e0 100644
--- a/include/elf/common.h
+++ b/include/elf/common.h
@@ -781,6 +781,8 @@
 					/*   note name must be "LINUX".  */
 #define NT_RISCV_CSR    0x900		/* RISC-V Control and Status Registers */
 					/*   note name must be "LINUX".  */
+#define NT_RISCV_HW_BREAK    0x903	/* RISC-V hardware breakpoint registers */
+					/*   note name must be "LINUX".  */
 #define NT_SIGINFO	0x53494749	/* Fields of siginfo_t.  */
 #define NT_FILE		0x46494c45	/* Description of mapped files.  */
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v2 2/2] gdbserver: riscv: Add support for hardware breakpoints/watchpoints
  2026-04-09  2:45 [PATCH v2 0/2] RISC-V hardware breakpoint/watchpoint support Zane Leung
  2026-04-09  2:45 ` [PATCH v2 1/2] gdb: riscv: Add support for hardware breakpoints/watchpoints Zane Leung
@ 2026-04-09  2:45 ` Zane Leung
  2026-04-09  4:49   ` liangzhen
  1 sibling, 1 reply; 4+ messages in thread
From: Zane Leung @ 2026-04-09  2:45 UTC (permalink / raw)
  To: gdb-patches; +Cc: zhuangqiubin

From: liangzhen <zhen.liang@spacemit.com>

Add support for hardware breakpoints and watchpoints on RISC-V Linux
gdbserver.

Signed-off-by: liangzhen <zhen.liang@spacemit.com>
---
 gdbserver/configure.srv      |   3 +
 gdbserver/linux-riscv-low.cc | 239 +++++++++++++++++++++++++++++++++++
 2 files changed, 242 insertions(+)

diff --git a/gdbserver/configure.srv b/gdbserver/configure.srv
index 7bdd92e3f82..a2a01777cc8 100644
--- a/gdbserver/configure.srv
+++ b/gdbserver/configure.srv
@@ -284,6 +284,9 @@ case "${gdbserver_host}" in
   riscv*-*-linux*)	srv_tgtobj="arch/riscv.o nat/riscv-linux-tdesc.o"
 			srv_tgtobj="${srv_tgtobj} linux-riscv-low.o"
 			srv_tgtobj="${srv_tgtobj} ${srv_linux_obj}"
+			srv_tgtobj="${srv_tgtobj} nat/riscv-hw-point.o"
+			srv_tgtobj="${srv_tgtobj} nat/riscv-linux.o"
+			srv_tgtobj="${srv_tgtobj} nat/riscv-linux-hw-point.o"
 			srv_linux_regsets=yes
 			srv_linux_usrregs=yes
 			srv_linux_thread_db=yes
diff --git a/gdbserver/linux-riscv-low.cc b/gdbserver/linux-riscv-low.cc
index 8cc0a980fc1..d295619661e 100644
--- a/gdbserver/linux-riscv-low.cc
+++ b/gdbserver/linux-riscv-low.cc
@@ -21,6 +21,9 @@
 #include "linux-low.h"
 #include "tdesc.h"
 #include "elf/common.h"
+#include "nat/riscv-hw-point.h"
+#include "nat/riscv-linux.h"
+#include "nat/riscv-linux-hw-point.h"
 #include "nat/riscv-linux-tdesc.h"
 #include "opcode/riscv.h"
 
@@ -41,6 +44,8 @@ public:
 
   const gdb_byte *sw_breakpoint_from_kind (int kind, int *size) override;
 
+  bool supports_z_point_type (char z_type) override;
+
 protected:
 
   void low_arch_setup () override;
@@ -59,6 +64,28 @@ protected:
 
   bool low_breakpoint_at (CORE_ADDR pc) override;
 
+  int low_insert_point (raw_bkpt_type type, CORE_ADDR addr,
+			int size, raw_breakpoint *bp) override;
+
+  int low_remove_point (raw_bkpt_type type, CORE_ADDR addr,
+			int size, raw_breakpoint *bp) override;
+
+  bool low_stopped_by_watchpoint () override;
+
+  std::vector<CORE_ADDR> low_stopped_data_addresses () override;
+
+  arch_process_info *low_new_process () override;
+
+  void low_delete_process (arch_process_info *info) override;
+
+  void low_new_thread (lwp_info *) override;
+
+  void low_delete_thread (arch_lwp_info *) override;
+
+  void low_new_fork (process_info *parent, process_info *child) override;
+
+  void low_prepare_to_resume (lwp_info *lwp) override;
+
   bool low_supports_catch_syscall () override;
 
   void low_get_syscall_trapinfo (regcache *regcache, int *sysno) override;
@@ -82,6 +109,19 @@ riscv_target::low_cannot_store_register (int regno)
 			  "is not implemented by the target");
 }
 
+void
+riscv_target::low_prepare_to_resume (lwp_info *lwp)
+{
+  riscv_linux_prepare_to_resume (lwp);
+}
+
+/* Per-process arch-specific data we want to keep.  */
+
+struct arch_process_info
+{
+  struct riscv_debug_reg_state debug_reg_state;
+};
+
 /* Implementation of linux target ops method "low_supports_catch_syscall".  */
 
 bool
@@ -120,6 +160,7 @@ riscv_target::low_arch_setup ()
     }
 
   current_process ()->tdesc = tdesc.release ();
+  riscv_linux_get_debug_reg_capacity (current_thread->id.lwp ());
 }
 
 /* Collect GPRs from REGCACHE into BUF.  */
@@ -335,6 +376,204 @@ riscv_target::low_breakpoint_at (CORE_ADDR pc)
     return false;
 }
 
+/* Initialize the per-process RISC-V hardware debug register state.
+   Clear all breakpoint/watchpoint entries and reset their reference
+   counters in *STATE.  */
+
+static void
+riscv_init_debug_reg_state (struct riscv_debug_reg_state *state)
+{
+  for (int i = 0; i < RISCV_HWBP_MAX_NUM; ++i)
+    {
+      state->dr_addr_hwbp[i] = 0;
+      state->dr_type_hwbp[i] = 0;
+      state->dr_len_hwbp[i] = 0;
+      state->dr_ref_count_hwbp[i] = 0;
+    }
+}
+
+/* See nat/riscv-linux-hw-point.h.  */
+
+struct riscv_debug_reg_state *
+riscv_get_debug_reg_state (pid_t pid)
+{
+  struct process_info *proc = find_process_pid (pid);
+
+  return &proc->priv->arch_private->debug_reg_state;
+}
+
+/* Implementation of target ops method "supports_z_point_type".  */
+
+bool
+riscv_target::supports_z_point_type (char z_type)
+{
+  switch (z_type)
+    {
+    case Z_PACKET_SW_BP:
+    case Z_PACKET_HW_BP:
+    case Z_PACKET_WRITE_WP:
+    case Z_PACKET_READ_WP:
+    case Z_PACKET_ACCESS_WP:
+      return true;
+    default:
+      return false;
+    }
+}
+
+/* Implementation of linux target ops method "low_insert_point".
+
+   It actually only records the info of the to-be-inserted bp/wp;
+   the actual insertion will happen when threads are resumed.  */
+
+int
+riscv_target::low_insert_point (raw_bkpt_type type, CORE_ADDR addr,
+				int len, raw_breakpoint *bp)
+{
+  int ret;
+  enum target_hw_bp_type targ_type;
+  struct riscv_debug_reg_state *state
+    = riscv_get_debug_reg_state (current_thread->id.pid ());
+
+  if (show_debug_regs)
+    fprintf (stderr, "insert_point on entry (addr=0x%08lx, len=%d)\n",
+	     (unsigned long) addr, len);
+
+  /* Determine the type from the raw breakpoint type.  */
+  targ_type = raw_bkpt_type_to_target_hw_bp_type (type);
+
+  ret = riscv_handle_point (targ_type, addr, len,
+			    1 /* is_insert */,
+			    current_lwp_ptid (), state);
+
+  if (show_debug_regs)
+    riscv_show_debug_reg_state (state, "insert_point", addr, len,
+				targ_type);
+
+  return ret;
+}
+
+/* Implementation of linux target ops method "low_remove_point".
+
+   It actually only records the info of the to-be-removed bp/wp,
+   the actual removal will be done when threads are resumed.  */
+
+int
+riscv_target::low_remove_point (raw_bkpt_type type, CORE_ADDR addr,
+				int len, raw_breakpoint *bp)
+{
+  int ret;
+  enum target_hw_bp_type targ_type;
+  struct riscv_debug_reg_state *state
+    = riscv_get_debug_reg_state (current_thread->id.pid ());
+
+  if (show_debug_regs)
+    fprintf (stderr, "remove_point on entry (addr=0x%08lx, len=%d)\n",
+	     (unsigned long) addr, len);
+
+  /* Determine the type from the raw breakpoint type.  */
+  targ_type = raw_bkpt_type_to_target_hw_bp_type (type);
+
+  ret = riscv_handle_point (targ_type, addr, len,
+			    0 /* is_insert */,
+			    current_lwp_ptid (), state);
+
+  if (show_debug_regs)
+    riscv_show_debug_reg_state (state, "remove_point", addr, len,
+				targ_type);
+
+  return ret;
+}
+
+/* Implementation of linux target ops method "low_stopped_data_addresses".  */
+
+std::vector<CORE_ADDR>
+riscv_target::low_stopped_data_addresses ()
+{
+  siginfo_t siginfo;
+  struct riscv_debug_reg_state *state;
+  int pid = current_thread->id.lwp ();
+
+  /* Get the siginfo.  */
+  if (ptrace (PTRACE_GETSIGINFO, pid, NULL, &siginfo) != 0)
+    return {};
+
+  /* Need to be a hardware breakpoint/watchpoint trap.  */
+  if (siginfo.si_signo != SIGTRAP
+      || (siginfo.si_code & 0xffff) != 0x0004 /* TRAP_HWBKPT */)
+    return {};
+
+  /* Check if the address matches any watched address.  */
+  state = riscv_get_debug_reg_state (current_thread->id.pid ());
+  CORE_ADDR result;
+  if (riscv_stopped_data_address (state, (CORE_ADDR) siginfo.si_addr, &result))
+    return { result };
+
+  return {};
+}
+
+/* Implementation of linux target ops method "low_stopped_by_watchpoint".  */
+
+bool
+riscv_target::low_stopped_by_watchpoint ()
+{
+  return !low_stopped_data_addresses ().empty ();
+}
+
+/* Implementation of linux target ops method "low_new_process".  */
+
+arch_process_info *
+riscv_target::low_new_process ()
+{
+  struct arch_process_info *info = XCNEW (struct arch_process_info);
+
+  riscv_init_debug_reg_state (&info->debug_reg_state);
+
+  return info;
+}
+
+/* Implementation of linux target ops method "low_delete_process".  */
+
+void
+riscv_target::low_delete_process (arch_process_info *info)
+{
+  xfree (info);
+}
+
+void
+riscv_target::low_new_thread (lwp_info *lwp)
+{
+  riscv_linux_new_thread (lwp);
+}
+
+void
+riscv_target::low_delete_thread (arch_lwp_info *arch_lwp)
+{
+  riscv_linux_delete_thread (arch_lwp);
+}
+
+/* Implementation of linux target ops method "low_new_fork".  */
+
+void
+riscv_target::low_new_fork (process_info *parent,
+			      process_info *child)
+{
+  /* These are allocated by linux_add_process.  */
+  gdb_assert (parent->priv != NULL
+	      && parent->priv->arch_private != NULL);
+  gdb_assert (child->priv != NULL
+	      && child->priv->arch_private != NULL);
+
+  /* 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.  The debug registers mirror will become zeroed
+     in the end before detaching the forked off process, thus making
+     this compatible with older Linux kernels too.  */
+
+  *child->priv->arch_private = *parent->priv->arch_private;
+}
+
 /* The linux target ops object.  */
 
 linux_process_target *the_linux_target = &the_riscv_target;
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2 2/2] gdbserver: riscv: Add support for hardware breakpoints/watchpoints
  2026-04-09  2:45 ` [PATCH v2 2/2] gdbserver: " Zane Leung
@ 2026-04-09  4:49   ` liangzhen
  0 siblings, 0 replies; 4+ messages in thread
From: liangzhen @ 2026-04-09  4:49 UTC (permalink / raw)
  To: gdb-patches; +Cc: zhuangqiubin

Note: something forgot to change.

On 4/9/2026 10:45 AM, Zane Leung wrote:
> +/* Implementation of linux target ops method "low_insert_point".
> +
> +   It actually only records the info of the to-be-inserted bp/wp;
> +   the actual insertion will happen when threads are resumed.  */
> +
> +int
> +riscv_target::low_insert_point (raw_bkpt_type type, CORE_ADDR addr,
> +				int len, raw_breakpoint *bp)
> +{
> +  int ret;
> +  enum target_hw_bp_type targ_type;
> +  struct riscv_debug_reg_state *state
> +    = riscv_get_debug_reg_state (current_thread->id.pid ());
> +
> +  if (show_debug_regs)
> +    fprintf (stderr, "insert_point on entry (addr=0x%08lx, len=%d)\n",
> +	     (unsigned long) addr, len);
> +
> +  /* Determine the type from the raw breakpoint type.  */
> +  targ_type = raw_bkpt_type_to_target_hw_bp_type (type);
> +
> +  ret = riscv_handle_point (targ_type, addr, len,
> +			    1 /* is_insert */,
> +			    current_lwp_ptid (), state);
> +
Here should be
ret = riscv_handle_point (targ_type, addr, len,
                                         1 /* is_insert */,
                                          current_lwp_ptid (), state) ? 0 : -1;
> +  if (show_debug_regs)
> +    riscv_show_debug_reg_state (state, "insert_point", addr, len,
> +				targ_type);
> +
> +  return ret;
> +}
> +
> +/* Implementation of linux target ops method "low_remove_point".
> +
> +   It actually only records the info of the to-be-removed bp/wp,
> +   the actual removal will be done when threads are resumed.  */
> +
> +int
> +riscv_target::low_remove_point (raw_bkpt_type type, CORE_ADDR addr,
> +				int len, raw_breakpoint *bp)
> +{
> +  int ret;
> +  enum target_hw_bp_type targ_type;
> +  struct riscv_debug_reg_state *state
> +    = riscv_get_debug_reg_state (current_thread->id.pid ());
> +
> +  if (show_debug_regs)
> +    fprintf (stderr, "remove_point on entry (addr=0x%08lx, len=%d)\n",
> +	     (unsigned long) addr, len);
> +
> +  /* Determine the type from the raw breakpoint type.  */
> +  targ_type = raw_bkpt_type_to_target_hw_bp_type (type);
> +
> +  ret = riscv_handle_point (targ_type, addr, len,
> +			    0 /* is_insert */,
> +			    current_lwp_ptid (), state);
> +
Same above.
> +  if (show_debug_regs)
> +    riscv_show_debug_reg_state (state, "remove_point", addr, len,
> +				targ_type);
> +
> +  return ret;
> +}
> +

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-04-09  4:49 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-09  2:45 [PATCH v2 0/2] RISC-V hardware breakpoint/watchpoint support Zane Leung
2026-04-09  2:45 ` [PATCH v2 1/2] gdb: riscv: Add support for hardware breakpoints/watchpoints Zane Leung
2026-04-09  2:45 ` [PATCH v2 2/2] gdbserver: " Zane Leung
2026-04-09  4:49   ` liangzhen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox