* [REGRESSION] 32-bit ARM's BKPT instruction no longer works
@ 2026-06-21 19:15 slipher
2026-06-21 20:19 ` Russell King (Oracle)
2026-06-26 12:53 ` Linus Walleij
0 siblings, 2 replies; 15+ messages in thread
From: slipher @ 2026-06-21 19:15 UTC (permalink / raw)
To: linux-kernel@vger.kernel.org
Cc: stable@vger.kernel.org, regressions@lists.linux.dev,
linus.walleij@linaro.org, rmk+kernel@armlinux.org.uk
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.
I believe the culprit to be commit
c3f89986fde7bb9ccc86a901bf28e1f7d69fc3b3 "ARM: 9391/2: hw_breakpoint:
Handle CFI breakpoints". The commit defines the method-of-entry code 3
as "ARM_ENTRY_CFI_BREAKPOINT", but this is the code used for any BKPT
instruction - see
https://developer.arm.com/documentation/ddi0379/a/Debug-Register-Reference/Control-and-status-registers/Debug-Status-and-Control-Register--DSCR-?lang=en
"Method of Debug Entry (MOE), bits [5:2]". If the CFI option is disabled
in the kernel config, hw_breakpoint_pending() returns 0 indicating the
breakpoint was handled, but takes no action. So breakpoints cannot be
used by user-space code, regardless of how CONFIG_CFI is set. The blog
post
https://www.jwhitham.org/2015/04/the-mystery-of-fifteen-millisecond.html
gives a nice overview of the control flow in older, working kernels.
The following Systemtap script can be used to demonstrate that the
ARM_ENTRY_CFI_BREAKPOINT path is used, when running the above C program.
probe kernel.function("hw_breakpoint_pending").call {
printf("hw_breakpoint_pending entered\n");
}
probe kernel.function("hw_breakpoint_pending").return {
printf("hw_breakpoint_pending returned %d\n", $return);
}
// these are not called
probe kernel.function("watchpoint_handler") {
printf("watchpoint_handler\n");
}
probe kernel.function("breakpoint_handler") {
printf("breakpoint_handler\n");
}
Tested on a 7.0.12 kernel, the output is:
hw_breakpoint_pending entered
hw_breakpoint_pending returned 0
hw_breakpoint_pending entered
hw_breakpoint_pending returned 0
hw_breakpoint_pending entered
hw_breakpoint_pending returned 0
...
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-21 19:15 [REGRESSION] 32-bit ARM's BKPT instruction no longer works slipher
@ 2026-06-21 20:19 ` Russell King (Oracle)
2026-06-21 21:53 ` slipher
2026-06-26 12:53 ` Linus Walleij
1 sibling, 1 reply; 15+ messages in thread
From: Russell King (Oracle) @ 2026-06-21 20:19 UTC (permalink / raw)
To: slipher
Cc: linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev, linus.walleij@linaro.org
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.
BKPT was added in later versions of the architecture. In order for
32-bit ARM to have functional breakpoints with older versions of the
architecture, we had to invent our own breakpoint instruction using
what was available in the reference manuals of the time - and this
needed to be maintained in a forwards compatible manner. Sadly, Arm
Ltd were late to the party.
The ARM mode breakpoint instructions that were chosen were 0xe7f001f0
and 0xde01 for Thumb. These cause a SIGTRAP with a TRAP_BRKPT code.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-21 20:19 ` Russell King (Oracle)
@ 2026-06-21 21:53 ` slipher
2026-06-21 22:41 ` Russell King (Oracle)
0 siblings, 1 reply; 15+ messages in thread
From: slipher @ 2026-06-21 21:53 UTC (permalink / raw)
To: Russell King (Oracle)
Cc: linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev
On Sunday, June 21st, 2026 at 3:19 PM, Russell King (Oracle) <linux@armlinux.org.uk> 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.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-21 21:53 ` slipher
@ 2026-06-21 22:41 ` Russell King (Oracle)
2026-06-21 23:24 ` Russell King
0 siblings, 1 reply; 15+ messages in thread
From: Russell King (Oracle) @ 2026-06-21 22:41 UTC (permalink / raw)
To: slipher
Cc: linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev
On Sun, Jun 21, 2026 at 09:53:17PM +0000, slipher wrote:
>
> On Sunday, June 21st, 2026 at 3:19 PM, Russell King (Oracle) <linux@armlinux.org.uk> 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
This message is printed at Alert level. It's just not supposed to
happen, and if anyone sees it, it means someone cocked up in the kernel
and didn't provide the code to handle a fault that can be generated.
In these situations, the kernel's response is to try and keep the system
running by delivering a signal that should result in the process being
terminated. In this case, the hardware breakpoint code tells the
generic code to deliver a SIGTRAP / TRAP_HWBKPT, and this will be
delivered by force_sig_fault() after the noisy kernel message has been
produced.
force_sig_fault() will unblock the signal and set the handler to
default if it was blocked or ignored. The default action for SIGTRAP
should be to generate a coredump and terminate the program.
> 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.
... and while it's repeatedly raising the same fault, it's flooding the
kernel console with Alert level messages telling you the fault hasn't
been handled even on older kernels... yet you seem to be under the
impression that this is supposed to work.
You are testing something that has never been tested before, and are
hitting behaviour that isn't _supposed_ to be clean.
That said, the change of behaviour is wrong. If
hw_breakpoint_cfi_handler() doesn't understand the reason its been
called, it should cause the old behaviour (where the alert message
is printed) to be actioned.
The issue over whether BKPT should correctly raise a SIGTRAP that
is appropriately handled is an entirely separate issue, which I
would regard as a feature request rather than a regression.
Let me put it slightly differently. BKPT in userspace hasn't been
supported by the kernel, and the behaviour you've seen from the
kernel is incidental to the kernel's abort handling - it is not
by design.
Architecturally, BKPT is used with JTAG debuggers, causing the
processor to enter debug mode so a JTAG debugger can do its
stuff. There was some discussion ten years ago whether LLVM
should use BKPT for setting software breakpoints, and it seems
they decided against it because of interfering with JTAG
debuggers. See https://reviews.llvm.org/D16853?id=46899#347119
Also see the linked discussion from that post, where using BKPT
was discussed with gdb. Basically, if a hardware JTAG debugger is
connected, BKPT goes straight to the hardware debugger not the
kernel. However, note that the sourceware discussion is talking
about Thumb2 rather than ARM, but the same will apply there.
In essence, the decision was to stick with the UDF instructions
for software breakpoints handled by the kernel, and leave BKPT
for hardware JTAG debuggers. Consequently, explicitly executing
BKPT without a hardware JTAG debugger is unexpected, the results
of which are not guaranteed.
Indeed, under older architectures, you'll get an undefined
instruction exception and the program killed by a SIGILL not a
SIGTRAP, because BKPT isn't architecturally defined there.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-21 22:41 ` Russell King (Oracle)
@ 2026-06-21 23:24 ` Russell King
2026-06-23 2:05 ` slipher
0 siblings, 1 reply; 15+ messages in thread
From: Russell King @ 2026-06-21 23:24 UTC (permalink / raw)
To: slipher
Cc: linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev
On Sun, Jun 21, 2026 at 11:41:03PM +0100, Russell King (Oracle) wrote:
> On Sun, Jun 21, 2026 at 09:53:17PM +0000, slipher wrote:
> >
> > On Sunday, June 21st, 2026 at 3:19 PM, Russell King (Oracle) <linux@armlinux.org.uk> 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
>
> This message is printed at Alert level. It's just not supposed to
> happen, and if anyone sees it, it means someone cocked up in the kernel
> and didn't provide the code to handle a fault that can be generated.
>
> In these situations, the kernel's response is to try and keep the system
> running by delivering a signal that should result in the process being
> terminated. In this case, the hardware breakpoint code tells the
> generic code to deliver a SIGTRAP / TRAP_HWBKPT, and this will be
> delivered by force_sig_fault() after the noisy kernel message has been
> produced.
>
> force_sig_fault() will unblock the signal and set the handler to
> default if it was blocked or ignored. The default action for SIGTRAP
> should be to generate a coredump and terminate the program.
>
> > 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.
>
> ... and while it's repeatedly raising the same fault, it's flooding the
> kernel console with Alert level messages telling you the fault hasn't
> been handled even on older kernels... yet you seem to be under the
> impression that this is supposed to work.
>
> You are testing something that has never been tested before, and are
> hitting behaviour that isn't _supposed_ to be clean.
>
> That said, the change of behaviour is wrong. If
> hw_breakpoint_cfi_handler() doesn't understand the reason its been
> called, it should cause the old behaviour (where the alert message
> is printed) to be actioned.
>
> The issue over whether BKPT should correctly raise a SIGTRAP that
> is appropriately handled is an entirely separate issue, which I
> would regard as a feature request rather than a regression.
>
> Let me put it slightly differently. BKPT in userspace hasn't been
> supported by the kernel, and the behaviour you've seen from the
> kernel is incidental to the kernel's abort handling - it is not
> by design.
>
> Architecturally, BKPT is used with JTAG debuggers, causing the
> processor to enter debug mode so a JTAG debugger can do its
> stuff. There was some discussion ten years ago whether LLVM
> should use BKPT for setting software breakpoints, and it seems
> they decided against it because of interfering with JTAG
> debuggers. See https://reviews.llvm.org/D16853?id=46899#347119
>
> Also see the linked discussion from that post, where using BKPT
> was discussed with gdb. Basically, if a hardware JTAG debugger is
> connected, BKPT goes straight to the hardware debugger not the
> kernel. However, note that the sourceware discussion is talking
> about Thumb2 rather than ARM, but the same will apply there.
>
> In essence, the decision was to stick with the UDF instructions
> for software breakpoints handled by the kernel, and leave BKPT
> for hardware JTAG debuggers. Consequently, explicitly executing
> BKPT without a hardware JTAG debugger is unexpected, the results
> of which are not guaranteed.
>
> Indeed, under older architectures, you'll get an undefined
> instruction exception and the program killed by a SIGILL not a
> SIGTRAP, because BKPT isn't architecturally defined there.
For further clarification, see the ARM Architecture Reference Manual,
DDI0100E, which introduced BKPT, page 114, but specifically page 115
which states in the notes:
"Hardware override
"Debug hardware in an implementation is specifically permitted to
override the normal behavior of the BKPT instruction. Because of
this, software must not use this instruction for purposes other than
those documented by the debug system being used (if any). In
particular, software cannot rely on the Prefetch Abort exception
occurring, unless either there is guaranteed to be no debug hardware
in the system or the debug system specifies that it will occur.
"For more information, consult the documentation for the debug
system being used."
DDI0406C also mentions C2.2 states that if DBGEN is enabled, then
all debug events become halting and cause the CPU to enter debug
state (for a hardware debugger to respond to.) However, the above
statement is no longer present, but is covered via other means.
Indeed, a JTAG hardware debugger can still override BKPT to
put the CPU into debug mode and omit to generate the Prefetch
Abort exception.
Thus, BKPT isn't guaranteed to raise a prefetch abort depending
on whether there's a hardware debugger connected and how that
debugger has configured the interface.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-21 23:24 ` Russell King
@ 2026-06-23 2:05 ` slipher
2026-06-23 9:48 ` Russell King
0 siblings, 1 reply; 15+ messages in thread
From: slipher @ 2026-06-23 2:05 UTC (permalink / raw)
To: Russell King
Cc: linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev
On Sunday, June 21st, 2026 at 6:25 PM, Russell King <linux@armlinux.org.uk> wrote:
> On Sun, Jun 21, 2026 at 11:41:03PM +0100, Russell King (Oracle) wrote:
> > On Sun, Jun 21, 2026 at 09:53:17PM +0000, slipher wrote:
> > >
> > > On Sunday, June 21st, 2026 at 3:19 PM, Russell King (Oracle) <linux@armlinux.org.uk> 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
> >
> > This message is printed at Alert level. It's just not supposed to
> > happen, and if anyone sees it, it means someone cocked up in the kernel
> > and didn't provide the code to handle a fault that can be generated.
> >
> > In these situations, the kernel's response is to try and keep the system
> > running by delivering a signal that should result in the process being
> > terminated. In this case, the hardware breakpoint code tells the
> > generic code to deliver a SIGTRAP / TRAP_HWBKPT, and this will be
> > delivered by force_sig_fault() after the noisy kernel message has been
> > produced.
> >
> > force_sig_fault() will unblock the signal and set the handler to
> > default if it was blocked or ignored. The default action for SIGTRAP
> > should be to generate a coredump and terminate the program.
> >
> > > 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.
> >
> > ... and while it's repeatedly raising the same fault, it's flooding the
> > kernel console with Alert level messages telling you the fault hasn't
> > been handled even on older kernels... yet you seem to be under the
> > impression that this is supposed to work.
> >
> > You are testing something that has never been tested before, and are
> > hitting behaviour that isn't _supposed_ to be clean.
> >
> > That said, the change of behaviour is wrong. If
> > hw_breakpoint_cfi_handler() doesn't understand the reason its been
> > called, it should cause the old behaviour (where the alert message
> > is printed) to be actioned.
> >
> > The issue over whether BKPT should correctly raise a SIGTRAP that
> > is appropriately handled is an entirely separate issue, which I
> > would regard as a feature request rather than a regression.
> >
> > Let me put it slightly differently. BKPT in userspace hasn't been
> > supported by the kernel, and the behaviour you've seen from the
> > kernel is incidental to the kernel's abort handling - it is not
> > by design.
> >
> > Architecturally, BKPT is used with JTAG debuggers, causing the
> > processor to enter debug mode so a JTAG debugger can do its
> > stuff. There was some discussion ten years ago whether LLVM
> > should use BKPT for setting software breakpoints, and it seems
> > they decided against it because of interfering with JTAG
> > debuggers. See https://reviews.llvm.org/D16853?id=46899#347119
> >
> > Also see the linked discussion from that post, where using BKPT
> > was discussed with gdb. Basically, if a hardware JTAG debugger is
> > connected, BKPT goes straight to the hardware debugger not the
> > kernel. However, note that the sourceware discussion is talking
> > about Thumb2 rather than ARM, but the same will apply there.
> >
> > In essence, the decision was to stick with the UDF instructions
> > for software breakpoints handled by the kernel, and leave BKPT
> > for hardware JTAG debuggers. Consequently, explicitly executing
> > BKPT without a hardware JTAG debugger is unexpected, the results
> > of which are not guaranteed.
> >
> > Indeed, under older architectures, you'll get an undefined
> > instruction exception and the program killed by a SIGILL not a
> > SIGTRAP, because BKPT isn't architecturally defined there.
>
> For further clarification, see the ARM Architecture Reference Manual,
> DDI0100E, which introduced BKPT, page 114, but specifically page 115
> which states in the notes:
>
> "Hardware override
> "Debug hardware in an implementation is specifically permitted to
> override the normal behavior of the BKPT instruction. Because of
> this, software must not use this instruction for purposes other than
> those documented by the debug system being used (if any). In
> particular, software cannot rely on the Prefetch Abort exception
> occurring, unless either there is guaranteed to be no debug hardware
> in the system or the debug system specifies that it will occur.
>
> "For more information, consult the documentation for the debug
> system being used."
>
> DDI0406C also mentions C2.2 states that if DBGEN is enabled, then
> all debug events become halting and cause the CPU to enter debug
> state (for a hardware debugger to respond to.) However, the above
> statement is no longer present, but is covered via other means.
> Indeed, a JTAG hardware debugger can still override BKPT to
> put the CPU into debug mode and omit to generate the Prefetch
> Abort exception.
>
> Thus, BKPT isn't guaranteed to raise a prefetch abort depending
> on whether there's a hardware debugger connected and how that
> debugger has configured the interface.
>
> --
> RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
> FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
>
To be clear, I'm not coming at this from a standpoint of "BKPT must be
the one true breakpoint instruction because it's the one named after
breakpoints". A piece of legacy software than I use relies on this
instruction generating SIGTRAP (and then longjmp'ing out of the signal
handler). A program stopped working, so I understood that to be a
regression according to the definitions on kernel.org. If the
maintainers consider my use case to be too xkcd.com/1172 to care about,
that's understandable. I'm not concerned about whether fixes are
backported; it shouldn't be that hard to fix by swapping with UDF
instructions.
Anyhow, regardless of how previous kernel versions behave, I would like
to simply report some buggy behavior. I think we agree that resuming at
a faulting instruction to create an infinite loop can't be the right
thing to do. Additionally, it seems fishy that the software-defined(?)
CFI fault code coincides with one of the method-of-entry codes generated
by the processor, or that an error in user-space code can trigger a jump
into the CFI fault path. Maybe this is intentional and it is somehow
expedient to do this, but it should be better documented at least.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-23 2:05 ` slipher
@ 2026-06-23 9:48 ` Russell King
2026-06-23 13:35 ` Linus Walleij
0 siblings, 1 reply; 15+ messages in thread
From: Russell King @ 2026-06-23 9:48 UTC (permalink / raw)
To: slipher, Linus Walleij
Cc: linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev
On Tue, Jun 23, 2026 at 02:05:29AM +0000, slipher wrote:
>
> On Sunday, June 21st, 2026 at 6:25 PM, Russell King <linux@armlinux.org.uk> wrote:
>
> > On Sun, Jun 21, 2026 at 11:41:03PM +0100, Russell King (Oracle) wrote:
> > > On Sun, Jun 21, 2026 at 09:53:17PM +0000, slipher wrote:
> > > >
> > > > On Sunday, June 21st, 2026 at 3:19 PM, Russell King (Oracle) <linux@armlinux.org.uk> 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
> > >
> > > This message is printed at Alert level. It's just not supposed to
> > > happen, and if anyone sees it, it means someone cocked up in the kernel
> > > and didn't provide the code to handle a fault that can be generated.
> > >
> > > In these situations, the kernel's response is to try and keep the system
> > > running by delivering a signal that should result in the process being
> > > terminated. In this case, the hardware breakpoint code tells the
> > > generic code to deliver a SIGTRAP / TRAP_HWBKPT, and this will be
> > > delivered by force_sig_fault() after the noisy kernel message has been
> > > produced.
> > >
> > > force_sig_fault() will unblock the signal and set the handler to
> > > default if it was blocked or ignored. The default action for SIGTRAP
> > > should be to generate a coredump and terminate the program.
> > >
> > > > 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.
> > >
> > > ... and while it's repeatedly raising the same fault, it's flooding the
> > > kernel console with Alert level messages telling you the fault hasn't
> > > been handled even on older kernels... yet you seem to be under the
> > > impression that this is supposed to work.
> > >
> > > You are testing something that has never been tested before, and are
> > > hitting behaviour that isn't _supposed_ to be clean.
> > >
> > > That said, the change of behaviour is wrong. If
> > > hw_breakpoint_cfi_handler() doesn't understand the reason its been
> > > called, it should cause the old behaviour (where the alert message
> > > is printed) to be actioned.
> > >
> > > The issue over whether BKPT should correctly raise a SIGTRAP that
> > > is appropriately handled is an entirely separate issue, which I
> > > would regard as a feature request rather than a regression.
> > >
> > > Let me put it slightly differently. BKPT in userspace hasn't been
> > > supported by the kernel, and the behaviour you've seen from the
> > > kernel is incidental to the kernel's abort handling - it is not
> > > by design.
> > >
> > > Architecturally, BKPT is used with JTAG debuggers, causing the
> > > processor to enter debug mode so a JTAG debugger can do its
> > > stuff. There was some discussion ten years ago whether LLVM
> > > should use BKPT for setting software breakpoints, and it seems
> > > they decided against it because of interfering with JTAG
> > > debuggers. See https://reviews.llvm.org/D16853?id=46899#347119
> > >
> > > Also see the linked discussion from that post, where using BKPT
> > > was discussed with gdb. Basically, if a hardware JTAG debugger is
> > > connected, BKPT goes straight to the hardware debugger not the
> > > kernel. However, note that the sourceware discussion is talking
> > > about Thumb2 rather than ARM, but the same will apply there.
> > >
> > > In essence, the decision was to stick with the UDF instructions
> > > for software breakpoints handled by the kernel, and leave BKPT
> > > for hardware JTAG debuggers. Consequently, explicitly executing
> > > BKPT without a hardware JTAG debugger is unexpected, the results
> > > of which are not guaranteed.
> > >
> > > Indeed, under older architectures, you'll get an undefined
> > > instruction exception and the program killed by a SIGILL not a
> > > SIGTRAP, because BKPT isn't architecturally defined there.
> >
> > For further clarification, see the ARM Architecture Reference Manual,
> > DDI0100E, which introduced BKPT, page 114, but specifically page 115
> > which states in the notes:
> >
> > "Hardware override
> > "Debug hardware in an implementation is specifically permitted to
> > override the normal behavior of the BKPT instruction. Because of
> > this, software must not use this instruction for purposes other than
> > those documented by the debug system being used (if any). In
> > particular, software cannot rely on the Prefetch Abort exception
> > occurring, unless either there is guaranteed to be no debug hardware
> > in the system or the debug system specifies that it will occur.
> >
> > "For more information, consult the documentation for the debug
> > system being used."
> >
> > DDI0406C also mentions C2.2 states that if DBGEN is enabled, then
> > all debug events become halting and cause the CPU to enter debug
> > state (for a hardware debugger to respond to.) However, the above
> > statement is no longer present, but is covered via other means.
> > Indeed, a JTAG hardware debugger can still override BKPT to
> > put the CPU into debug mode and omit to generate the Prefetch
> > Abort exception.
> >
> > Thus, BKPT isn't guaranteed to raise a prefetch abort depending
> > on whether there's a hardware debugger connected and how that
> > debugger has configured the interface.
> >
> > --
> > RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
> > FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
> >
>
> To be clear, I'm not coming at this from a standpoint of "BKPT must be
> the one true breakpoint instruction because it's the one named after
> breakpoints". A piece of legacy software than I use relies on this
> instruction generating SIGTRAP (and then longjmp'ing out of the signal
> handler). A program stopped working, so I understood that to be a
> regression according to the definitions on kernel.org. If the
> maintainers consider my use case to be too xkcd.com/1172 to care about,
> that's understandable. I'm not concerned about whether fixes are
> backported; it shouldn't be that hard to fix by swapping with UDF
> instructions.
Sigh. It is not that we don't care - in fact, I've already told Linus W
(as author of the commit causing your issue - who you should have Cc'd
on this report) that this needs to be fixed so that the behaviour that
userspace sees doesn't change - as per kernel rules.
However, what I'm also pointing out is that your use case results in
behaviours that can't be relied upon in userspace to work from an
architectural point of view, and that have historically always produced
a kernel alert message - thus are slow - and I'd say by intention
because BPKT has never actually been supported.
If one disables PERF_EVENTS in the kernel configuration, you won't
even get the SIGTRAP for BKPT, but instead get a SIGBUS. That is also
how the kernel would handle BKPT propr to 3rd September 2010 (v2.6.37)
even with PERF_EVENTS enabled.
Hence, the raising of SIGTRAP instead of SIGBUS can also be regarded as
a regression /if/ someone pop up and say that they're relying on that
behaviour - and if that were to be reported, under kernel rules, that
regression would also need to be fixed, which means that generating
SIGTRAP for BKPT no longer becomes possible, and thus your program
breaks - but the historical nature of the older behaviour wins out.
So, what I'm saying is that your program is relying on unstable
foundations here, but let me be clear: because you have reported the
change of behaviour, it will get fixed. We just can't guarantee that
it will remain fixed for the reasons I've pointed out in my various
emails to you.
Sadly, that's what happens when every i isn't dotted and every t isn't
crossed when it comes to "features" that the CPU supports but the
kernel doesn't.
Let me also be clear: I expect Linus W to fix this - firstly, his
commit introduced the breakage, and secondly, I have little time at
the moment to do any kernel hacking (ongoing long term family issues.)
> Anyhow, regardless of how previous kernel versions behave, I would like
> to simply report some buggy behavior. I think we agree that resuming at
> a faulting instruction to create an infinite loop can't be the right
> thing to do. Additionally, it seems fishy that the software-defined(?)
> CFI fault code coincides with one of the method-of-entry codes generated
> by the processor, or that an error in user-space code can trigger a jump
> into the CFI fault path. Maybe this is intentional and it is somehow
> expedient to do this, but it should be better documented at least.
It is documented as I have pointed out in the architecture reference
manuals. It is not the kernel's job to document architectural details.
I suspect that the CFI fault code was a decision by compiler authors,
but I can't say because I don't have a setup that generates the code
for CFI.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-23 9:48 ` Russell King
@ 2026-06-23 13:35 ` Linus Walleij
2026-06-23 15:38 ` Russell King
0 siblings, 1 reply; 15+ messages in thread
From: Linus Walleij @ 2026-06-23 13:35 UTC (permalink / raw)
To: Russell King
Cc: slipher, linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev
On Tue, Jun 23, 2026 at 11:48 AM Russell King <linux@armlinux.org.uk> wrote:
> Let me also be clear: I expect Linus W to fix this
I'll try!
I guess the offending commit is:
commit c3f89986fde7bb9ccc86a901bf28e1f7d69fc3b3
"ARM: 9391/2: hw_breakpoint: Handle CFI breakpoints"
> I suspect that the CFI fault code was a decision by compiler authors,
> but I can't say because I don't have a setup that generates the code
> for CFI.
Yep, the LLVM implementers chose breakpoint code 0x03:
I think it comes from here:
https://llvm.org/doxygen/ARMAsmPrinter_8cpp_source.html
Line 1536-37:
unsigned AddrIndex = TRI->getEncodingValue(AddrReg);
unsigned ESR = 0x8000 | (31 << 5) | (AddrIndex & 31);
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-23 13:35 ` Linus Walleij
@ 2026-06-23 15:38 ` Russell King
0 siblings, 0 replies; 15+ messages in thread
From: Russell King @ 2026-06-23 15:38 UTC (permalink / raw)
To: Linus Walleij
Cc: slipher, linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev
On Tue, Jun 23, 2026 at 03:35:05PM +0200, Linus Walleij wrote:
> On Tue, Jun 23, 2026 at 11:48 AM Russell King <linux@armlinux.org.uk> wrote:
>
> > Let me also be clear: I expect Linus W to fix this
>
> I'll try!
>
> I guess the offending commit is:
> commit c3f89986fde7bb9ccc86a901bf28e1f7d69fc3b3
> "ARM: 9391/2: hw_breakpoint: Handle CFI breakpoints"
>
> > I suspect that the CFI fault code was a decision by compiler authors,
> > but I can't say because I don't have a setup that generates the code
> > for CFI.
>
> Yep, the LLVM implementers chose breakpoint code 0x03:
> I think it comes from here:
> https://llvm.org/doxygen/ARMAsmPrinter_8cpp_source.html
> Line 1536-37:
> unsigned AddrIndex = TRI->getEncodingValue(AddrReg);
> unsigned ESR = 0x8000 | (31 << 5) | (AddrIndex & 31);
Presumably it's actually line 1618:
EmitToStreamer(*OutStreamer, MCInstBuilder(ARM::UDF).addImm(ESR));
and if ARM::UDF happens to be BKPT rather than the UDF instruction
(maybe this is configurable? Can you check what this is and whether
it is?)
If LLVM is also using the BKPT instruction, then we have a compiler
binary interface vs userspace program using BKPT issue...
Given the "no regressions" rule, this means we can't ever support the
LLVM CFI BKPT usage unless LLVM changes... and that creates a
regression for the compiler people, because they have to care about
more than just the Linux kernel and one userspace program.
I don't see a clean solution to this, other than saying "No!" to
LLVM people and backing out your change.
Maybe our nameless regressionreporter can find out which BKPT
instruction(s) is/are actually being used (it takes an immediate
value.)
This makes me want the old days of Acorn Computers back, because
*they* were sensible when it comes to instruction sets and fields
such as the immediate in SWI/SVC - they had the foresight to state
that the immediate would be broken up into fields which included an
OS identifier, effectively separating the numberspace so that
different OS etc avoid clashing with that instruction. Linux had
a time when it supported running Acorn RISC OS programs natively
as a result of that in the days of OABI. Seems to me the computer
industry just goes backwards. :/
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-21 19:15 [REGRESSION] 32-bit ARM's BKPT instruction no longer works slipher
2026-06-21 20:19 ` Russell King (Oracle)
@ 2026-06-26 12:53 ` Linus Walleij
2026-06-26 13:08 ` Russell King
2026-06-26 13:53 ` David Laight
1 sibling, 2 replies; 15+ messages in thread
From: Linus Walleij @ 2026-06-26 12:53 UTC (permalink / raw)
To: slipher, Nathan Chancellor, Kees Cook, Sami Tolvanen
Cc: linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev, linus.walleij@linaro.org,
rmk+kernel@armlinux.org.uk
[Adding Nathan and Kees so we can figure out how best to deal with this]
On Sun, Jun 21, 2026 at 9:15 PM slipher <slipher@protonmail.com> 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.
>
> I believe the culprit to be commit
> c3f89986fde7bb9ccc86a901bf28e1f7d69fc3b3 "ARM: 9391/2: hw_breakpoint:
> Handle CFI breakpoints". The commit defines the method-of-entry code 3
> as "ARM_ENTRY_CFI_BREAKPOINT", but this is the code used for any BKPT
> instruction - see
> https://developer.arm.com/documentation/ddi0379/a/Debug-Register-Reference/Control-and-status-registers/Debug-Status-and-Control-Register--DSCR-?lang=en
> "Method of Debug Entry (MOE), bits [5:2]". If the CFI option is disabled
> in the kernel config, hw_breakpoint_pending() returns 0 indicating the
> breakpoint was handled, but takes no action. So breakpoints cannot be
> used by user-space code, regardless of how CONFIG_CFI is set. The blog
> post
> https://www.jwhitham.org/2015/04/the-mystery-of-fifteen-millisecond.html
> gives a nice overview of the control flow in older, working kernels.
Does simply reverting the patch solve the issue?
> The following Systemtap script can be used to demonstrate that the
> ARM_ENTRY_CFI_BREAKPOINT path is used, when running the above C program.
Yeah it's definitely that one causing it.
I sent the naive solution to it, and before anyone point it out: no it does
not allow custom breakpoints to be mixed with kernel CFI, but it
probably makes legacy systems work on newer kernels since they
probably don't select CFI.
https://lore.kernel.org/linux-arm-kernel/20260626-arm32-cfi-bug-v1-1-a467b5050c0b@kernel.org/T/#u
I understand that this is not solving everything.
If it is under all circumstances unacceptable to be able to construct
a userspace which will change the user-facing behaviour of BKPT,
I think we need to revert CFI breakpoint handling, back put the patch,
disable CFI on ARM and wait for the compiler(s) to start behaving
differently on ARM.
CFI folks: any ideas on what we could do instead of BKPT
when we hit a CFI snag? Any ideas from other architectures?
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-26 12:53 ` Linus Walleij
@ 2026-06-26 13:08 ` Russell King
2026-06-26 13:32 ` Linus Walleij
2026-06-26 13:53 ` David Laight
1 sibling, 1 reply; 15+ messages in thread
From: Russell King @ 2026-06-26 13:08 UTC (permalink / raw)
To: Linus Walleij
Cc: slipher, Nathan Chancellor, Kees Cook, Sami Tolvanen,
linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev, linus.walleij@linaro.org
On Fri, Jun 26, 2026 at 02:53:56PM +0200, Linus Walleij wrote:
> [Adding Nathan and Kees so we can figure out how best to deal with this]
>
> On Sun, Jun 21, 2026 at 9:15 PM slipher <slipher@protonmail.com> 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.
> >
> > I believe the culprit to be commit
> > c3f89986fde7bb9ccc86a901bf28e1f7d69fc3b3 "ARM: 9391/2: hw_breakpoint:
> > Handle CFI breakpoints". The commit defines the method-of-entry code 3
> > as "ARM_ENTRY_CFI_BREAKPOINT", but this is the code used for any BKPT
> > instruction - see
> > https://developer.arm.com/documentation/ddi0379/a/Debug-Register-Reference/Control-and-status-registers/Debug-Status-and-Control-Register--DSCR-?lang=en
> > "Method of Debug Entry (MOE), bits [5:2]". If the CFI option is disabled
> > in the kernel config, hw_breakpoint_pending() returns 0 indicating the
> > breakpoint was handled, but takes no action. So breakpoints cannot be
> > used by user-space code, regardless of how CONFIG_CFI is set. The blog
> > post
> > https://www.jwhitham.org/2015/04/the-mystery-of-fifteen-millisecond.html
> > gives a nice overview of the control flow in older, working kernels.
>
> Does simply reverting the patch solve the issue?
>
> > The following Systemtap script can be used to demonstrate that the
> > ARM_ENTRY_CFI_BREAKPOINT path is used, when running the above C program.
>
> Yeah it's definitely that one causing it.
>
> I sent the naive solution to it, and before anyone point it out: no it does
> not allow custom breakpoints to be mixed with kernel CFI, but it
> probably makes legacy systems work on newer kernels since they
> probably don't select CFI.
> https://lore.kernel.org/linux-arm-kernel/20260626-arm32-cfi-bug-v1-1-a467b5050c0b@kernel.org/T/#u
>
> I understand that this is not solving everything.
>
> If it is under all circumstances unacceptable to be able to construct
> a userspace which will change the user-facing behaviour of BKPT,
> I think we need to revert CFI breakpoint handling, back put the patch,
> disable CFI on ARM and wait for the compiler(s) to start behaving
> differently on ARM.
>
> CFI folks: any ideas on what we could do instead of BKPT
> when we hit a CFI snag? Any ideas from other architectures?
This is the root of why BKPT should *not* be used.
The compiler people say "oh we can use BKPT to indicate a program
error."
The hardware debugger guys say "I'm using BKPT for my implementation
of debugging" which takes it away from software usages.
Then someone comes along and says "we can use BKPT in our program
for XYZ and ignore that the kernel complains... oh the kernel
complains, that has bad performance but let's 'fix' the kernel so
it behaves how we want it to".
The problem here is that BKPT is being overloaded to have multiple
different purposes by different people. This *can't* work, and the
only answer to it is... no one should use it.
That doesn't mean we don't fix the regression. That means that we
should *never* introduce any new uses of this instruction in
compilers, kernel, etc.
As I've already pointed out, if PERF_EVENTS is disabled, then the
kernel won't even deliver a SIGTRAP on BKPT, but instead a SIGBUS.
That's not a regression, even though this userspace program will
break - that's the *original* behaviour prior to hw_breakpoint.c
being added. So, this userspace program is fragile and broken by
using BKPT.
What's more is that, as I've already pointed out, if someone were to
report a regression that their userspace program breaks because BKPT
now raises a SIGTRAP rather than SIGBUS, we would have to fix that
regression, which would then break this program.
Aren't regressions wonderful!
So I think safest is that everyone just moves away from BKPT.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-26 13:08 ` Russell King
@ 2026-06-26 13:32 ` Linus Walleij
0 siblings, 0 replies; 15+ messages in thread
From: Linus Walleij @ 2026-06-26 13:32 UTC (permalink / raw)
To: Russell King
Cc: slipher, Nathan Chancellor, Kees Cook, Sami Tolvanen,
linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev, linus.walleij@linaro.org
On Fri, Jun 26, 2026 at 3:08 PM Russell King <linux@armlinux.org.uk> wrote:
> Aren't regressions wonderful!
Makes me feel alive! ;)
> So I think safest is that everyone just moves away from BKPT.
I agree.
I'm waiting for the compiler guys to see if they have some good idea
on what to do istead.
Since it is a security feature, to me hanging in an eternal loop waiting
for watchdog reset or power off is better than issuing BKPT.
But that's pretty nasty.
Can the compiler simply use a read of the guard region at ffc00000
to raise a SIGBUS? (This is inherently a Linux-only solution but,
perhaps this can be parameterized?)
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-26 12:53 ` Linus Walleij
2026-06-26 13:08 ` Russell King
@ 2026-06-26 13:53 ` David Laight
2026-06-26 15:38 ` Russell King
1 sibling, 1 reply; 15+ messages in thread
From: David Laight @ 2026-06-26 13:53 UTC (permalink / raw)
To: Linus Walleij
Cc: slipher, Nathan Chancellor, Kees Cook, Sami Tolvanen,
linux-kernel@vger.kernel.org, stable@vger.kernel.org,
regressions@lists.linux.dev, linus.walleij@linaro.org,
rmk+kernel@armlinux.org.uk
On Fri, 26 Jun 2026 14:53:56 +0200
Linus Walleij <linusw@kernel.org> wrote:
> [Adding Nathan and Kees so we can figure out how best to deal with this]
>
> On Sun, Jun 21, 2026 at 9:15 PM slipher <slipher@protonmail.com> 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.
> >
> > I believe the culprit to be commit
> > c3f89986fde7bb9ccc86a901bf28e1f7d69fc3b3 "ARM: 9391/2: hw_breakpoint:
> > Handle CFI breakpoints". The commit defines the method-of-entry code 3
> > as "ARM_ENTRY_CFI_BREAKPOINT", but this is the code used for any BKPT
> > instruction - see
> > https://developer.arm.com/documentation/ddi0379/a/Debug-Register-Reference/Control-and-status-registers/Debug-Status-and-Control-Register--DSCR-?lang=en
> > "Method of Debug Entry (MOE), bits [5:2]". If the CFI option is disabled
> > in the kernel config, hw_breakpoint_pending() returns 0 indicating the
> > breakpoint was handled, but takes no action. So breakpoints cannot be
> > used by user-space code, regardless of how CONFIG_CFI is set. The blog
> > post
> > https://www.jwhitham.org/2015/04/the-mystery-of-fifteen-millisecond.html
> > gives a nice overview of the control flow in older, working kernels.
>
> Does simply reverting the patch solve the issue?
>
> > The following Systemtap script can be used to demonstrate that the
> > ARM_ENTRY_CFI_BREAKPOINT path is used, when running the above C program.
>
> Yeah it's definitely that one causing it.
>
> I sent the naive solution to it, and before anyone point it out: no it does
> not allow custom breakpoints to be mixed with kernel CFI, but it
> probably makes legacy systems work on newer kernels since they
> probably don't select CFI.
> https://lore.kernel.org/linux-arm-kernel/20260626-arm32-cfi-bug-v1-1-a467b5050c0b@kernel.org/T/#u
>
> I understand that this is not solving everything.
I'm confused.
Why would building a kernel with CFI (to check kernel indirect calls)
change the behaviour of executing anything in userspace?
If userspace is compiled with CFI and gets an equivalent fail then you'd
(probably) want a fatal signal - but isn't that entirely unrelated to
the kernel code.
Do those checks even need kernel support? I know shadow stacks do.
David
>
> If it is under all circumstances unacceptable to be able to construct
> a userspace which will change the user-facing behaviour of BKPT,
> I think we need to revert CFI breakpoint handling, back put the patch,
> disable CFI on ARM and wait for the compiler(s) to start behaving
> differently on ARM.
>
> CFI folks: any ideas on what we could do instead of BKPT
> when we hit a CFI snag? Any ideas from other architectures?
>
> Yours,
> Linus Walleij
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-26 13:53 ` David Laight
@ 2026-06-26 15:38 ` Russell King
2026-06-26 16:35 ` David Laight
0 siblings, 1 reply; 15+ messages in thread
From: Russell King @ 2026-06-26 15:38 UTC (permalink / raw)
To: David Laight
Cc: Linus Walleij, slipher, Nathan Chancellor, Kees Cook,
Sami Tolvanen, linux-kernel@vger.kernel.org,
stable@vger.kernel.org, regressions@lists.linux.dev,
linus.walleij@linaro.org
On Fri, Jun 26, 2026 at 02:53:56PM +0100, David Laight wrote:
> On Fri, 26 Jun 2026 14:53:56 +0200
> Linus Walleij <linusw@kernel.org> wrote:
>
> > [Adding Nathan and Kees so we can figure out how best to deal with this]
> >
> > On Sun, Jun 21, 2026 at 9:15 PM slipher <slipher@protonmail.com> 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.
> > >
> > > I believe the culprit to be commit
> > > c3f89986fde7bb9ccc86a901bf28e1f7d69fc3b3 "ARM: 9391/2: hw_breakpoint:
> > > Handle CFI breakpoints". The commit defines the method-of-entry code 3
> > > as "ARM_ENTRY_CFI_BREAKPOINT", but this is the code used for any BKPT
> > > instruction - see
> > > https://developer.arm.com/documentation/ddi0379/a/Debug-Register-Reference/Control-and-status-registers/Debug-Status-and-Control-Register--DSCR-?lang=en
> > > "Method of Debug Entry (MOE), bits [5:2]". If the CFI option is disabled
> > > in the kernel config, hw_breakpoint_pending() returns 0 indicating the
> > > breakpoint was handled, but takes no action. So breakpoints cannot be
> > > used by user-space code, regardless of how CONFIG_CFI is set. The blog
> > > post
> > > https://www.jwhitham.org/2015/04/the-mystery-of-fifteen-millisecond.html
> > > gives a nice overview of the control flow in older, working kernels.
> >
> > Does simply reverting the patch solve the issue?
> >
> > > The following Systemtap script can be used to demonstrate that the
> > > ARM_ENTRY_CFI_BREAKPOINT path is used, when running the above C program.
> >
> > Yeah it's definitely that one causing it.
> >
> > I sent the naive solution to it, and before anyone point it out: no it does
> > not allow custom breakpoints to be mixed with kernel CFI, but it
> > probably makes legacy systems work on newer kernels since they
> > probably don't select CFI.
> > https://lore.kernel.org/linux-arm-kernel/20260626-arm32-cfi-bug-v1-1-a467b5050c0b@kernel.org/T/#u
> >
> > I understand that this is not solving everything.
>
> I'm confused.
> Why would building a kernel with CFI (to check kernel indirect calls)
> change the behaviour of executing anything in userspace?
>
> If userspace is compiled with CFI and gets an equivalent fail then you'd
> (probably) want a fatal signal - but isn't that entirely unrelated to
> the kernel code.
> Do those checks even need kernel support? I know shadow stacks do.
CFI generates instructions that can check the type of the function
against the caller. It appears that on 32-bit ARM, Clang close that,
in the case of a mismatch, it would cause a BKPT instruction to be
executed.
Linus' code in commit c3f89986fde7 ("ARM: 9391/2: hw_breakpoint:
Handle CFI breakpoints") added code to handle this BKPT use.
However, we now have a regression reported as a result of that commit
where there is a userspace program that has explicit BKPT instructions
encoded within it, and the program relies on the kernel behaviour that
was introduced in f81ef4a920c8 ("ARM: 6356/1: hw-breakpoint: add ARM
backend for the hw-breakpoint framework") in 2.6.37 - and this "new"
behaviour is conditional on CONFIG_PERF_EVENTS being enabled - where
it raises a SIGTRAP.
Prior to this commit, or whenever CONFIG_PERF_EVENTS is disabled, the
kernel will raise a SIGBUS instead.
Both SIGTRAP and SIGBUS are "forced" signals - the kernel will force
them to be delivered to the program irrespective of whether the program
has blocked or ignored these signals, since this is the kernel trying
to save the system (because it doesn't know how to handle it.)
Moreover, BKPT was only introduced around the ARMv5TE era, and the
FSR code for it was only added in later architecture reference manuals,
changing an existing FSR code from an implementation defined "Terminal
Exception" to an architecturally defined "Debug Exception".
Support for this "Debug Exception" was only added with patch 6356/1,
but that did not handle the BKPT instruction. Linus' commit above
(9391/1) added support for the CFI case, but meant that userspace
would now spin on a BKPT instruction rather than force a signal,
thereby causing the regression.
We can't fix BKPT handling - this userspace program relies on the fact
that the kernel doesn't handle this instruction (for example, it relies
on the PC not being advanced) and advancing the PC by one instruction
after a SIGTRAP handler returns may not be the correct way to handle
it anyway. Consider BKPT being used as an "assert" type context, where
the compiler doesn't expect execution to continue, and a literal pool
following the instruction.
We are now stuck with the sorry state that BKPT is, and as I have said
many times now, BKPT should be avoided - it's an utter trainwreck. The
only sensible use that BKPT has is with a hardware debugger that traps
the BKPT entry into debug mode (a special hardware debugger mode that
the CPU enters which software can't see).
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [REGRESSION] 32-bit ARM's BKPT instruction no longer works
2026-06-26 15:38 ` Russell King
@ 2026-06-26 16:35 ` David Laight
0 siblings, 0 replies; 15+ messages in thread
From: David Laight @ 2026-06-26 16:35 UTC (permalink / raw)
To: Russell King
Cc: Linus Walleij, slipher, Nathan Chancellor, Kees Cook,
Sami Tolvanen, linux-kernel@vger.kernel.org,
stable@vger.kernel.org, regressions@lists.linux.dev,
linus.walleij@linaro.org
On Fri, 26 Jun 2026 16:38:02 +0100
Russell King <linux@armlinux.org.uk> wrote:
> On Fri, Jun 26, 2026 at 02:53:56PM +0100, David Laight wrote:
> > On Fri, 26 Jun 2026 14:53:56 +0200
> > Linus Walleij <linusw@kernel.org> wrote:
> >
> > > [Adding Nathan and Kees so we can figure out how best to deal with this]
> > >
> > > On Sun, Jun 21, 2026 at 9:15 PM slipher <slipher@protonmail.com> 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.
> > > >
> > > > I believe the culprit to be commit
> > > > c3f89986fde7bb9ccc86a901bf28e1f7d69fc3b3 "ARM: 9391/2: hw_breakpoint:
> > > > Handle CFI breakpoints". The commit defines the method-of-entry code 3
> > > > as "ARM_ENTRY_CFI_BREAKPOINT", but this is the code used for any BKPT
> > > > instruction - see
> > > > https://developer.arm.com/documentation/ddi0379/a/Debug-Register-Reference/Control-and-status-registers/Debug-Status-and-Control-Register--DSCR-?lang=en
> > > > "Method of Debug Entry (MOE), bits [5:2]". If the CFI option is disabled
> > > > in the kernel config, hw_breakpoint_pending() returns 0 indicating the
> > > > breakpoint was handled, but takes no action. So breakpoints cannot be
> > > > used by user-space code, regardless of how CONFIG_CFI is set. The blog
> > > > post
> > > > https://www.jwhitham.org/2015/04/the-mystery-of-fifteen-millisecond.html
> > > > gives a nice overview of the control flow in older, working kernels.
> > >
> > > Does simply reverting the patch solve the issue?
> > >
> > > > The following Systemtap script can be used to demonstrate that the
> > > > ARM_ENTRY_CFI_BREAKPOINT path is used, when running the above C program.
> > >
> > > Yeah it's definitely that one causing it.
> > >
> > > I sent the naive solution to it, and before anyone point it out: no it does
> > > not allow custom breakpoints to be mixed with kernel CFI, but it
> > > probably makes legacy systems work on newer kernels since they
> > > probably don't select CFI.
> > > https://lore.kernel.org/linux-arm-kernel/20260626-arm32-cfi-bug-v1-1-a467b5050c0b@kernel.org/T/#u
> > >
> > > I understand that this is not solving everything.
> >
> > I'm confused.
> > Why would building a kernel with CFI (to check kernel indirect calls)
> > change the behaviour of executing anything in userspace?
> >
> > If userspace is compiled with CFI and gets an equivalent fail then you'd
> > (probably) want a fatal signal - but isn't that entirely unrelated to
> > the kernel code.
> > Do those checks even need kernel support? I know shadow stacks do.
>
> CFI generates instructions that can check the type of the function
> against the caller. It appears that on 32-bit ARM, Clang close that,
> in the case of a mismatch, it would cause a BKPT instruction to be
> executed.
>
> Linus' code in commit c3f89986fde7 ("ARM: 9391/2: hw_breakpoint:
> Handle CFI breakpoints") added code to handle this BKPT use.
>
> However, we now have a regression reported as a result of that commit
> where there is a userspace program that has explicit BKPT instructions
> encoded within it, and the program relies on the kernel behaviour that
> was introduced in f81ef4a920c8 ("ARM: 6356/1: hw-breakpoint: add ARM
> backend for the hw-breakpoint framework") in 2.6.37 - and this "new"
> behaviour is conditional on CONFIG_PERF_EVENTS being enabled - where
> it raises a SIGTRAP.
>
> Prior to this commit, or whenever CONFIG_PERF_EVENTS is disabled, the
> kernel will raise a SIGBUS instead.
>
> Both SIGTRAP and SIGBUS are "forced" signals - the kernel will force
> them to be delivered to the program irrespective of whether the program
> has blocked or ignored these signals, since this is the kernel trying
> to save the system (because it doesn't know how to handle it.)
>
> Moreover, BKPT was only introduced around the ARMv5TE era, and the
> FSR code for it was only added in later architecture reference manuals,
> changing an existing FSR code from an implementation defined "Terminal
> Exception" to an architecturally defined "Debug Exception".
>
> Support for this "Debug Exception" was only added with patch 6356/1,
> but that did not handle the BKPT instruction. Linus' commit above
> (9391/1) added support for the CFI case, but meant that userspace
> would now spin on a BKPT instruction rather than force a signal,
> thereby causing the regression.
>
> We can't fix BKPT handling - this userspace program relies on the fact
> that the kernel doesn't handle this instruction (for example, it relies
> on the PC not being advanced) and advancing the PC by one instruction
> after a SIGTRAP handler returns may not be the correct way to handle
> it anyway. Consider BKPT being used as an "assert" type context, where
> the compiler doesn't expect execution to continue, and a literal pool
> following the instruction.
>
> We are now stuck with the sorry state that BKPT is, and as I have said
> many times now, BKPT should be avoided - it's an utter trainwreck. The
> only sensible use that BKPT has is with a hardware debugger that traps
> the BKPT entry into debug mode (a special hardware debugger mode that
> the CPU enters which software can't see).
>
I'd probably forgotten a bit in the middle of that.
(Possibly backing up the pc.)
I guess it would need a flag in an elf header/section to set the behaviour
on a per program basis (horrid).
David
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-06-26 16:35 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-21 19:15 [REGRESSION] 32-bit ARM's BKPT instruction no longer works slipher
2026-06-21 20:19 ` Russell King (Oracle)
2026-06-21 21:53 ` slipher
2026-06-21 22:41 ` Russell King (Oracle)
2026-06-21 23:24 ` Russell King
2026-06-23 2:05 ` slipher
2026-06-23 9:48 ` Russell King
2026-06-23 13:35 ` Linus Walleij
2026-06-23 15:38 ` Russell King
2026-06-26 12:53 ` Linus Walleij
2026-06-26 13:08 ` Russell King
2026-06-26 13:32 ` Linus Walleij
2026-06-26 13:53 ` David Laight
2026-06-26 15:38 ` Russell King
2026-06-26 16:35 ` David Laight
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.