M3 step 3: syscall/sysret fast path
Per-core MSR setup (EFER.SCE, STAR, LSTAR, SFMASK) enables syscall; the GDT layout was already chosen so sysret lands on CS 0x23 / SS 0x1B. A new syscall_entry stub swaps in the kernel GS, switches to the task's kernel stack via the per-CPU block, builds a CpuState frame identical to the interrupt path's, and reuses interruptDispatch (vector 128) — then sysretq back. enter_user now also publishes kernel_rsp so the borrowed path's syscalls land on a good stack. /sbin/init uses the `syscall` instruction. int 0x80 stays for the test blobs. Suite 27/27. (Noted in the stub: sysretq #GPs in ring 0 on a non-canonical return RIP — a hardening item once untrusted user code exists.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8a7235c725
commit
5d57e7b01c
|
|
@ -14,12 +14,14 @@ const sys_exit = 0;
|
||||||
const sys_write = 2;
|
const sys_write = 2;
|
||||||
|
|
||||||
fn syscall2(n: u64, a: u64, b: u64) u64 {
|
fn syscall2(n: u64, a: u64, b: u64) u64 {
|
||||||
return asm volatile ("int $0x80"
|
// The `syscall` instruction clobbers RCX (return RIP) and R11 (saved RFLAGS);
|
||||||
|
// the kernel entry stub preserves everything else.
|
||||||
|
return asm volatile ("syscall"
|
||||||
: [ret] "={rax}" (-> u64),
|
: [ret] "={rax}" (-> u64),
|
||||||
: [n] "{rax}" (n),
|
: [n] "{rax}" (n),
|
||||||
[a] "{rdi}" (a),
|
[a] "{rdi}" (a),
|
||||||
[b] "{rsi}" (b),
|
[b] "{rsi}" (b),
|
||||||
: .{ .memory = true });
|
: .{ .rcx = true, .r11 = true, .memory = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(msg: []const u8) void {
|
fn write(msg: []const u8) void {
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ pub fn init() void {
|
||||||
gdt.init();
|
gdt.init();
|
||||||
tss.init();
|
tss.init();
|
||||||
idt.init();
|
idt.init();
|
||||||
|
pcpu.initSyscall();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the kernel's own page tables (with real permissions) and switch onto
|
/// Build the kernel's own page tables (with real permissions) and switch onto
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ enter_user:
|
||||||
push %r15
|
push %r15
|
||||||
mov %rsp, user_saved_rsp(%rip) # where user_exit_to_kernel unwinds to
|
mov %rsp, user_saved_rsp(%rip) # where user_exit_to_kernel unwinds to
|
||||||
mov %rsp, (%rdx) # TSS.rsp0: ring-3 interrupts stack here
|
mov %rsp, (%rdx) # TSS.rsp0: ring-3 interrupts stack here
|
||||||
|
mov %rsp, %gs:0 # kernel_rsp: the syscall stub stacks here too
|
||||||
push $0x1B # user SS (0x18 | RPL 3)
|
push $0x1B # user SS (0x18 | RPL 3)
|
||||||
push %rsi # user RSP
|
push %rsi # user RSP
|
||||||
push $0x202 # RFLAGS: IF | reserved-1
|
push $0x202 # RFLAGS: IF | reserved-1
|
||||||
|
|
@ -140,6 +141,69 @@ user_exit_to_kernel:
|
||||||
pop %rbx
|
pop %rbx
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
# syscall_entry: the target of the SYSCALL instruction (LSTAR). The CPU does NOT
|
||||||
|
# switch stacks — it puts the return RIP in RCX, the saved RFLAGS in R11, loads
|
||||||
|
# CS/SS from STAR, masks RFLAGS with SFMASK (so IF is already clear), and jumps
|
||||||
|
# here with RSP still the *user* stack. We swap in the kernel GS, switch to the
|
||||||
|
# task's kernel stack via the per-CPU block, build a CpuState frame identical to
|
||||||
|
# the interrupt path's, and reuse interruptDispatch (vector 128) — then SYSRET.
|
||||||
|
#
|
||||||
|
# Hazard (acceptable while init is the only, trusted, user program): SYSRETQ #GPs
|
||||||
|
# in ring 0 if the return RIP (RCX) is non-canonical. A hostile user could arrange
|
||||||
|
# that; hardening (canonical check / iretq fallback) is a later security-track item.
|
||||||
|
.global syscall_entry
|
||||||
|
syscall_entry:
|
||||||
|
swapgs # kernel GS base
|
||||||
|
movq %rsp, %gs:8 # stash user rsp in the scratch slot
|
||||||
|
movq %gs:0, %rsp # switch to this task's kernel stack
|
||||||
|
# Build the trap frame (same field order as isr_common), highest field first.
|
||||||
|
pushq $0x1B # ss (user data | 3)
|
||||||
|
pushq %gs:8 # rsp (user, from scratch)
|
||||||
|
pushq %r11 # rflags (saved by syscall)
|
||||||
|
pushq $0x23 # cs (user code | 3)
|
||||||
|
pushq %rcx # rip (saved by syscall)
|
||||||
|
pushq $0 # error_code (none for a syscall)
|
||||||
|
pushq $128 # vector (same as the int 0x80 gate)
|
||||||
|
push %rax
|
||||||
|
push %rbx
|
||||||
|
push %rcx
|
||||||
|
push %rdx
|
||||||
|
push %rsi
|
||||||
|
push %rdi
|
||||||
|
push %rbp
|
||||||
|
push %r8
|
||||||
|
push %r9
|
||||||
|
push %r10
|
||||||
|
push %r11
|
||||||
|
push %r12
|
||||||
|
push %r13
|
||||||
|
push %r14
|
||||||
|
push %r15
|
||||||
|
mov %rsp, %rdi # trap-frame pointer
|
||||||
|
call interruptDispatch
|
||||||
|
pop %r15
|
||||||
|
pop %r14
|
||||||
|
pop %r13
|
||||||
|
pop %r12
|
||||||
|
pop %r11
|
||||||
|
pop %r10
|
||||||
|
pop %r9
|
||||||
|
pop %r8
|
||||||
|
pop %rbp
|
||||||
|
pop %rdi
|
||||||
|
pop %rsi
|
||||||
|
pop %rdx
|
||||||
|
pop %rcx
|
||||||
|
pop %rbx
|
||||||
|
pop %rax
|
||||||
|
add $16, %rsp # drop vector + error_code -> rsp at rip
|
||||||
|
popq %rcx # rip -> RCX (SYSRETQ restores RIP from RCX)
|
||||||
|
addq $8, %rsp # skip the cs slot (SYSRETQ loads CS from STAR)
|
||||||
|
popq %r11 # rflags -> R11 (SYSRETQ restores RFLAGS from R11)
|
||||||
|
popq %rsp # user rsp (the ss slot below is abandoned)
|
||||||
|
swapgs # user GS base
|
||||||
|
sysretq # -> ring 3: RIP=RCX, RFLAGS=R11, CS/SS from STAR
|
||||||
|
|
||||||
.section .bss
|
.section .bss
|
||||||
.balign 8
|
.balign 8
|
||||||
user_saved_rsp:
|
user_saved_rsp:
|
||||||
|
|
|
||||||
|
|
@ -54,3 +54,24 @@ pub fn sched() usize {
|
||||||
pub fn setKernelRsp(index: usize, top: usize) void {
|
pub fn setKernelRsp(index: usize, top: usize) void {
|
||||||
blocks[index].kernel_rsp = top;
|
blocks[index].kernel_rsp = top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast-syscall MSRs.
|
||||||
|
const ia32_efer = 0xC000_0080;
|
||||||
|
const ia32_star = 0xC000_0081;
|
||||||
|
const ia32_lstar = 0xC000_0082;
|
||||||
|
const ia32_sfmask = 0xC000_0084;
|
||||||
|
|
||||||
|
/// Enable the `syscall`/`sysret` fast path on this core (BSP and each AP). EFER.SCE
|
||||||
|
/// turns the instructions on; STAR sets the selectors syscall/sysret load; LSTAR
|
||||||
|
/// is the entry stub (isr.s); SFMASK clears RFLAGS bits on entry (notably IF —
|
||||||
|
/// the handler runs with interrupts off, like the int-gate path). The GDT is laid
|
||||||
|
/// out (kernel code 0x08, then user data 0x18 / code 0x20) precisely so these line
|
||||||
|
/// up: syscall loads CS 0x08 / SS 0x10; sysret loads CS = base+16 and SS = base+8
|
||||||
|
/// with RPL forced to 3, so base 0x10 gives CS 0x23 (user code|3) and SS 0x1B.
|
||||||
|
pub fn initSyscall() void {
|
||||||
|
io.wrmsr(ia32_efer, io.rdmsr(ia32_efer) | 1); // SCE
|
||||||
|
io.wrmsr(ia32_star, (@as(u64, 0x08) << 32) | (@as(u64, 0x10) << 48));
|
||||||
|
const entry = @extern(*const anyopaque, .{ .name = "syscall_entry" });
|
||||||
|
io.wrmsr(ia32_lstar, @intFromPtr(entry));
|
||||||
|
io.wrmsr(ia32_sfmask, 0x4_0700); // clear IF, TF, DF, AC on entry
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,7 @@ fn apEntry(percpu: usize) callconv(.c) noreturn {
|
||||||
tss.setupThisCpu(cpu); // this core's TSS + IST stack, loaded into TR
|
tss.setupThisCpu(cpu); // this core's TSS + IST stack, loaded into TR
|
||||||
idt.loadOnThisCpu(); // the shared IDT
|
idt.loadOnThisCpu(); // the shared IDT
|
||||||
pcpu.setLocal(cpu, percpu); // per-CPU block via GS base — *after* the GDT reload
|
pcpu.setLocal(cpu, percpu); // per-CPU block via GS base — *after* the GDT reload
|
||||||
|
pcpu.initSyscall(); // enable syscall/sysret on this core
|
||||||
|
|
||||||
apic.initSecondary(); // software-enable this core's LAPIC
|
apic.initSecondary(); // software-enable this core's LAPIC
|
||||||
apic.initTimer(apic.frequencyHz()); // arm its timer (still masked: interrupts off)
|
apic.initTimer(apic.frequencyHz()); // arm its timer (still masked: interrupts off)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue