; syscall_entry.asm - SYSCALL instruction handler + userspace entry/exit for Pactor64 ; ; When the CPU executes SYSCALL: ; RCX = return address (user RIP) ; R11 = saved RFLAGS ; RIP = MSR_LSTAR ; CS = MSR_STAR[63:48] (= 0x08) ; SS = MSR_STAR[63:48] + 8 (= 0x10) ; ; SYSRET (with REX.W): ; RIP = RCX ; RFLAGS = R11 ; CS = MSR_STAR[47:32] | 3 (= 0x1B) ; SS = (MSR_STAR[47:32] + 8) | 3 (= 0x23) ; ; IMPORTANT: SYSCALL only clobbers RCX and R11. All other GPRs must be ; preserved across the syscall handler for the user program to work correctly. bits 64 section .text extern syscall_dispatch global syscall_entry global enter_userspace global return_to_kernel ; ============================================================================ ; syscall_entry — SYSCALL handler entry point ; ============================================================================ syscall_entry: ; Save user RSP, RFLAGS (from R11), RIP (from RCX) mov [rel user_rsp], rsp mov [rel user_rflags], r11 mov [rel user_rcx], rcx ; User RIP (return address) ; Switch to kernel stack mov rsp, syscall_stack_top ; === Save ALL user registers === ; SYSCALL clobbers RCX and R11, but all other GPRs must be preserved. ; We save everything so we can restore after dispatch. push rax ; syscall number / return value push rbx push rcx ; user RIP (already saved, but save for callee-saved) push rdx ; arg3 push rsi ; arg2 push rdi ; arg1 push rbp push r8 ; arg5 push r9 ; arg6 push r10 ; arg4 push r12 push r13 push r14 push r15 ; === Map Linux syscall ABI → C calling convention === ; Linux SYSCALL receives: ; rax = syscall number ; rdi = arg1, rsi = arg2, rdx = arg3 ; r10 = arg4, r8 = arg5, r9 = arg6 ; ; C calling convention (for syscall_dispatch): ; rdi = num, rsi = arg1, rdx = arg2, rcx = arg3 ; r8 = arg4, r9 = arg5 ; ; Move in reverse order to avoid clobbering values we still need: mov r9, r8 ; arg5 → C arg6 (r9) mov r8, r10 ; arg4 → C arg5 (r8) mov rcx, rdx ; arg3 → C arg4 (rcx) mov rdx, rsi ; arg2 → C arg3 (rdx) mov rsi, rdi ; arg1 → C arg2 (rsi) mov rdi, rax ; num → C arg1 (rdi) ; Check if this is exit(60) BEFORE calling dispatch — handle in asm cmp rdi, 60 ; SYS_EXIT? jne .not_exit ; Exit: print message and return to kernel ; rsi = exit status jmp .return_to_kernel .not_exit: call syscall_dispatch ; Return value is in rax — save it mov [rel syscall_retval], rax ; === Restore ALL user registers === ; Restore in reverse order (except rax — we'll set it to the return value) pop r15 pop r14 pop r13 pop r12 pop r10 pop r9 pop r8 pop rbp pop rdi pop rsi pop rdx pop rcx ; original user RCX (will be overwritten by SYSRET) pop rbx ; Don't pop rax yet — we need the return value ; Set return value mov rax, [rel syscall_retval] ; Restore user RSP and RFLAGS for SYSRET mov rsp, [rel user_rsp] mov r11, [rel user_rflags] mov rcx, [rel user_rcx] ; Enable interrupts on return or r11, (1 << 9) ; Set IF bit ; SYSRET returns to RCX:RIP with R11:RFLAGS o64 sysret ; ============================================================================ ; .return_to_kernel — exit syscall path ; Clean up syscall stack and return to shell. ; Called when syscall_dispatch returns -999 (exit). ; ============================================================================ .return_to_kernel: ; Clean up the 14 saved registers from the syscall stack add rsp, 14 * 8 ; Jump to return_to_kernel which restores kernel RSP and rets jmp return_to_kernel ; ============================================================================ ; enter_userspace(uint64_t user_rip, uint64_t user_rsp) ; Jump from ring 0 to ring 3 using IRETQ. ; rdi = user RIP ; rsi = user RSP ; ============================================================================ enter_userspace: ; Save kernel state for return_to_kernel mov [rel saved_kernel_rsp], rsp mov [rel saved_kernel_rbp], rbp ; Disable interrupts during IRETQ frame setup cli ; Use SYSRET to enter ring 3 — set RSP manually before SYSRET mov rsp, rsi ; User RSP mov rcx, rdi ; User RIP (SYSRET sets RIP=RCX) mov r11, 0x202 ; User RFLAGS (IF=1, bit1=1) o64 sysret return_to_kernel: cli ; Restore kernel segments push rax mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax pop rax ; Get return address and target RSP mov r8, [rel saved_kernel_rsp] mov r9, [r8] ; return address add r8, 8 ; RSP past the return addr mov rbp, [rel saved_kernel_rbp] ; Build IRETQ on syscall stack (clean CS/SS transition) mov rsp, syscall_stack_top push qword 0x10 ; SS = kernel data push r8 ; RSP = shell stack pushfq or qword [rsp], 0x200 ; IF push qword 0x08 ; CS = kernel code push r9 ; RIP = return address iretq ; Restore kernel segment registers (user code set them to ring 3 values) push rax mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax pop rax pop rax sti ret ; ============================================================================ ; Data section — saved state ; ============================================================================ section .data align 8 user_rsp: dq 0 user_rflags: dq 0 user_rcx: dq 0 saved_kernel_rsp: dq 0 saved_kernel_rbp: dq 0 syscall_retval: dq 0 ; ============================================================================ ; BSS — dedicated syscall stack (8KB) ; ============================================================================ section .bss align 16 resb 8192 ; 8KB syscall stack syscall_stack_top: