kernel: preserve SSE/FPU (XMM) state across the syscall/interrupt boundary
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.
This commit is contained in:
parent
24c49f56e1
commit
65bb04d890
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue