Files
ViperOS/VKernel/Kernel/intr/isr.s
T
TermiNexus 4e1be2be88 fix(intr): 修正 isr80 的调用号/arg4 压栈顺序(int 0x80 一直把 arg4 当调用号)
栈向下增长,先压的落在高地址,而要的布局是 [S+0]=调用号(rax)、[S+8]=arg4(r10),所以必须先 push r10 后 push rax;原实现反了 ⇒ mov rax,[rsp+40] 取到 arg4 当调用号、mov r8,[rsp+168] 取到调用号当 arg4 ⇒ syscall_handler 收到错误的 n。

现场(探针实测):用户线程出口跳板 mov eax,34(THREAD_EXIT); int 0x80; jmp $ —— int 0x80 带着错的 n 进 syscall_handler,不匹配任何分支返回 0,iretq 回用户态落到 jmp $ 死转。该线程永不再 yield ⇒ needs_reschedule 无人消费 ⇒ 全系统停止切换:探针显示 cur 冻结在 5、t5 恒为 RUNNING,intr_cs=2B intr_rip=0x7FFFFFDE0007(正是跳板页偏移 7 的 jmp $)rsp=0x7FFFFFDE0000 ⇒ 桌面线程(tid 6)饿死,[perf]/[chk] 永不再打印。

这条路径此前无任何调用者(vpsdk 全走 syscall 指令),所以从未暴露。同时移除定位用的临时探针(timer.vp 心跳/调度器快照、isr.s 的 _intr_*)。
2026-10-16 18:07:57 +08:00

647 lines
22 KiB
ArmAsm
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.section .bss
.comm _per_cpu, 32, 8
.comm _proc_mgr, 8192, 16
.comm _proc_mgr_ptr, 8, 8
.comm _saved_cr3, 8, 8
.comm _yield_cnt, 4, 4
.comm _yield_lock, 4, 4
.comm _fs_lock, 4, 4
# 【诊断】异常现场(#GP/#PF 等带错误码的向量在 ISR_ERR 入口保存,isrCommonhandler 打印)。
# 只保留最小三件套:出错 RIP / 错误码 / 出错时的 RSP(#PF 另读 cr2)。
.comm _fault_rip, 8, 8
.comm _fault_err, 8, 8
.comm _fault_rsp, 8, 8
# ISR 入口暂存 caller-saved 寄存器(rax/rcx/r8-r11):本宏会用它们当临时寄存器
# (读 RIP/CS/RFLAGS、算 is_user),若不保存就会破坏被中断代码在这些寄存器里的
# 活跃值 → 概率性内存破坏/异常。出口(iretq 前)恢复。
.comm _isr_scratch, 48, 8
.section .text
.intel_syntax noprefix
.globl isr_default
isr_default:
push rax
push rdx
mov al, 0x20
out 0xA0, al
out 0x20, al
pop rdx
pop rax
iretq
# =============================================================================
# ISR macros with Ring 3 support
#
# When CPU takes interrupt/exception:
# From Ring 0: pushes RFLAGS, CS, RIP (3 qwords)
# From Ring 3: pushes SS, RSP, RFLAGS, CS, RIP (5 qwords)
# CPU also switches to TSS RSP0 stack when coming from Ring 3.
#
# Strategy:
# 1. Check CS RPL to determine if from Ring 3
# 2. If from Ring 3: swapgs (make gs: point to per_cpu), save user RSP
# 3. Save full interrupt frame into registers
# 4. Re-push in uniform format with is_user flag
# 5. Call handler
# 6. On return, rebuild correct iretq frame based on is_user
# 7. If returning to Ring 3: swapgs back
# =============================================================================
# 保存/恢复被中断代码的 caller-saved 寄存器(本 ISR 宏会用它们当临时寄存器)。
# 若不保存,中断落在“这些寄存器里存有活跃值”的瞬间就会破坏该值(新版编译器
# 寄存器分配会踩到 → 概率性内存破坏、printf 变参错乱、#GP)。需成对使用:
# 入口第一件事 ISR_SAVE_CLOBBERED;各返回路径在 push 好 iretq 帧之后 ISR_RESTORE_CLOBBERED。
# (iretq 从栈取帧,与寄存器无关,故恢复寄存器不影响返回。)
.macro ISR_SAVE_CLOBBERED
mov [_isr_scratch], rax
mov [_isr_scratch + 8], rcx
mov [_isr_scratch + 16], r8
mov [_isr_scratch + 24], r9
mov [_isr_scratch + 32], r10
mov [_isr_scratch + 40], r11
.endm
.macro ISR_RESTORE_CLOBBERED
mov rax, [_isr_scratch]
mov rcx, [_isr_scratch + 8]
mov r8, [_isr_scratch + 16]
mov r9, [_isr_scratch + 24]
mov r10, [_isr_scratch + 32]
mov r11, [_isr_scratch + 40]
.endm
.macro ISR_NOERR num
.globl isr\num
isr\num:
ISR_SAVE_CLOBBERED
# Check CS RPL to determine if from Ring 3
mov rax, [rsp + 8] # CS from interrupt frame
test al, 3 # RPL bits: 0 = Ring 0, 3 = Ring 3
jnz isr\num\()_from_user
isr\num\()_from_kernel:
# From Ring 0: no swapgs needed, already on kernel stack
mov r8, [rsp] # RIP
mov r9, [rsp + 8] # CS
mov r10, [rsp + 16] # RFLAGS
xor r11, r11 # SS = 0
xor rax, rax # RSP = 0
add rsp, 24 # remove the 3 CPU-pushed qwords
jmp isr\num\()_frame_ready
isr\num\()_from_user:
# From Ring 3: swapgs to make gs: accessible, CPU already switched to TSS RSP0
swapgs
# Save user RSP to per_cpu.user_rsp (gs:[0x0])
# Note: CPU has already switched RSP to TSS RSP0, so the user RSP is on this stack
mov r8, [rsp] # RIP
mov r9, [rsp + 8] # CS
mov r10, [rsp + 16] # RFLAGS
mov rax, [rsp + 24] # RSP (user)
mov r11, [rsp + 32] # SS (user)
# 不再把用户 RSP 写进 per_cpu.user_rsp:该槽已无读者(syscall 返回改为从本线程内核栈取),
# 而它是"每 CPU 一个" —— 别的线程写它会让"正挂起的 syscall"返回时拿到别人的栈。
add rsp, 40 # remove the 5 CPU-pushed qwords
isr\num\()_frame_ready:
# r8=RIP, r9=CS, r10=RFLAGS, rax=RSP(0 if kernel), r11=SS(0 if kernel)
# Build uniform frame on stack for iretq later
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
# Push is_user flag (1 if from Ring 3, 0 if from Ring 0)
xor ecx, ecx
test r9b, 3 # test CS RPL
setnz cl # cl = 1 if from Ring 3
push rcx # is_user
# Push all general-purpose registers
push rax
push rbx
push rdx
push rsi
push rdi
push rbp
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
call isr\num\()_handler
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 rbx
pop rax
# Pop is_user flag
pop rcx # is_user
# Pop the uniform frame into registers
pop r8 # RIP
pop r9 # CS
pop r10 # RFLAGS
pop rax # RSP (user)
pop r11 # SS (user)
test rcx, rcx
jnz isr\num\()_ret_user
isr\num\()_ret_kernel:
# Return to Ring 0: push RFLAGS, CS, RIP only
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
ISR_RESTORE_CLOBBERED
iretq
isr\num\()_ret_user:
# Return to Ring 3: swapgs back, then push SS, RSP, RFLAGS, CS, RIP
swapgs
# DIAG+FIX: if RFLAGS.NT (bit 14) is set, iretq performs hardware task-return
# which loads TSS selector 0x28 -> #GP(err=0x28). Detect and clear it.
test r10, 0x4000
jz 1f
btr r10, 14
mov dx, 0x3F8
mov al, 0x4E # 'N' marker = NT was set (now cleared)
out dx, al
1:
# 用户态 CS 现在统一是 0x2B(gdt.vp 已把用户代码段放到 GDT 第 5 项 = 0x28:
# · sysretq 硬件按 STAR[63:48]+16 算 CS=0x2B、SS=+8=0x23;
# · drop_to_user_mode 首次进用户态也改用 0x2B iretq,两条入口一致。
# 仍兼容接受 0x1B:GDT 第 3 项保留了一份同样的用户代码镜像(早期路径用过),
# 避免任何残留路径被误判“坏 CS/SS” → 原逻辑只认 0x1B,导致 app 发过 syscall 后
# 任何用户态中断/异常都走 cli;hlt 假死,并掩盖真正的异常现场。
cmp r11, 0x23
jne 8f
cmp r9, 0x1B
je 6f
cmp r9, 0x2B
jne 8f
6:
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
ISR_RESTORE_CLOBBERED
iretq
8:
mov dx, 0x3F8
mov al, 0x42 # 'B' = bad CS/SS
out dx, al
mov al, 0x20 # space
out dx, al
# Output CS (r9) low byte as 2 hex digits
mov rcx, r9
mov rax, rcx
shr rax, 4
and rax, 0xF
add al, 0x30
cmp al, 0x39
jle 2f
add al, 7
2:
out dx, al
mov rax, rcx
and rax, 0xF
add al, 0x30
cmp al, 0x39
jle 3f
add al, 7
3:
out dx, al
mov al, 0x20 # space
out dx, al
# Output SS (r11) low byte as 2 hex digits
mov rcx, r11
mov rax, rcx
shr rax, 4
and rax, 0xF
add al, 0x30
cmp al, 0x39
jle 4f
add al, 7
4:
out dx, al
mov rax, rcx
and rax, 0xF
add al, 0x30
cmp al, 0x39
jle 5f
add al, 7
5:
out dx, al
mov al, 0x0A # newline
out dx, al
cli
7:
hlt
jmp 7b
.endm
.macro ISR_ERR num
.globl isr\num
isr\num:
ISR_SAVE_CLOBBERED
# Stack at entry (with error code):
# From Ring 0: [rsp]=error_code, [rsp+8]=RIP, [rsp+16]=CS, [rsp+24]=RFLAGS
# From Ring 3: [rsp]=error_code, [rsp+8]=RIP, [rsp+16]=CS, [rsp+24]=RFLAGS, [rsp+32]=RSP, [rsp+40]=SS
# Check CS RPL
mov rax, [rsp + 16] # CS from interrupt frame
test al, 3
jnz isr\num\()_from_user
isr\num\()_from_kernel:
# From Ring 0: no swapgs needed
mov rdx, [rsp] # error_code
mov r8, [rsp + 8] # RIP
mov r9, [rsp + 16] # CS
mov r10, [rsp + 24] # RFLAGS
xor r11, r11 # SS = 0
xor rax, rax # RSP = 0
# 【诊断】保存异常现场:rdx=错误码 / r8=RIP;出错 RSP = 入口 rsp + 32(err+RIP+CS+RFLAGS)
mov [_fault_rip], r8
mov [_fault_err], rdx
lea rcx, [rsp + 32]
mov [_fault_rsp], rcx
add rsp, 32 # remove 4 CPU-pushed qwords (error + 3 frame)
jmp isr\num\()_frame_ready
isr\num\()_from_user:
# From Ring 3: swapgs, CPU already on TSS RSP0
swapgs
mov rdx, [rsp] # error_code
mov r8, [rsp + 8] # RIP
mov r9, [rsp + 16] # CS
mov r10, [rsp + 24] # RFLAGS
mov rax, [rsp + 32] # RSP (user)
mov r11, [rsp + 40] # SS (user)
# 【诊断】同 kernel 路径:rdx=错误码 / r8=RIP;用户态出错 RSP 就是被压下的用户 RSP
mov [_fault_rip], r8
mov [_fault_err], rdx
mov [_fault_rsp], rax
# 同上:不再写 per_cpu.user_rsp(该槽已无读者,且是每 CPU 一个 ⇒ 会串台)
add rsp, 48 # remove 6 CPU-pushed qwords (error + 5 frame)
isr\num\()_frame_ready:
# rdx=error_code, r8=RIP, r9=CS, r10=RFLAGS, rax=RSP, r11=SS
# Build uniform frame on stack
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
push rdx # error_code
# Push is_user flag
xor ecx, ecx
test r9b, 3
setnz cl
push rcx # is_user
# Push all general-purpose registers
push rax
push rbx
push rsi
push rdi
push rbp
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
call isr\num\()_handler
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rbx
pop rax
# Pop is_user flag
pop rcx # is_user
add rsp, 8 # skip error_code (handler already consumed it)
pop r8 # RIP
pop r9 # CS
pop r10 # RFLAGS
pop rax # RSP (user)
pop r11 # SS (user)
test rcx, rcx
jnz isr\num\()_ret_user
isr\num\()_ret_kernel:
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
ISR_RESTORE_CLOBBERED
iretq
isr\num\()_ret_user:
swapgs
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
ISR_RESTORE_CLOBBERED
iretq
.endm
ISR_NOERR 0
ISR_NOERR 1
ISR_NOERR 2
ISR_NOERR 3
ISR_NOERR 4
ISR_NOERR 5
ISR_NOERR 6
ISR_NOERR 7
ISR_ERR 8
ISR_NOERR 9
ISR_ERR 10
ISR_ERR 11
ISR_ERR 12
ISR_ERR 13
ISR_ERR 14
ISR_NOERR 15
ISR_NOERR 16
ISR_ERR 17
ISR_NOERR 18
ISR_NOERR 19
ISR_NOERR 20
ISR_NOERR 21
ISR_NOERR 22
ISR_NOERR 23
ISR_NOERR 24
ISR_NOERR 25
ISR_NOERR 26
ISR_NOERR 27
ISR_NOERR 28
ISR_NOERR 29
ISR_NOERR 30
ISR_NOERR 31
ISR_NOERR 32
ISR_NOERR 33
ISR_NOERR 34
ISR_NOERR 35
ISR_NOERR 36
ISR_NOERR 37
ISR_NOERR 38
ISR_NOERR 39
ISR_NOERR 40
ISR_NOERR 41
ISR_NOERR 42
ISR_NOERR 43
ISR_NOERR 44
ISR_NOERR 45
ISR_NOERR 46
ISR_NOERR 47
.globl syscall_entry
syscall_entry:
# 入口来源判据(**不得占用 rax**:rax 是系统调用号;bt 只改 CF)。
# 本系统地址布局:用户栈由 drop_to_user_mode 固定设在 0x7fff_ffff_...(≈2^47),用户堆从
# 0x20000000 起;内核栈一律低位(boot 0x7d8d0、线程 ks≈0xbb6b000)⇒ 用 bit46(64TB)区分:
# rsp ≥ 2^46 ⇒ 用户态发起;否则内核态发起(内核自己调 .so 的 `_syscallN` 走这条)。
# 原实现 `mov rax,rsp; shr rax,47` 有两个致命错:① 覆盖调用号 → num 恒 0;② 内核栈 < 2^47
# 被判成用户态 → 误执行 swapgs → 用错误 GS 读 gs:[8] → rsp 变垃圾 → 三重故障。
bt rsp, 46
jnc syscall_from_kernel
syscall_from_user:
swapgs
mov gs:[0x10], rsp # 瞬时暂存用户 rsp:用**无人写入**的 0x10 槽(不再用 0x0 —— 那个槽
# 是"每 CPU 一个"且被 int 0x80/中断写过,容易被别的线程污染)。
# 入口段 IF=0 且不切线程,故这几条指令之间不可能被打断。
mov rsp, gs:[0x8] # 切内核栈
push qword ptr gs:[0x10] # 【修复】用户 rsp 存进**本线程内核栈**:返回只从这里取
push 0 # 来源标记(用户态发起):出口据此选 sysretq
jmp syscall_common
syscall_from_kernel:
push 0 # 占位(内核路径无用户 rsp;保持帧布局与用户路径一致)
push 1 # 来源标记(内核态发起):出口据此 jmp rcx 返回,且不 swapgs
syscall_common:
push rcx
push r11
push rbx
push rdx
push rsi
push rdi
push rbp
push r8
push r9
push r10
push r12
push r13
push r14
push r15
mov rbp, rsp
and rsp, ~15
# 对齐补齐:被调者入口必须满足 SysV(rsp ≡ 8 (mod 16);栈上 1 个参数时要求调用前 rsp ≡ 8)。
# 注意 `and rsp,~15` 已把 rsp 归零,所以只有**它之后**的深度才影响对齐:
# 补齐(8) + arg6(8) + call(8) = 24 ≡ 8 ✓。标记槽与 14 个寄存器都在 and 之前,不参与。
# (去掉这里的 `sub rsp,8` 会让 handler 入口 ≡ 0 → 其调用树里 rsp 相对的 16 字节对齐 SSE
# 例如内核 printf 的 movaps %xmm0,0x60(%rsp) 就 #GP。)
sub rsp, 8
push r9
mov r9, r8
mov r8, r10
mov rcx, rdx
mov rdx, rsi
mov rsi, rdi
mov rdi, rax
call syscall_handler
add rsp, 8
mov rsp, rbp
pop r15
pop r14
pop r13
pop r12
pop r10
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rdx
pop rbx
pop r11
pop rcx
# 返回去向用**栈上的来源标记**([rsp],入口 push 0/1)。必须是栈上而不是全局:syscall 会
# 嵌套(app 的 SO_CALL1 → 内核调 .so → .so 内部又 syscall 打日志),全局标记会被内层覆盖,
# 外层(用户态)就会误走内核返回分支 → jmp rcx 回用户 rip 却不换回用户栈,app 用内核栈继续跑。
# 原来用 `mov rax,rcx; shr rax,47` 判还有第二个错:覆盖 rax 里的返回值,且用户 rip(0x40xxxx)
# 与内核 rip(0x10xxxx) 都 < 2^47 无法区分。
cmp qword ptr [rsp], 0
jne syscall_ret_kernel
syscall_ret_user:
# 【修复】用户 rsp 从**本线程内核栈**取(入口压入),不再读 per_cpu.user_rsp ——
# 那个槽是"每 CPU 一个",别的线程的 int 0x80 / Ring-3 中断会把自己的 rsp 写进去,
# 正在挂起的 syscall 返回时就会拿到"别人的栈"(现场:rsp=cr2=0x100000000)。
# 帧布局(14 个寄存器 pop 完后的 rsp 指向处):[rsp]=来源标记(0),[rsp+8]=用户 rsp。
# 注意:这里**不借任何通用寄存器**(rax/rdx/rsi/rdi/r8-r15 都是 app 的活值;rcx 是
# sysretq 的返回地址;r11 还要用于 popfq)⇒ 用 `mov rsp, [rsp+8]`(x86 成熟惯用法:
# 源操作数先读、再写 rsp)。
push r11 # RFLAGS 落到内核栈
popfq # 此后 IF=1,但仍在 Ring0 且 GS 仍是 &_per_cpu ⇒ 与 Ring-0 中断路径一致,安全
mov rsp, [rsp + 8] # 切回用户栈(不动通用寄存器)
swapgs
sysretq
syscall_ret_kernel:
add rsp, 16 # 内核路径:弹掉 占位槽 + 来源标记,恢复到入口 rsp
push r11
popfq
jmp rcx
# =============================================================================
# ISR 0x80 - int 0x80 syscall handler for user-mode ELF apps
#
# int 0x80 calling convention (ViperOS):
# rax = syscall number
# rbx = arg1
# rcx = arg2
# rdx = arg3
# r10 = arg4
#
# Translated to syscall_handler calling convention:
# rdi = n (syscall number)
# rsi = arg1
# rdx = arg2
# rcx = arg3
# r8 = arg4
# r9 = arg5 (0)
# stack = arg6 (0)
#
# Return value in rax(其余 15 个通用寄存器全部原样恢复;RFLAGS 走原始帧 iretq,同样不变)。
# 返回不再重建 iret 帧:收尾直接回到 **CPU 压下的原始帧** iretq,由帧内 CS.RPL 决定弹 3 个
# 字段(内核)还是 5 个(用户),因此不需要 is_user 分支(原实现的 is_user 判断还被后面的
# `pop rcx` 冲掉,等于没用)。
#
# 栈布局(E = 入口 rsp 指向 CPU 压下的 RIP;S = E-16 = 保住调用号/a4 之后的 rsp):
# S+0 调用号(rax) S+8 arg4(r10)
# S+16 RIP S+24 CS S+32 RFLAGS S+40 RSP(user) S+48 SS(user)
# =============================================================================
.globl isr80
isr80:
# 取 CS/RSP 都要拿寄存器当临时,所以先把 **调用号(rax) 与 arg4(r10)** 压栈保住:
# 原实现直接用它们当临时(`mov rax,[rsp+8]` 取 CS、`mov r10,[rsp+16]` 取 RFLAGS),
# 后面 `push rax`/`push r10` 存进保存块的其实是 CS/RFLAGS → handler 收到乱掉的 n 与 a4。
#
# 【顺序绝对不能反】栈向下增长,先压的落在**高**地址。本块要的布局(S = 两次 push
# 后的 rsp = 入口 rsp-16,与文件头/下面的注释一致)是 [S+0]=调用号(rax)、[S+8]=arg4(r10),
# 所以必须**先 push r10、后 push rax**。若反了:下面 `mov rax,[rsp+40]`(= [S+0])取到
# arg4 当调用号、`mov r8,[rsp+168]`(= [S+8])取到调用号当 arg4 ⇒ syscall_handler 拿到
# 错误的 n。本路径在修好之前**没有任何调用者**(vpsdk 全走 `syscall` 指令),所以从未暴露;
# 现场是用户线程出口跳板(alloc_user_thread 映射的 `mov eax,34; int 0x80; jmp $`)
# rax=34(THREAD_EXIT) 但 n 错 ⇒ 不匹配任何分支 ⇒ 返回 0 ⇒ iretq 回用户态 ⇒ 卡在 `jmp $`
# 空转 ⇒ 该线程永不再 yield ⇒ needs_reschedule 无人消费 ⇒ 全系统不再切换(桌面饿死)。
push r10 # [S+8] = arg4
push rax # [S+0] = 调用号
test byte ptr [rsp + 24], 3 # 入口帧 CS([S+24])的 RPL;内存操作数,不占用寄存器
jz isr80_frame_ready # 内核态入口:无需 swapgs
swapgs
# 注意:这里**不要**写 gs:[0x0]!per_cpu.user_rsp 是"每 CPU 一个"的槽,而 SYSCALL 路径
# 在入口写入、出口读取它。若 int 0x80(其它线程在用户态发起)也在同一槽里写一次,
# 就会把正在进行的 syscall 的用户 rsp 覆盖成"别人的 rsp" ⇒ 那条 syscall 返回时 sysretq
# 用错 rsp ⇒ 用户态在错误栈上执行(现场:rsp=cr2=0x100000000 = mm.vm_alloc 的首个虚拟地址)。
# int 0x80 的返回直接用 CPU 压下的原始帧(RIP/CS/RFLAGS/RSP/SS 都在栈上),无需该槽。
isr80_frame_ready:
# 5 个占位 push:统一帧只是用来固定下面 15 个保存槽的偏移。收尾直接在 **CPU 压下的原始帧**
# 上 iretq(RSP/SS 与环切换由 iretq 依据帧内 CS.RPL 自行处理),因此不再从入口帧搬运
# RIP/CS/RFLAGS/SS —— 那些值已无人读取。
push 0 # SS
push 0 # RSP
push 0 # RFLAGS
push 0 # CS
push 0 # RIP
mov rax, [rsp + 40] # 取回调用号([S+0])→ 下面 push rax 存进保存块的 rax 槽
# Save ALL registers FIRST (including original rcx = arg2)
# Order: rax, rbx, rcx, rdx, rsi, rdi, rbp, r8, r9, r10, r11, r12, r13, r14, r15
push rax # [rsp+112] rax (syscall number)
push rbx # [rsp+104] rbx (arg1)
push rcx # [rsp+96] rcx (arg2)
push rdx # [rsp+88] rdx (arg3)
push rsi # [rsp+80]
push rdi # [rsp+72]
push rbp # [rsp+64]
push r8 # [rsp+56]
push r9 # [rsp+48]
push r10 # [rsp+40] r10 (arg4)
push r11 # [rsp+32]
push r12 # [rsp+24]
push r13 # [rsp+16]
push r14 # [rsp+8]
push r15 # [rsp+0]
# Translate int 0x80 calling convention to syscall_handler convention
# 偏移以“15 个保存槽全部压完后”的 rsp 为基准(见上面的栈布局注释)
mov rdi, [rsp + 112] # 调用号(入口压栈保存 → 保存块的 rax 槽)
mov rsi, [rsp + 104] # arg1 (rbx)
mov rdx, [rsp + 96] # arg2 (rcx)
mov rcx, [rsp + 88] # arg3 (rdx)
mov r8, [rsp + 168] # arg4(入口压栈保存;原用 [rsp+48],那里其实是被覆盖的 RFLAGS)
xor r9, r9 # arg5 = 0
push 0 # arg6 = 0 (on stack)
call syscall_handler
add rsp, 8 # remove arg6
mov [rsp + 112], rax # 返回值写回保存块的 rax 槽(下面最后一次 pop rax 取回)
# Restore registers(rax 留到最后取返回值)
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 # = 返回值
# 跳过统一帧(5*8=40) 与保存的 调用号/arg4(2*8=16),回到入口帧位置,直接在原始帧上 iretq:
# 帧内 CS.RPL=3 → 弹 RSP/SS 回 Ring3(返回前 swapgs 还原用户 GS);RPL=0 → 弹 3 个字段回内核。
add rsp, 56
test byte ptr [rsp + 8], 3 # 帧内 CS
jz isr80_iret
swapgs
isr80_iret:
iretq