aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Baldwin <jhb@FreeBSD.org>2022-03-22 12:05:43 -0700
committerJohn Baldwin <jhb@FreeBSD.org>2022-03-22 12:05:43 -0700
commit065a00b3a461463cca766ac6bb33e3be436397bd (patch)
treebb7761b78a3d276940938ce646aecc9b3d46bf0e
parenta3627b54280ba306766f2689fb35442f24c4c313 (diff)
downloadgdb-065a00b3a461463cca766ac6bb33e3be436397bd.zip
gdb-065a00b3a461463cca766ac6bb33e3be436397bd.tar.gz
gdb-065a00b3a461463cca766ac6bb33e3be436397bd.tar.bz2
Add support for hardware breakpoints/watchpoints on FreeBSD/Aarch64.
This shares aarch64-nat.c and nat/aarch64-hw-point.c with the Linux native target. Since FreeBSD writes all of the debug registers in one ptrace op, use an unordered_set<> to track the "dirty" state for threads rather than bitmasks of modified registers.
-rw-r--r--gdb/NEWS2
-rw-r--r--gdb/aarch64-fbsd-nat.c260
-rw-r--r--gdb/configure.nat3
3 files changed, 263 insertions, 2 deletions
diff --git a/gdb/NEWS b/gdb/NEWS
index 68895e7..e7f163e 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,8 @@
*** Changes since GDB 12
+* GDB now supports hardware watchpoints on FreeBSD/Aarch64.
+
* Python API
** New function gdb.format_address(ADDRESS, PROGSPACE, ARCHITECTURE),
diff --git a/gdb/aarch64-fbsd-nat.c b/gdb/aarch64-fbsd-nat.c
index e6ca119..99e2bf3 100644
--- a/gdb/aarch64-fbsd-nat.c
+++ b/gdb/aarch64-fbsd-nat.c
@@ -18,24 +18,60 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "defs.h"
+#include "arch-utils.h"
+#include "inferior.h"
#include "regcache.h"
#include "target.h"
+#include "nat/aarch64-hw-point.h"
-#include <sys/types.h>
+#include <sys/param.h>
#include <sys/ptrace.h>
+#include <machine/armreg.h>
#include <machine/reg.h>
#include "fbsd-nat.h"
#include "aarch64-fbsd-tdep.h"
+#include "aarch64-nat.h"
#include "inf-ptrace.h"
+#if __FreeBSD_version >= 1400005
+#define HAVE_DBREG
+
+#include <unordered_set>
+#endif
+
+#ifdef HAVE_DBREG
+struct aarch64_fbsd_nat_target final
+ : public aarch64_nat_target<fbsd_nat_target>
+#else
struct aarch64_fbsd_nat_target final : public fbsd_nat_target
+#endif
{
void fetch_registers (struct regcache *, int) override;
void store_registers (struct regcache *, int) override;
+
+#ifdef HAVE_DBREG
+ /* Hardware breakpoints and watchpoints. */
+ bool stopped_by_watchpoint () override;
+ bool stopped_data_address (CORE_ADDR *) override;
+ bool stopped_by_hw_breakpoint () override;
+ bool supports_stopped_by_hw_breakpoint () override;
+
+ void post_startup_inferior (ptid_t) override;
+ void post_attach (int pid) override;
+
+ void low_new_fork (ptid_t parent, pid_t child) override;
+ void low_delete_thread (thread_info *) override;
+ void low_prepare_to_resume (thread_info *) override;
+
+private:
+ void probe_debug_regs (int pid);
+ static bool debug_regs_probed;
+#endif
};
static aarch64_fbsd_nat_target the_aarch64_fbsd_nat_target;
+bool aarch64_fbsd_nat_target::debug_regs_probed;
/* Fetch register REGNUM from the inferior. If REGNUM is -1, do this
for all registers. */
@@ -63,9 +99,231 @@ aarch64_fbsd_nat_target::store_registers (struct regcache *regcache,
PT_SETFPREGS, &aarch64_fbsd_fpregset);
}
+#ifdef HAVE_DBREG
+/* Set of threads which need to update debug registers on next resume. */
+
+static std::unordered_set<lwpid_t> aarch64_debug_pending_threads;
+
+/* Implement the "stopped_data_address" target_ops method. */
+
+bool
+aarch64_fbsd_nat_target::stopped_data_address (CORE_ADDR *addr_p)
+{
+ siginfo_t siginfo;
+ struct aarch64_debug_reg_state *state;
+
+ if (!fbsd_nat_get_siginfo (inferior_ptid, &siginfo))
+ return false;
+
+ /* This must be a hardware breakpoint. */
+ if (siginfo.si_signo != SIGTRAP
+ || siginfo.si_code != TRAP_TRACE
+ || siginfo.si_trapno != EXCP_WATCHPT_EL0)
+ return false;
+
+ const CORE_ADDR addr_trap = (CORE_ADDR) siginfo.si_addr;
+
+ /* Check if the address matches any watched address. */
+ state = aarch64_get_debug_reg_state (inferior_ptid.pid ());
+ return aarch64_stopped_data_address (state, addr_trap, addr_p);
+}
+
+/* Implement the "stopped_by_watchpoint" target_ops method. */
+
+bool
+aarch64_fbsd_nat_target::stopped_by_watchpoint ()
+{
+ CORE_ADDR addr;
+
+ return stopped_data_address (&addr);
+}
+
+/* Implement the "stopped_by_hw_breakpoint" target_ops method. */
+
+bool
+aarch64_fbsd_nat_target::stopped_by_hw_breakpoint ()
+{
+ siginfo_t siginfo;
+ struct aarch64_debug_reg_state *state;
+
+ if (!fbsd_nat_get_siginfo (inferior_ptid, &siginfo))
+ return false;
+
+ /* This must be a hardware breakpoint. */
+ if (siginfo.si_signo != SIGTRAP
+ || siginfo.si_code != TRAP_TRACE
+ || siginfo.si_trapno != EXCP_WATCHPT_EL0)
+ return false;
+
+ return !stopped_by_watchpoint();
+}
+
+/* Implement the "supports_stopped_by_hw_breakpoint" target_ops method. */
+
+bool
+aarch64_fbsd_nat_target::supports_stopped_by_hw_breakpoint ()
+{
+ return true;
+}
+
+/* Fetch the hardware debug register capability information. */
+
+void
+aarch64_fbsd_nat_target::probe_debug_regs (int pid)
+{
+ if (!debug_regs_probed)
+ {
+ struct dbreg reg;
+
+ debug_regs_probed = true;
+ aarch64_num_bp_regs = 0;
+ aarch64_num_wp_regs = 0;
+
+ if (ptrace(PT_GETDBREGS, pid, (PTRACE_TYPE_ARG3) &reg, 0) == 0)
+ {
+ switch (reg.db_debug_ver)
+ {
+ case AARCH64_DEBUG_ARCH_V8:
+ case AARCH64_DEBUG_ARCH_V8_1:
+ case AARCH64_DEBUG_ARCH_V8_2:
+ case AARCH64_DEBUG_ARCH_V8_4:
+ break;
+ default:
+ return;
+ }
+
+ aarch64_num_bp_regs = reg.db_nbkpts;
+ 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;
+ }
+ aarch64_num_wp_regs = reg.db_nwtpts;
+ 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;
+ }
+ }
+ }
+}
+
+/* Implement the virtual inf_ptrace_target::post_startup_inferior method. */
+
+void
+aarch64_fbsd_nat_target::post_startup_inferior (ptid_t ptid)
+{
+ aarch64_remove_debug_reg_state (ptid.pid ());
+ probe_debug_regs (ptid.pid ());
+ fbsd_nat_target::post_startup_inferior (ptid);
+}
+
+/* Implement the "post_attach" target_ops method. */
+
+void
+aarch64_fbsd_nat_target::post_attach (int pid)
+{
+ aarch64_remove_debug_reg_state (pid);
+ probe_debug_regs (pid);
+ fbsd_nat_target::post_attach (pid);
+}
+
+/* Implement the virtual fbsd_nat_target::low_new_fork method. */
+
+void
+aarch64_fbsd_nat_target::low_new_fork (ptid_t parent, pid_t child)
+{
+ struct aarch64_debug_reg_state *parent_state, *child_state;
+
+ /* If there is no parent state, no watchpoints nor breakpoints have
+ been set, so there is nothing to do. */
+ parent_state = aarch64_lookup_debug_reg_state (parent.pid ());
+ if (parent_state == nullptr)
+ return;
+
+ /* The kernel clears debug registers in the new child process after
+ fork, but 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. */
+
+ child_state = aarch64_get_debug_reg_state (child);
+ *child_state = *parent_state;
+}
+
+/* Mark debug register state "dirty" for all threads belonging to the
+ current inferior. */
+
+void
+aarch64_notify_debug_reg_change (ptid_t ptid,
+ int is_watchpoint, unsigned int idx)
+{
+ for (thread_info *tp : current_inferior ()->non_exited_threads ())
+ {
+ if (tp->ptid.lwp_p ())
+ aarch64_debug_pending_threads.emplace (tp->ptid.lwp ());
+ }
+}
+
+/* Implement the virtual fbsd_nat_target::low_delete_thread method. */
+
+void
+aarch64_fbsd_nat_target::low_delete_thread (thread_info *tp)
+{
+ gdb_assert(tp->ptid.lwp_p ());
+ aarch64_debug_pending_threads.erase (tp->ptid.lwp ());
+}
+
+/* Implement the virtual fbsd_nat_target::low_prepare_to_resume method. */
+
+void
+aarch64_fbsd_nat_target::low_prepare_to_resume (thread_info *tp)
+{
+ gdb_assert(tp->ptid.lwp_p ());
+
+ if (aarch64_debug_pending_threads.erase (tp->ptid.lwp ()) == 0)
+ return;
+
+ struct aarch64_debug_reg_state *state =
+ aarch64_lookup_debug_reg_state (tp->ptid.pid ());
+ gdb_assert(state != nullptr);
+
+ struct dbreg reg;
+ memset (&reg, 0, sizeof(reg));
+ for (int i = 0; i < aarch64_num_bp_regs; i++)
+ {
+ reg.db_breakregs[i].dbr_addr = state->dr_addr_bp[i];
+ reg.db_breakregs[i].dbr_ctrl = state->dr_ctrl_bp[i];
+ }
+ for (int i = 0; i < aarch64_num_wp_regs; i++)
+ {
+ reg.db_watchregs[i].dbw_addr = state->dr_addr_wp[i];
+ reg.db_watchregs[i].dbw_ctrl = state->dr_ctrl_wp[i];
+ }
+ if (ptrace(PT_SETDBREGS, tp->ptid.lwp (), (PTRACE_TYPE_ARG3) &reg, 0) != 0)
+ error (_("Failed to set hardware debug registers"));
+}
+#else
+/* A stub that should never be called. */
+void
+aarch64_notify_debug_reg_change (ptid_t ptid,
+ int is_watchpoint, unsigned int idx)
+{
+ gdb_assert (true);
+}
+#endif
+
void _initialize_aarch64_fbsd_nat ();
void
_initialize_aarch64_fbsd_nat ()
{
+#ifdef HAVE_DBREG
+ aarch64_initialize_hw_point ();
+#endif
add_inf_child_target (&the_aarch64_fbsd_nat_target);
}
diff --git a/gdb/configure.nat b/gdb/configure.nat
index 4f5850d..d219d6a 100644
--- a/gdb/configure.nat
+++ b/gdb/configure.nat
@@ -154,7 +154,8 @@ case ${gdb_host} in
case ${gdb_host_cpu} in
aarch64)
# Host: FreeBSD/aarch64
- NATDEPFILES="${NATDEPFILES} aarch64-fbsd-nat.o"
+ NATDEPFILES="${NATDEPFILES} aarch64-nat.o \
+ nat/aarch64-hw-point.o aarch64-fbsd-nat.o"
LOADLIBES=
;;
arm)