385 lines
10 KiB
C
385 lines
10 KiB
C
/* Native-dependent code for FreeBSD/aarch64.
|
|
|
|
Copyright (C) 2017-2022 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 "defs.h"
|
|
#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 = (aarch64_gdbarch_tdep *) gdbarch_tdep (gdbarch);
|
|
if (tdep->has_tls ())
|
|
{
|
|
const struct regcache_map_entry aarch64_fbsd_tls_regmap[] =
|
|
{
|
|
{ 1, tdep->tls_regnum, 8 },
|
|
{ 0 }
|
|
};
|
|
|
|
const struct regset aarch64_fbsd_tls_regset =
|
|
{
|
|
aarch64_fbsd_tls_regmap,
|
|
regcache_supply_regset, regcache_collect_regset
|
|
};
|
|
|
|
fetch_regset<uint64_t> (regcache, regnum, NT_ARM_TLS,
|
|
&aarch64_fbsd_tls_regset);
|
|
}
|
|
}
|
|
|
|
/* 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 = (aarch64_gdbarch_tdep *) gdbarch_tdep (gdbarch);
|
|
if (tdep->has_tls ())
|
|
{
|
|
const struct regcache_map_entry aarch64_fbsd_tls_regmap[] =
|
|
{
|
|
{ 1, tdep->tls_regnum, 8 },
|
|
{ 0 }
|
|
};
|
|
|
|
const struct regset aarch64_fbsd_tls_regset =
|
|
{
|
|
aarch64_fbsd_tls_regmap,
|
|
regcache_supply_regset, regcache_collect_regset
|
|
};
|
|
|
|
store_regset<uint64_t> (regcache, regnum, NT_ARM_TLS,
|
|
&aarch64_fbsd_tls_regset);
|
|
}
|
|
}
|
|
|
|
/* Implement the target read_description method. */
|
|
|
|
const struct target_desc *
|
|
aarch64_fbsd_nat_target::read_description ()
|
|
{
|
|
aarch64_features features;
|
|
features.tls = have_regset (inferior_ptid, NT_ARM_TLS) != 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 ()
|
|
{
|
|
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);
|
|
}
|