| /* Native-dependent code for FreeBSD/aarch64. |
| |
| Copyright (C) 2017-2024 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 "arch-utils.h" |
| #include "inferior.h" |
| #include "regcache.h" |
| #include "target.h" |
| #include "nat/aarch64-hw-point.h" |
| |
| #include "elf/common.h" |
| |
| #include <sys/param.h> |
| #include <sys/ptrace.h> |
| #include <machine/armreg.h> |
| #include <machine/reg.h> |
| |
| #include "fbsd-nat.h" |
| #include "aarch64-tdep.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; |
| |
| const struct target_desc *read_description () 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; |
| |
| /* Fetch register REGNUM from the inferior. If REGNUM is -1, do this |
| for all registers. */ |
| |
| void |
| aarch64_fbsd_nat_target::fetch_registers (struct regcache *regcache, |
| int regnum) |
| { |
| fetch_register_set<struct reg> (regcache, regnum, PT_GETREGS, |
| &aarch64_fbsd_gregset); |
| fetch_register_set<struct fpreg> (regcache, regnum, PT_GETFPREGS, |
| &aarch64_fbsd_fpregset); |
| |
| gdbarch *gdbarch = regcache->arch (); |
| aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch); |
| if (tdep->has_tls ()) |
| fetch_regset<uint64_t> (regcache, regnum, NT_ARM_TLS, |
| &aarch64_fbsd_tls_regset, tdep->tls_regnum_base); |
| } |
| |
| /* Store register REGNUM back into the inferior. If REGNUM is -1, do |
| this for all registers. */ |
| |
| void |
| aarch64_fbsd_nat_target::store_registers (struct regcache *regcache, |
| int regnum) |
| { |
| store_register_set<struct reg> (regcache, regnum, PT_GETREGS, PT_SETREGS, |
| &aarch64_fbsd_gregset); |
| store_register_set<struct fpreg> (regcache, regnum, PT_GETFPREGS, |
| PT_SETFPREGS, &aarch64_fbsd_fpregset); |
| |
| gdbarch *gdbarch = regcache->arch (); |
| aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch); |
| if (tdep->has_tls ()) |
| store_regset<uint64_t> (regcache, regnum, NT_ARM_TLS, |
| &aarch64_fbsd_tls_regset, tdep->tls_regnum_base); |
| } |
| |
| /* Implement the target read_description method. */ |
| |
| const struct target_desc * |
| aarch64_fbsd_nat_target::read_description () |
| { |
| if (inferior_ptid == null_ptid) |
| return this->beneath ()->read_description (); |
| |
| aarch64_features features; |
| features.tls = have_regset (inferior_ptid, NT_ARM_TLS)? 1 : 0; |
| return aarch64_read_description (features); |
| } |
| |
| #ifdef HAVE_DBREG |
| bool aarch64_fbsd_nat_target::debug_regs_probed; |
| |
| /* 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 () |
| { |
| return stopped_data_address (nullptr); |
| } |
| |
| /* 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: |
| case AARCH64_DEBUG_ARCH_V8_8: |
| case AARCH64_DEBUG_ARCH_V8_9: |
| 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); |
| } |