arch/arm64/Kconfig | 2 +- arch/arm64/include/asm/entry-common.h | 77 +++++++++++++++++ arch/arm64/include/asm/syscall.h | 5 +- arch/arm64/include/asm/thread_info.h | 16 +--- arch/arm64/kernel/debug-monitors.c | 8 ++ arch/arm64/kernel/entry-common.c | 2 +- arch/arm64/kernel/ptrace.c | 115 -------------------------- arch/arm64/kernel/signal.c | 2 +- arch/arm64/kernel/syscall.c | 34 +++----- 9 files changed, 102 insertions(+), 159 deletions(-)
This series converts arm64 to the generic entry infrastructure.
Currently, architectures such as x86, RISC-V, LoongArch, Powerprc
and S390 use generic entry, which significantly reduces maintenance
burden and improves code elegance. arm64 already adopted generic IRQ
entry in commit b3cf07851b6c ("arm64: entry: Switch to generic IRQ entry"),
making it the right time to complete the conversion.
This work brings arm64 in line with other major architectures,
reducing duplicated code and enabling future improvements like
Syscall User Dispatch and rseq time slice extension optimizations.
The series is rebased on v7.3-rc4 and has been tested with
stress-ng, hackbench, kselftests (ptrace, breakpoints, arm64/abi,
arm64/fp, vDSO), ptrace stress test, and Pseudo-NMI load test.
On gVisor with systrap mode, the Syscall User Dispatch (SUD) feature
yields a slightly lower performance gain on arm64 (3% ~ 5%), similar to
that on x86.
Result: the average over 1M getpid calls, ns/op
| KVM VM Configuration | SUD | no-SUD | Savings % |
| ------------------- | ----- | -------| --------- |
| 2 vCPU, pinned | 4269.8 | 4509.5 | 5.3% |
| 4 vCPU, pinned | 4175.4 | 4369.1 | 4.4% |
| 8 vCPU, pinned | 4352.6 | 4485.4 | 3.0% |
Link: https://github.com/google/gvisor/commit/a45d6227b521012109cde9a942d644d778eb773b
Changes in v19:
- Rebased on v7.3-rc4.
- Fix build problem as Karl pointed out.
- Remove has_syscall_work() later to make it more clear.
- Add gvisor test data.
- Link to v18: https://lore.kernel.org/all/20260902095537.602517-2-ruanjinjie@huawei.com/
Changes in v18:
- Rebased on v7.3-rc1.
- Drop the SUD patch as Mark suggeted.
- Update the commit message.
- Add Reviewed-by.
- Link to v17: https://lore.kernel.org/all/20260721081858.1169276-1-ruanjinjie@huawei.com/
For changes in v7 to v16, please refer to the individual patch threads:
- v16: https://lore.kernel.org/all/20260629130616.642022-1-ruanjinjie@huawei.com/
- v15: https://lore.kernel.org/all/20260511092103.1974980-1-ruanjinjie@huawei.com/
- v14: https://lore.kernel.org/all/20260320102620.1336796-1-ruanjinjie@huawei.com/
- v13: https://lore.kernel.org/all/20260317082020.737779-1-ruanjinjie@huawei.com/
- v12: https://lore.kernel.org/all/20260203133728.848283-1-ruanjinjie@huawei.com/
- v11: https://lore.kernel.org/all/20260128031934.3906955-1-ruanjinjie@huawei.com/
- v10: https://lore.kernel.org/all/20251222114737.1334364-1-ruanjinjie@huawei.com/
- v9: https://lore.kernel.org/all/20251204082123.2792067-1-ruanjinjie@huawei.com/
- v8: https://lore.kernel.org/all/20251126071446.3234218-1-ruanjinjie@huawei.com/
- v7: https://lore.kernel.org/all/20251117133048.53182-1-ruanjinjie@huawei.com/
Jinjie Ruan (14):
arm64: ptrace: Fix redundant syscall exit stop for
PTRACE_SYSEMU_SINGLESTEP
arm64: ptrace: Rework audit_syscall_entry()
arm64: ptrace: Open-code seccomp check in syscall_trace_enter()
arm64: ptrace: Rename and clean up syscall_trace_enter()
arm64: ptrace: Protect rseq_syscall() from tracer PC modifications
arm64: syscall: Rework the syscall exit path in el0_svc_common()
arm64: ptrace: Pass thread flags to trace enter/exit
arm64: ptrace: Extract arm64_syscall_exit_to_user_mode_work() helper
arm64: ptrace: Align syscall exit work semantics with generic entry
arm64: syscall: Use exit-specific flags check in el0_svc_common()
arm64: syscall: Simplify el0_svc_common() syscall exit path
arm64: ptrace: Make return type of arm64_syscall_trace_enter() bool
arm64: entry: Convert to generic entry
arm64: Inline el0_svc_common()
arch/arm64/Kconfig | 2 +-
arch/arm64/include/asm/entry-common.h | 77 +++++++++++++++++
arch/arm64/include/asm/syscall.h | 5 +-
arch/arm64/include/asm/thread_info.h | 16 +---
arch/arm64/kernel/debug-monitors.c | 8 ++
arch/arm64/kernel/entry-common.c | 2 +-
arch/arm64/kernel/ptrace.c | 115 --------------------------
arch/arm64/kernel/signal.c | 2 +-
arch/arm64/kernel/syscall.c | 34 +++-----
9 files changed, 102 insertions(+), 159 deletions(-)
--
2.34.1
On Tue, Sep 22, 2026 at 11:54:56AM +0800, Jinjie Ruan wrote:
> This series converts arm64 to the generic entry infrastructure.
With my trusty LLM driving a bunch of orchestration, I gave this
a fairly wide before/after run under qemu-system-aarch64, trying
to cover things beyond the ptrace/breakpoints/abi/fp/vDSO list in
the cover letter. But since this is all emulated, it's mainly basic
correctness coverage, without any meaningful concurrency coverage. I
just wanted to find stuff that maybe hadn't been exercised yet.
tl;dr: the only behavioral difference I could find anywhere is what
patch 1 fixed, and I found no meaningful regressions.
Setup: the series applies cleanly to v7.3-rc2 (I only noticed later
the series was actually based on -rc4), so "base" is v7.3-rc2 and
"patched" is the same tree plus the 14 patches, with each pair built
from the same config with GCC 16.1.0. The guest was QEMU 11.1.0:
-M virt,gic-version=max,mte=on -cpu max,pauth-impdef=on -smp 4
I used four kernel configs, each built for both base and patched:
plain defconfig + SECCOMP, USER_NS, AUDITSYSCALL, KUNIT_ALL_TESTS
lockdep plain + PROVE_LOCKING, TRACE_IRQFLAGS, PROVE_RCU,
DEBUG_ATOMIC_SLEEP, DEBUG_PREEMPT
hooks lockdep + the options that add work to syscall entry/exit:
FTRACE_SYSCALLS, KSTACK_ERASE, LKDTM, NO_HZ_FULL,
CONTEXT_TRACKING_USER (booted nohz_full=2-3)
rt hooks + PREEMPT_RT
and ran userspace tests from both an AArch64 and an AArch32 (armhf)
userspace, since the latter takes the is_compat_task() side of
ptrace_save_reg() and the cover letter didn't mentioned COMPAT.
Results were identical on base and patched (pass/fail/skip):
seccomp_bpf 98/1/12 (AArch64), 94/2/15 (AArch32)
ptrace selftests get/set_syscall_info, peeksiginfo,
vmaccess: no change
breakpoint_test_arm64 213/0/0
rseq basic, percpu_ops pass
KUnit (KUNIT_ALL_TESTS) ~1500 results, no change
MTE selftests 117/0/0 (but see below)
strace 6.18 test suite 81/6/0 (ptrace/seccomp tests)
audit (a0 filter) pass
syscall tracepoints pass (argument recorded correctly)
lkdtm KSTACK_ERASE pass
lkdtm stack-entropy.sh 7 bits
lockdep/RCU 0 splats in lockdep, hooks, and rt
The only delta I could find was the expected one:
sysemu_singlestep (new test) FAIL on base, pass on patched
So patch 1 fixes the described bug, but it had no in-tree test, so I
wrote one. I'll send that separately.
Various things I noticed along the way:
- The set of traceable symbols on the syscall path changes:
removed: syscall_trace_enter, syscall_trace_exit,
el0_svc_common.constprop.0
added: trace_syscall_enter, trace_syscall_exit,
syscall_enter_audit
These aren't ABI, and the new names are the same ones x86,
riscv, loongarch, and s390 already expose, so this is really an
improvement. But it might be worth a sentence in the cover letter,
since patch 13's "[Compatibility]" note could be read as "nothing
observable changes", which isn't _strictly_ true. :)
- Syscall-path stack use grows by 48 bytes. Base do_el0_svc() has a
16-byte frame and calls el0_svc_common() with a 48-byte frame; with
patch 14 inlining it (plus the __always_inline generic enter/exit
helpers), do_el0_svc() grows from 48 to 808 bytes and 1 to 13 calls,
with a single 112-byte frame: 112 - 64 = 48. lkdtm KSTACK_ERASE with
randomize_kstack_offset=off confirms exactly that at runtime (2288 vs
2336 bytes, ten samples, no variance), on both userspaces and in
both the hooks and rt configs. It's small, but it's not mentioned as
a (minor) trade-off for patch 14's ~1% speedup. Other size deltas:
.text +12K, ptrace.o -3.6K, and +3.1K of shared
kernel/entry/syscall-common.o (all seems expected/unremarkable).
- Patch 14 also moves that inlined code into .noinstr.text (+896
bytes). Since arm64 doesn't have objtool noinstr validation like x86,
I checked the images directly: no calls from .noinstr.text to
instrumentation outside the noinstr range, and no __mcount_loc entry
falls inside it, on either kernel. But we don't seem to have anything
that will continue to enforce this?
- CONFIG_DEBUG_RSEQ "depends on ... && !GENERIC_ENTRY", so patch 13
makes it unselectable on arm64 and rseq_syscall() becomes the no-op
stub. Patches 5 and 9 carefully reposition that call, and then patch
13 removes its effect. Not a bug (generic entry does the equivalent
via rseq_debug_enabled, and I can see __rseq_debug_syscall_return()
called from the new do_el0_svc()), but patch 5 reads like a
standalone fix that disappears eight patches later, so a note in the
cover letter might help make sense of this?
- The cover letter includes the gvisor SUD numbers, but SUD needs
ARCH_SUPPORTS_SYSCALL_USER_DISPATCH (as well as GENERIC_ENTRY),
and with the SUD patch dropped in v18, arm64 doesn't select it,
so CONFIG_SYSCALL_USER_DISPATCH can't be enabled yet. (rseq slice
extension is similarly blocked on HAVE_GENERIC_TIF_BITS.) A reader
could take those numbers as something this series delivers.
- arm64 defconfig has FTRACE_SYSCALLS=n, which means
SYSCALL_WORK_SYSCALL_TRACEPOINT can never be set and the tracepoint
paths touched by patches 2, 3, and 9 aren't even built. So anyone
testing with defconfig isn't exercising those; you may want to test
with it enabled. (Also, compat tasks never hit syscall tracepoints
on arm64 at all, due to ARCH_TRACE_IGNORE_COMPAT_SYSCALLS, so that
path can only be covered by native tasks, but that's unchanged by
the series.)
- I verified that the entry work ordering is unchanged. It looks
correct to me: arm64 did ptrace, seccomp, tracepoint, audit; generic
entry does the same. (It also checks SUD and rseq-slice before those,
but neither can be enabled on arm64 yet.)
Suggestions:
- rr: Since you have real hardware, could you also run the rr test
suite? rr has been the most sensitive ptrace user I've encountered,
and it depends on exactly the syscall-stop and single-step behavior
this series touches. I couldn't run it: QEMU's TCG PMU exposes PMUv3
but only counts CPU_CYCLES (BR_RETIRED always reads 0, on every
CPU model I tried), so rr aborts in check_working_counters(). If
you do, note that you'll need "proc_mem.force_override=always" (or
CONFIG_PROC_MEM_ALWAYS_FORCE=y), or three rr tests fail for unrelated
reasons (rr-debugger/rr#4093). Build and test instructions are here:
https://github.com/rr-debugger/rr/wiki/Building-And-Installing#tests
- MTE: I didn't see MTE in the cover letter's test list, and it seems
relevant since patch 13 puts _TIF_MTE_ASYNC_FAULT into
ARCH_EXIT_TO_USER_MODE_WORK, so an async tag check fault lands
on the reworked exit path. The MTE selftests showed no differences,
but a third of them don't actually run for me under QEMU, so they'd
be worth running on your hardware. (Testing MTE under QEMU saw
check_mmap_options hang at test 5, the first test with tag checking
on, and check_child_memory never produces output, on both base and
patched, so only 117 of the 183 planned MTE tests ran. But this is,
of course, a QEMU/selftest issue unrelated to the generic entry.)
So, for the series:
Tested-by: Kees Cook <kees@kernel.org>
-Kees
--
Kees Cook
在 2026/9/23 2:29, Kees Cook 写道: > On Tue, Sep 22, 2026 at 11:54:56AM +0800, Jinjie Ruan wrote: >> This series converts arm64 to the generic entry infrastructure. > > With my trusty LLM driving a bunch of orchestration, I gave this > a fairly wide before/after run under qemu-system-aarch64, trying > to cover things beyond the ptrace/breakpoints/abi/fp/vDSO list in > the cover letter. But since this is all emulated, it's mainly basic > correctness coverage, without any meaningful concurrency coverage. I > just wanted to find stuff that maybe hadn't been exercised yet. > > tl;dr: the only behavioral difference I could find anywhere is what > patch 1 fixed, and I found no meaningful regressions. > > Setup: the series applies cleanly to v7.3-rc2 (I only noticed later > the series was actually based on -rc4), so "base" is v7.3-rc2 and > "patched" is the same tree plus the 14 patches, with each pair built > from the same config with GCC 16.1.0. The guest was QEMU 11.1.0: > > -M virt,gic-version=max,mte=on -cpu max,pauth-impdef=on -smp 4 > > I used four kernel configs, each built for both base and patched: > > plain defconfig + SECCOMP, USER_NS, AUDITSYSCALL, KUNIT_ALL_TESTS > lockdep plain + PROVE_LOCKING, TRACE_IRQFLAGS, PROVE_RCU, > DEBUG_ATOMIC_SLEEP, DEBUG_PREEMPT > hooks lockdep + the options that add work to syscall entry/exit: > FTRACE_SYSCALLS, KSTACK_ERASE, LKDTM, NO_HZ_FULL, > CONTEXT_TRACKING_USER (booted nohz_full=2-3) > rt hooks + PREEMPT_RT > > and ran userspace tests from both an AArch64 and an AArch32 (armhf) > userspace, since the latter takes the is_compat_task() side of > ptrace_save_reg() and the cover letter didn't mentioned COMPAT. > > Results were identical on base and patched (pass/fail/skip): > > seccomp_bpf 98/1/12 (AArch64), 94/2/15 (AArch32) > ptrace selftests get/set_syscall_info, peeksiginfo, > vmaccess: no change > breakpoint_test_arm64 213/0/0 > rseq basic, percpu_ops pass > KUnit (KUNIT_ALL_TESTS) ~1500 results, no change > MTE selftests 117/0/0 (but see below) > strace 6.18 test suite 81/6/0 (ptrace/seccomp tests) > audit (a0 filter) pass > syscall tracepoints pass (argument recorded correctly) > lkdtm KSTACK_ERASE pass > lkdtm stack-entropy.sh 7 bits > lockdep/RCU 0 splats in lockdep, hooks, and rt > > The only delta I could find was the expected one: > > sysemu_singlestep (new test) FAIL on base, pass on patched > > So patch 1 fixes the described bug, but it had no in-tree test, so I > wrote one. I'll send that separately. > > Various things I noticed along the way: > > - The set of traceable symbols on the syscall path changes: > > removed: syscall_trace_enter, syscall_trace_exit, > el0_svc_common.constprop.0 > added: trace_syscall_enter, trace_syscall_exit, > syscall_enter_audit > > These aren't ABI, and the new names are the same ones x86, > riscv, loongarch, and s390 already expose, so this is really an > improvement. But it might be worth a sentence in the cover letter, > since patch 13's "[Compatibility]" note could be read as "nothing > observable changes", which isn't _strictly_ true. :) > > - Syscall-path stack use grows by 48 bytes. Base do_el0_svc() has a > 16-byte frame and calls el0_svc_common() with a 48-byte frame; with > patch 14 inlining it (plus the __always_inline generic enter/exit > helpers), do_el0_svc() grows from 48 to 808 bytes and 1 to 13 calls, > with a single 112-byte frame: 112 - 64 = 48. lkdtm KSTACK_ERASE with > randomize_kstack_offset=off confirms exactly that at runtime (2288 vs > 2336 bytes, ten samples, no variance), on both userspaces and in > both the hooks and rt configs. It's small, but it's not mentioned as > a (minor) trade-off for patch 14's ~1% speedup. Other size deltas: > .text +12K, ptrace.o -3.6K, and +3.1K of shared > kernel/entry/syscall-common.o (all seems expected/unremarkable). > > - Patch 14 also moves that inlined code into .noinstr.text (+896 > bytes). Since arm64 doesn't have objtool noinstr validation like x86, > I checked the images directly: no calls from .noinstr.text to > instrumentation outside the noinstr range, and no __mcount_loc entry > falls inside it, on either kernel. But we don't seem to have anything > that will continue to enforce this? > > - CONFIG_DEBUG_RSEQ "depends on ... && !GENERIC_ENTRY", so patch 13 > makes it unselectable on arm64 and rseq_syscall() becomes the no-op > stub. Patches 5 and 9 carefully reposition that call, and then patch > 13 removes its effect. Not a bug (generic entry does the equivalent > via rseq_debug_enabled, and I can see __rseq_debug_syscall_return() > called from the new do_el0_svc()), but patch 5 reads like a > standalone fix that disappears eight patches later, so a note in the > cover letter might help make sense of this? > > - The cover letter includes the gvisor SUD numbers, but SUD needs > ARCH_SUPPORTS_SYSCALL_USER_DISPATCH (as well as GENERIC_ENTRY), > and with the SUD patch dropped in v18, arm64 doesn't select it, > so CONFIG_SYSCALL_USER_DISPATCH can't be enabled yet. (rseq slice > extension is similarly blocked on HAVE_GENERIC_TIF_BITS.) A reader > could take those numbers as something this series delivers. > > - arm64 defconfig has FTRACE_SYSCALLS=n, which means > SYSCALL_WORK_SYSCALL_TRACEPOINT can never be set and the tracepoint > paths touched by patches 2, 3, and 9 aren't even built. So anyone > testing with defconfig isn't exercising those; you may want to test > with it enabled. (Also, compat tasks never hit syscall tracepoints > on arm64 at all, due to ARCH_TRACE_IGNORE_COMPAT_SYSCALLS, so that > path can only be covered by native tasks, but that's unchanged by > the series.) > > - I verified that the entry work ordering is unchanged. It looks > correct to me: arm64 did ptrace, seccomp, tracepoint, audit; generic > entry does the same. (It also checks SUD and rseq-slice before those, > but neither can be enabled on arm64 yet.) > > Suggestions: > > - rr: Since you have real hardware, could you also run the rr test > suite? rr has been the most sensitive ptrace user I've encountered, > and it depends on exactly the syscall-stop and single-step behavior > this series touches. I couldn't run it: QEMU's TCG PMU exposes PMUv3 > but only counts CPU_CYCLES (BR_RETIRED always reads 0, on every > CPU model I tried), so rr aborts in check_working_counters(). If > you do, note that you'll need "proc_mem.force_override=always" (or > CONFIG_PROC_MEM_ALWAYS_FORCE=y), or three rr tests fail for unrelated > reasons (rr-debugger/rr#4093). Build and test instructions are here: > https://github.com/rr-debugger/rr/wiki/Building-And-Installing#tests Hi Kees, Thank you for your detailed testing. I tested the rr test cases on a Kunpeng HIP09 (with minor adaptations, since Kunpeng CPUs aren't supported by default), and the results showed no regressions. | | 7.3-rc4 baseline | Patched | | ------------ |------------------------ | ------------------------- | | result | 87% passed, 190 failed | 88% passed, 186 failed | | Failure Mode | 7.3-rc4 baseline | Patched | | ------------ | ---------------- | ---------- | | TICK_MISMATCH | 76 | 74 | | TIMEOUT | 68 | 65 | | GDB_SCRIPT | 34 | 37 | | PERF_EVENT_OPEN| 3 | 2 | | OVERSHOOT | 1 | 0 | | OTHER | 8 | 8 | | -------------- | --------------- | ---------- | | Total | 190 | 186 | > > - MTE: I didn't see MTE in the cover letter's test list, and it seems > relevant since patch 13 puts _TIF_MTE_ASYNC_FAULT into > ARCH_EXIT_TO_USER_MODE_WORK, so an async tag check fault lands > on the reworked exit path. The MTE selftests showed no differences, > but a third of them don't actually run for me under QEMU, so they'd > be worth running on your hardware. (Testing MTE under QEMU saw Turns out our arm64 machines don't support MTE; I was testing it on QEMU. > check_mmap_options hang at test 5, the first test with tag checking > on, and check_child_memory never produces output, on both base and > patched, so only 117 of the 183 planned MTE tests ran. But this is, > of course, a QEMU/selftest issue unrelated to the generic entry.) > > So, for the series: > > Tested-by: Kees Cook <kees@kernel.org> > > -Kees > -- Best regards, Jinjie
© 2016 - 2026 Red Hat, Inc.