From 065a00b3a461463cca766ac6bb33e3be436397bd Mon Sep 17 00:00:00 2001 From: John Baldwin Date: Tue, 22 Mar 2022 12:05:43 -0700 Subject: [PATCH] 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. --- gdb/NEWS | 2 + gdb/aarch64-fbsd-nat.c | 260 ++++++++++++++++++++++++++++++++++++++++- gdb/configure.nat | 3 +- 3 files changed, 263 insertions(+), 2 deletions(-) diff --git a/gdb/NEWS b/gdb/NEWS index 68895e76854..e7f163e8c85 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 e6ca1196139..99e2bf35276 100644 --- a/gdb/aarch64-fbsd-nat.c +++ b/gdb/aarch64-fbsd-nat.c @@ -18,24 +18,60 @@ along with this program. If not, see . */ #include "defs.h" +#include "arch-utils.h" +#include "inferior.h" #include "regcache.h" #include "target.h" +#include "nat/aarch64-hw-point.h" -#include +#include #include +#include #include #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 +#endif + +#ifdef HAVE_DBREG +struct aarch64_fbsd_nat_target final + : public aarch64_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 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) ®, 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 (®, 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) ®, 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 4f5850dd595..d219d6a960c 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) -- 2.30.2