Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
From: slipher
Date: Sun Jun 21 2026 - 17:53:43 EST
On Sunday, June 21st, 2026 at 3:19 PM, Russell King (Oracle) <linux@xxxxxxxxxxxxxxx> wrote:
> On Sun, Jun 21, 2026 at 07:15:27PM +0000, slipher wrote:
> > Consider the C program for 32-bit ARM architectures:
> >
> > int main() {
> > __asm__ __volatile__ ("BKPT");
> > return 0;
> > }
> >
> >
> > Expected behavior is that this raises SIGTRAP. Since Linux 6.10 this no
> > longer happens; instead execution perpetually resumes at the same
> > instruction, using 100% of CPU. It does not matter whether GDB is
> > attached. I have tested with an armv7l CPU, but I imagine any other
> > variants with the BKPT instruction would be equally affected.
>
> Looking at the code, I doubt this has ever cleanly raised SIGTRAP (can
> you check whether it does in kernels without c3f89986fde please?)
>
> What I suspect instead is you get an "Unhandled ... abort" instead
> and the program forcefully killed as hw_breakpoint_pending() would
> have ARM_DSCR_MOE(dscr) == 3, and the switch() would set ret = 1.
> That triggers the fault handlers in arch/arm/mm/fault.c to
> complain bitterly, and forced a SIGTRAP to the program to kill it
> off. No resumption from an unhandled trap is expected.
I have tested with a 6.6 kernel. All of that is correct, as detailed in
the aforementioned blog post, except the last sentence. The switch does
set ret = 1, thereby passing on the exception. The kernel complains,
with such lines in dmesg output:
[ 1547.164526] Unhandled prefetch abort: breakpoint debug exception (0x222) at 0x0001051c
Indeed, it is not clean or efficient; the blog
(https://www.jwhitham.org/2015/04/the-mystery-of-fifteen-millisecond.html)
even has a proposed patch to improve the performance when raising
SIGTRAP. However, it is possible to catch the signal, and even resume
with something like this:
#include <ucontext.h>
#include <signal.h>
#include <stdio.h>
void handl(int a, siginfo_t *b, void *uc) {
puts("caught SIGTRAP");
((ucontext_t*)uc)->uc_mcontext.arm_pc += 4;
}
int main() {
struct sigaction s;
s.sa_flags = SA_SIGINFO;
s.sa_sigaction = handl;
sigemptyset(&s.sa_mask);
sigaction(SIGTRAP, &s, 0);
puts("start");
__asm__ __volatile__("BKPT");
puts("resumed");
return 0;
}
Re-testing, I realized there is a huge caveat: SIGTRAP is *not* raised
when running under a debugger! If GDB is attached, either of the C
programs above will repeatedly resume at the faulting instruction on
Linux 6.6, just as they will with the latest kernels. So the regression
only affects the perhaps-obscure case of using BKPT without any
intention of attaching a debugger, unless that worked in even-earlier
versions of Linux.