[PATCH] arm64/hw_breakpoint: reject unaligned watchpoints that would truncate BAS
Breno Leitao
leitao at debian.org
Wed Jun 10 07:48:39 PDT 2026
On Wed, Jun 10, 2026 at 01:30:17PM +0100, Will Deacon wrote:
> On Mon, Jun 08, 2026 at 05:14:22AM -0700, Breno Leitao wrote:
> > On Fri, May 29, 2026 at 03:53:58PM +0100, Will Deacon wrote:
> > > and it looks like the aarch64_align_watchpoint() function does try to
> > > spill into multiple watchpoints, so perhaps your patch is ok. I'd
> > > appreciate your opinion, though.
> >
> > It won't, for two independent reasons.
>
> Sorry, not sure I understand you here when you say "it won't". GDB won't
> spill or something else?
>
> > The new -EINVAL is unreachable from GDB; only a raw perf_event_open() passing
> > an unaligned base with an oversized bp_len hits it, which is the bug.
>
> Why isn't it reachable via hw_break_set() => {ptrace_hbp_set_addr(),
> ptrace_hbp_set_ctrl()} ?
Sorry. What I should have said is: GDB handles that -EINVAL. From
gdb/nat/aarch64-linux-hw-point.c:
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"));
}
}
From: https://fossies.org/linux/gdb/gdb/nat/aarch64-linux-hw-point.c
aarch64_downgrade_regs() rounds the BAS up to the nearest legacy
0x01/0x03/0x0f/0xff mask and aligns the base down with
align_down(..., AARCH64_HWP_ALIGNMENT)
The retry then succeeds. So a ptrace NT_ARM_HW_WATCH with an unaligned
base and an oversized BAS will, after this patch, go:
set -> -EINVAL -> downgrade -> set -> ok
> > GDB in fact downgrades on the current kernel independently of this patch, so
> > behaviour is unchanged for it.
>
> That sounds like a bug?
On an unpatched kernel GDB does NOT downgrade for the exact case this patch
fixes, precisely because the kernel returns 0 (with a truncated BAS) instead of
-EINVAL.
So the real behaviour delta this patch introduces for GDB is:
- Before: unaligned-base + oversized-len watchpoint silently watches
fewer bytes than requested. GDB never notices; user sees missed
events.
- After: same request returns -EINVAL, GDB's existing PR-20207
fallback engages, watchpoint is reinstalled with a legacy mask, and every
requested byte is covered (possibly with a few extra).
More information about the linux-arm-kernel
mailing list