mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-27 04:52:05 +08:00
4bd817e71e
Move non-Linux-specific support for hardware break/watchpoints from nat/aarch64-linux-hw-point.c to nat/aarch64-hw-point.c. Changes beyond a simple split of the code are: - aarch64_linux_region_ok_for_watchpoint and aarch64_linux_any_set_debug_regs_state renamed to drop linux_ as they are not platform specific. - Platforms must implement the aarch64_notify_debug_reg_change function which is invoked from the platform-independent code when a debug register changes for a given debug register state. This does not use the indirection of a 'low' structure as is done for x86. - The handling for kernel_supports_any_contiguous_range is not pristine. For non-Linux it is simply defined to true. Some uses of this could perhaps be implemented as new 'low' routines for the various places that check it instead? - Pass down ptid into aarch64_handle_breakpoint and aarch64_handle_watchpoint rather than using current_lwp_ptid which is only defined on Linux. In addition, pass the ptid on to aarch64_notify_debug_reg_change instead of the unused state argument.
291 lines
8.9 KiB
C
291 lines
8.9 KiB
C
/* Copyright (C) 2009-2022 Free Software Foundation, Inc.
|
|
Contributed by ARM Ltd.
|
|
|
|
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/common-defs.h"
|
|
#include "gdbsupport/break-common.h"
|
|
#include "gdbsupport/common-regcache.h"
|
|
#include "nat/linux-nat.h"
|
|
#include "aarch64-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.h>
|
|
|
|
/* See aarch64-linux-hw-point.h */
|
|
|
|
bool kernel_supports_any_contiguous_range = true;
|
|
|
|
/* Helper for aarch64_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
|
|
debug_reg_change_callback (struct lwp_info *lwp, int is_watchpoint,
|
|
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 ("debug_reg_change_callback: \n\tOn entry:\n");
|
|
debug_printf ("\ttid%d, dr_changed_bp=0x%s, "
|
|
"dr_changed_wp=0x%s\n", tid,
|
|
phex (info->dr_changed_bp, 8),
|
|
phex (info->dr_changed_wp, 8));
|
|
}
|
|
|
|
dr_changed_ptr = is_watchpoint ? &info->dr_changed_wp
|
|
: &info->dr_changed_bp;
|
|
dr_changed = *dr_changed_ptr;
|
|
|
|
gdb_assert (idx >= 0
|
|
&& (idx <= (is_watchpoint ? aarch64_num_wp_regs
|
|
: aarch64_num_bp_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_bp=0x%s, "
|
|
"dr_changed_wp=0x%s\n", tid,
|
|
phex (info->dr_changed_bp, 8),
|
|
phex (info->dr_changed_wp, 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
|
|
aarch64_notify_debug_reg_change (ptid_t ptid,
|
|
int is_watchpoint, unsigned int idx)
|
|
{
|
|
ptid_t pid_ptid = ptid_t (ptid.pid ());
|
|
|
|
iterate_over_lwps (pid_ptid, [=] (struct lwp_info *info)
|
|
{
|
|
return debug_reg_change_callback (info,
|
|
is_watchpoint,
|
|
idx);
|
|
});
|
|
}
|
|
|
|
/* Reconfigure STATE to be compatible with Linux kernels with the PR
|
|
external/20207 bug. This is called when
|
|
KERNEL_SUPPORTS_ANY_CONTIGUOUS_RANGE transitions to false. Note we
|
|
don't try to support combining watchpoints with matching (and thus
|
|
shared) masks, as it's too late when we get here. On buggy
|
|
kernels, GDB will try to first setup the perfect matching ranges,
|
|
which will run out of registers before this function can merge
|
|
them. It doesn't look like worth the effort to improve that, given
|
|
eventually buggy kernels will be phased out. */
|
|
|
|
static void
|
|
aarch64_downgrade_regs (struct aarch64_debug_reg_state *state)
|
|
{
|
|
for (int i = 0; i < aarch64_num_wp_regs; ++i)
|
|
if ((state->dr_ctrl_wp[i] & 1) != 0)
|
|
{
|
|
gdb_assert (state->dr_ref_count_wp[i] != 0);
|
|
uint8_t mask_orig = (state->dr_ctrl_wp[i] >> 5) & 0xff;
|
|
gdb_assert (mask_orig != 0);
|
|
static const uint8_t old_valid[] = { 0x01, 0x03, 0x0f, 0xff };
|
|
uint8_t mask = 0;
|
|
for (const uint8_t old_mask : old_valid)
|
|
if (mask_orig <= old_mask)
|
|
{
|
|
mask = old_mask;
|
|
break;
|
|
}
|
|
gdb_assert (mask != 0);
|
|
|
|
/* No update needed for this watchpoint? */
|
|
if (mask == mask_orig)
|
|
continue;
|
|
state->dr_ctrl_wp[i] |= mask << 5;
|
|
state->dr_addr_wp[i]
|
|
= align_down (state->dr_addr_wp[i], AARCH64_HWP_ALIGNMENT);
|
|
|
|
/* Try to match duplicate entries. */
|
|
for (int j = 0; j < i; ++j)
|
|
if ((state->dr_ctrl_wp[j] & 1) != 0
|
|
&& state->dr_addr_wp[j] == state->dr_addr_wp[i]
|
|
&& state->dr_addr_orig_wp[j] == state->dr_addr_orig_wp[i]
|
|
&& state->dr_ctrl_wp[j] == state->dr_ctrl_wp[i])
|
|
{
|
|
state->dr_ref_count_wp[j] += state->dr_ref_count_wp[i];
|
|
state->dr_ref_count_wp[i] = 0;
|
|
state->dr_addr_wp[i] = 0;
|
|
state->dr_addr_orig_wp[i] = 0;
|
|
state->dr_ctrl_wp[i] &= ~1;
|
|
break;
|
|
}
|
|
|
|
aarch64_notify_debug_reg_change (current_lwp_ptid (),
|
|
1 /* is_watchpoint */, i);
|
|
}
|
|
}
|
|
|
|
/* Call ptrace to set the thread TID's hardware breakpoint/watchpoint
|
|
registers with data from *STATE. */
|
|
|
|
void
|
|
aarch64_linux_set_debug_regs (struct aarch64_debug_reg_state *state,
|
|
int tid, int watchpoint)
|
|
{
|
|
int i, count;
|
|
struct iovec iov;
|
|
struct user_hwdebug_state regs;
|
|
const CORE_ADDR *addr;
|
|
const unsigned int *ctrl;
|
|
|
|
memset (®s, 0, sizeof (regs));
|
|
iov.iov_base = ®s;
|
|
count = watchpoint ? aarch64_num_wp_regs : aarch64_num_bp_regs;
|
|
addr = watchpoint ? state->dr_addr_wp : state->dr_addr_bp;
|
|
ctrl = watchpoint ? state->dr_ctrl_wp : state->dr_ctrl_bp;
|
|
if (count == 0)
|
|
return;
|
|
iov.iov_len = (offsetof (struct user_hwdebug_state, dbg_regs)
|
|
+ count * sizeof (regs.dbg_regs[0]));
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
regs.dbg_regs[i].addr = addr[i];
|
|
regs.dbg_regs[i].ctrl = ctrl[i];
|
|
}
|
|
|
|
if (ptrace (PTRACE_SETREGSET, tid,
|
|
watchpoint ? NT_ARM_HW_WATCH : NT_ARM_HW_BREAK,
|
|
(void *) &iov))
|
|
{
|
|
/* Handle Linux kernels with the PR external/20207 bug. */
|
|
if (watchpoint && errno == EINVAL
|
|
&& kernel_supports_any_contiguous_range)
|
|
{
|
|
kernel_supports_any_contiguous_range = false;
|
|
aarch64_downgrade_regs (state);
|
|
aarch64_linux_set_debug_regs (state, tid, watchpoint);
|
|
return;
|
|
}
|
|
error (_("Unexpected error setting hardware debug registers"));
|
|
}
|
|
}
|
|
|
|
/* Return true if debug arch level is compatible for hw watchpoints
|
|
and breakpoints. */
|
|
|
|
static bool
|
|
compatible_debug_arch (unsigned int debug_arch)
|
|
{
|
|
if (debug_arch == AARCH64_DEBUG_ARCH_V8)
|
|
return true;
|
|
if (debug_arch == AARCH64_DEBUG_ARCH_V8_1)
|
|
return true;
|
|
if (debug_arch == AARCH64_DEBUG_ARCH_V8_2)
|
|
return true;
|
|
if (debug_arch == AARCH64_DEBUG_ARCH_V8_4)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Get the hardware debug register capacity information from the
|
|
process represented by TID. */
|
|
|
|
void
|
|
aarch64_linux_get_debug_reg_capacity (int tid)
|
|
{
|
|
struct iovec iov;
|
|
struct user_hwdebug_state dreg_state;
|
|
|
|
iov.iov_base = &dreg_state;
|
|
iov.iov_len = sizeof (dreg_state);
|
|
|
|
/* Get hardware watchpoint register info. */
|
|
if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_HW_WATCH, &iov) == 0
|
|
&& compatible_debug_arch (AARCH64_DEBUG_ARCH (dreg_state.dbg_info)))
|
|
{
|
|
aarch64_num_wp_regs = AARCH64_DEBUG_NUM_SLOTS (dreg_state.dbg_info);
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
warning (_("Unable to determine the number of hardware watchpoints"
|
|
" available."));
|
|
aarch64_num_wp_regs = 0;
|
|
}
|
|
|
|
/* Get hardware breakpoint register info. */
|
|
if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_HW_BREAK, &iov) == 0
|
|
&& compatible_debug_arch (AARCH64_DEBUG_ARCH (dreg_state.dbg_info)))
|
|
{
|
|
aarch64_num_bp_regs = AARCH64_DEBUG_NUM_SLOTS (dreg_state.dbg_info);
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
warning (_("Unable to determine the number of hardware breakpoints"
|
|
" available."));
|
|
aarch64_num_bp_regs = 0;
|
|
}
|
|
}
|