From 65bb04d8907fcdd9a5059e40f1d0f1a17ba745bc Mon Sep 17 00:00:00 2001 From: Daniel Samson <12231216+daniel-samson@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:39:23 +0100 Subject: [PATCH] kernel: preserve SSE/FPU (XMM) state across the syscall/interrupt boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kernel enabled SSE at boot and both kernel and userspace keep live values in XMM (LLVM emits movdqu/movaps for >=16-byte struct copies, plus floats and SIMD), yet the kernel never saved the SSE/FPU register file anywhere — not across switch_context, not across the syscall boundary, not across interrupts. Any value the compiler parked in an XMM register across a kernel entry could be silently clobbered by kernel code, or by whatever the scheduler ran while the task blocked: a whole-kernel, timing-dependent data-corruption bug. It was the real root cause of the "device_enumerate corruption" Heisenbug: the display service read garbage framebuffer geometry (height=0) because findDisplay held the 16-byte .display field live in xmm0 across the claim/mmio_map syscalls, and a timer preemption to the busy device-manager clobbered it. The tell that it was register-only: memory always read correct, and the bug vanished whenever an added syscall spilled the value to the stack. Fix: fxsave/fxrstor the register file in the asm stubs (isr_common and syscall_entry), right after pushing the GP trap frame — before any Zig kernel code can touch XMM — and right before the pops. rbx bridges the exact rsp across the call to interruptDispatch: it is callee-saved, so it survives even a blocking dispatch that context-switches away and back, and `and $-16,%rsp; sub $512,%rsp` gives fxsave its 16-byte-aligned scratch on the kernel stack. Context-switch-time save/restore alone is not enough — kernel code between the interrupt and switchTo already clobbers XMM. The commit-58927ed Gop.init workaround (copy scalar geometry fields rather than the whole descriptor by value) is now redundant but harmless; left in place. Deferred: a fresh task inherits the previous task's XMM (minor info-leak / nondeterminism), and the fxsave runs on every interrupt including ring0->ring0. --- system/kernel/architecture/x86_64/isr.s | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/system/kernel/architecture/x86_64/isr.s b/system/kernel/architecture/x86_64/isr.s index 6de32fc..5fb2d45 100644 --- a/system/kernel/architecture/x86_64/isr.s +++ b/system/kernel/architecture/x86_64/isr.s @@ -205,7 +205,17 @@ syscall_entry: push %r14 push %r15 mov %rsp, %rdi # trap-frame pointer + # Preserve the caller's SSE/x87 register file across the syscall — see the same + # dance in isr_common. Without it a syscall (or a task the scheduler runs while + # this one blocks) clobbers the caller's live XMM values, which the compiler is + # free to hold across a syscall (its wrappers only clobber rcx/r11/memory). + mov %rsp, %rbx + and $-16, %rsp + sub $512, %rsp + fxsave (%rsp) call interruptDispatch + fxrstor (%rsp) + mov %rbx, %rsp # back to the trap frame (undo the fxsave scratch) pop %r15 pop %r14 pop %r13 @@ -357,7 +367,21 @@ isr_common: push %r14 push %r15 mov %rsp, %rdi # first argument: pointer to the trap frame + # Save the interrupted SSE/x87 register file before any kernel code runs, and + # restore it on the way out — the kernel and user both keep live values in XMM + # (a 16-byte struct copy is a movdqu), and the kernel never otherwise preserves + # them, so an interrupt handler (and whatever the scheduler runs in its place) + # would silently clobber the interrupted task's vector registers. rbx bridges the + # exact rsp across the call: it is callee-saved (interruptDispatch and every + # context switch preserve it), so it survives even a blocking dispatch, and the + # `and`/`sub` gives fxsave its required 16-byte-aligned scratch on the kernel stack. + mov %rsp, %rbx + and $-16, %rsp + sub $512, %rsp + fxsave (%rsp) call interruptDispatch + fxrstor (%rsp) + mov %rbx, %rsp # back to the trap frame (undo the fxsave scratch) pop %r15 pop %r14 pop %r13