| 1 |
; syscall_entry.asm - SYSCALL instruction handler + userspace entry/exit for Pactor64 |
| 2 |
; |
| 3 |
; When the CPU executes SYSCALL: |
| 4 |
; RCX = return address (user RIP) |
| 5 |
; R11 = saved RFLAGS |
| 6 |
; RIP = MSR_LSTAR |
| 7 |
; CS = MSR_STAR[63:48] (= 0x08) |
| 8 |
; SS = MSR_STAR[63:48] + 8 (= 0x10) |
| 9 |
; |
| 10 |
; SYSRET (with REX.W): |
| 11 |
; RIP = RCX |
| 12 |
; RFLAGS = R11 |
| 13 |
; CS = MSR_STAR[47:32] | 3 (= 0x1B) |
| 14 |
; SS = (MSR_STAR[47:32] + 8) | 3 (= 0x23) |
| 15 |
; |
| 16 |
; IMPORTANT: SYSCALL only clobbers RCX and R11. All other GPRs must be |
| 17 |
; preserved across the syscall handler for the user program to work correctly. |
| 18 |
|
| 19 |
bits 64 |
| 20 |
section .text |
| 21 |
|
| 22 |
extern syscall_dispatch |
| 23 |
|
| 24 |
global syscall_entry |
| 25 |
global enter_userspace |
| 26 |
global return_to_kernel |
| 27 |
|
| 28 |
; ============================================================================ |
| 29 |
; syscall_entry — SYSCALL handler entry point |
| 30 |
; ============================================================================ |
| 31 |
syscall_entry: |
| 32 |
; Save user RSP, RFLAGS (from R11), RIP (from RCX) |
| 33 |
mov [rel user_rsp], rsp |
| 34 |
mov [rel user_rflags], r11 |
| 35 |
mov [rel user_rcx], rcx ; User RIP (return address) |
| 36 |
|
| 37 |
; Switch to kernel stack |
| 38 |
mov rsp, syscall_stack_top |
| 39 |
|
| 40 |
; === Save ALL user registers === |
| 41 |
; SYSCALL clobbers RCX and R11, but all other GPRs must be preserved. |
| 42 |
; We save everything so we can restore after dispatch. |
| 43 |
push rax ; syscall number / return value |
| 44 |
push rbx |
| 45 |
push rcx ; user RIP (already saved, but save for callee-saved) |
| 46 |
push rdx ; arg3 |
| 47 |
push rsi ; arg2 |
| 48 |
push rdi ; arg1 |
| 49 |
push rbp |
| 50 |
push r8 ; arg5 |
| 51 |
push r9 ; arg6 |
| 52 |
push r10 ; arg4 |
| 53 |
push r12 |
| 54 |
push r13 |
| 55 |
push r14 |
| 56 |
push r15 |
| 57 |
|
| 58 |
; === Map Linux syscall ABI → C calling convention === |
| 59 |
; Linux SYSCALL receives: |
| 60 |
; rax = syscall number |
| 61 |
; rdi = arg1, rsi = arg2, rdx = arg3 |
| 62 |
; r10 = arg4, r8 = arg5, r9 = arg6 |
| 63 |
; |
| 64 |
; C calling convention (for syscall_dispatch): |
| 65 |
; rdi = num, rsi = arg1, rdx = arg2, rcx = arg3 |
| 66 |
; r8 = arg4, r9 = arg5 |
| 67 |
; |
| 68 |
; Move in reverse order to avoid clobbering values we still need: |
| 69 |
mov r9, r8 ; arg5 → C arg6 (r9) |
| 70 |
mov r8, r10 ; arg4 → C arg5 (r8) |
| 71 |
mov rcx, rdx ; arg3 → C arg4 (rcx) |
| 72 |
mov rdx, rsi ; arg2 → C arg3 (rdx) |
| 73 |
mov rsi, rdi ; arg1 → C arg2 (rsi) |
| 74 |
mov rdi, rax ; num → C arg1 (rdi) |
| 75 |
|
| 76 |
; Check if this is exit(60) BEFORE calling dispatch — handle in asm |
| 77 |
cmp rdi, 60 ; SYS_EXIT? |
| 78 |
jne .not_exit |
| 79 |
; Exit: print message and return to kernel |
| 80 |
; rsi = exit status |
| 81 |
jmp .return_to_kernel |
| 82 |
.not_exit: |
| 83 |
call syscall_dispatch |
| 84 |
; Return value is in rax — save it |
| 85 |
mov [rel syscall_retval], rax |
| 86 |
|
| 87 |
; === Restore ALL user registers === |
| 88 |
; Restore in reverse order (except rax — we'll set it to the return value) |
| 89 |
pop r15 |
| 90 |
pop r14 |
| 91 |
pop r13 |
| 92 |
pop r12 |
| 93 |
pop r10 |
| 94 |
pop r9 |
| 95 |
pop r8 |
| 96 |
pop rbp |
| 97 |
pop rdi |
| 98 |
pop rsi |
| 99 |
pop rdx |
| 100 |
pop rcx ; original user RCX (will be overwritten by SYSRET) |
| 101 |
pop rbx |
| 102 |
; Don't pop rax yet — we need the return value |
| 103 |
|
| 104 |
; Set return value |
| 105 |
mov rax, [rel syscall_retval] |
| 106 |
|
| 107 |
; Restore user RSP and RFLAGS for SYSRET |
| 108 |
mov rsp, [rel user_rsp] |
| 109 |
mov r11, [rel user_rflags] |
| 110 |
mov rcx, [rel user_rcx] |
| 111 |
|
| 112 |
; Enable interrupts on return |
| 113 |
or r11, (1 << 9) ; Set IF bit |
| 114 |
|
| 115 |
; SYSRET returns to RCX:RIP with R11:RFLAGS |
| 116 |
o64 sysret |
| 117 |
|
| 118 |
; ============================================================================ |
| 119 |
; .return_to_kernel — exit syscall path |
| 120 |
; Clean up syscall stack and return to shell. |
| 121 |
; Called when syscall_dispatch returns -999 (exit). |
| 122 |
; ============================================================================ |
| 123 |
.return_to_kernel: |
| 124 |
; Clean up the 14 saved registers from the syscall stack |
| 125 |
add rsp, 14 * 8 |
| 126 |
; Jump to return_to_kernel which restores kernel RSP and rets |
| 127 |
jmp return_to_kernel |
| 128 |
|
| 129 |
; ============================================================================ |
| 130 |
; enter_userspace(uint64_t user_rip, uint64_t user_rsp) |
| 131 |
; Jump from ring 0 to ring 3 using IRETQ. |
| 132 |
; rdi = user RIP |
| 133 |
; rsi = user RSP |
| 134 |
; ============================================================================ |
| 135 |
enter_userspace: |
| 136 |
; Save kernel state for return_to_kernel |
| 137 |
mov [rel saved_kernel_rsp], rsp |
| 138 |
mov [rel saved_kernel_rbp], rbp |
| 139 |
|
| 140 |
; Disable interrupts during IRETQ frame setup |
| 141 |
cli |
| 142 |
|
| 143 |
; Use SYSRET to enter ring 3 — set RSP manually before SYSRET |
| 144 |
mov rsp, rsi ; User RSP |
| 145 |
mov rcx, rdi ; User RIP (SYSRET sets RIP=RCX) |
| 146 |
mov r11, 0x202 ; User RFLAGS (IF=1, bit1=1) |
| 147 |
o64 sysret |
| 148 |
|
| 149 |
return_to_kernel: |
| 150 |
cli |
| 151 |
; Restore kernel segments |
| 152 |
push rax |
| 153 |
mov ax, 0x10 |
| 154 |
mov ds, ax |
| 155 |
mov es, ax |
| 156 |
mov fs, ax |
| 157 |
mov gs, ax |
| 158 |
pop rax |
| 159 |
; Get return address and target RSP |
| 160 |
mov r8, [rel saved_kernel_rsp] |
| 161 |
mov r9, [r8] ; return address |
| 162 |
add r8, 8 ; RSP past the return addr |
| 163 |
mov rbp, [rel saved_kernel_rbp] |
| 164 |
; Build IRETQ on syscall stack (clean CS/SS transition) |
| 165 |
mov rsp, syscall_stack_top |
| 166 |
push qword 0x10 ; SS = kernel data |
| 167 |
push r8 ; RSP = shell stack |
| 168 |
pushfq |
| 169 |
or qword [rsp], 0x200 ; IF |
| 170 |
push qword 0x08 ; CS = kernel code |
| 171 |
push r9 ; RIP = return address |
| 172 |
iretq |
| 173 |
|
| 174 |
|
| 175 |
|
| 176 |
; Restore kernel segment registers (user code set them to ring 3 values) |
| 177 |
push rax |
| 178 |
mov ax, 0x10 |
| 179 |
mov ds, ax |
| 180 |
mov es, ax |
| 181 |
mov fs, ax |
| 182 |
mov gs, ax |
| 183 |
pop rax |
| 184 |
pop rax |
| 185 |
|
| 186 |
sti |
| 187 |
ret |
| 188 |
|
| 189 |
; ============================================================================ |
| 190 |
; Data section — saved state |
| 191 |
; ============================================================================ |
| 192 |
section .data |
| 193 |
align 8 |
| 194 |
user_rsp: dq 0 |
| 195 |
user_rflags: dq 0 |
| 196 |
user_rcx: dq 0 |
| 197 |
saved_kernel_rsp: dq 0 |
| 198 |
saved_kernel_rbp: dq 0 |
| 199 |
syscall_retval: dq 0 |
| 200 |
|
| 201 |
; ============================================================================ |
| 202 |
; BSS — dedicated syscall stack (8KB) |
| 203 |
; ============================================================================ |
| 204 |
section .bss |
| 205 |
align 16 |
| 206 |
resb 8192 ; 8KB syscall stack |
| 207 |
syscall_stack_top: |
| 208 |
|