- isr.s:ISR_NOERR 也保存 RIP/CS/RFLAGS/RSP,_fault_err 用 -1 表示无错误码; 两类宏都补存 rbx/rbp/r12(用户态 #PF 时 r12 常为出错 store 的目标对象基址) - idt.vp:新增不经驱动/缓冲的裸串口输出(asm.outb),打印 rip/rsp/cs/flags、 u 位、callee-saved、错误码;#UD 附带出错指令字节,#PF 附带 cr2 - 前 32 条异常打印完整现场,之后每次只补 '*',避免异常风暴刷爆串口
687 lines
24 KiB
ArmAsm
687 lines
24 KiB
ArmAsm
.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);另加 callee-saved 的 rbx/rbp/r12。
|
||
.comm _fault_rip, 8, 8
|
||
.comm _fault_err, 8, 8
|
||
.comm _fault_rsp, 8, 8
|
||
# 【诊断】CS / RFLAGS 现场:判"到底是用户态还是内核态出错""中断/方向/对齐标志是否异常"。
|
||
# 注意 ISR_NOERR(含 #UD=6)原来完全没保存 rip,导致 `[06]` 只剩一个光秃秃的向量号;
|
||
# 现在两类宏都保存同一套现场,`_fault_err = -1` 表示"本向量没有错误码"。
|
||
.comm _fault_cs, 8, 8
|
||
.comm _fault_flags, 8, 8
|
||
# 【诊断】callee-saved 寄存器现场(#GP/#PF 专用):r12/rbx/rbp 在 ISR 入口尚未被
|
||
# 任何宏破坏,直接落到全局槽里,供 isrCommonhandler 打印。用户态 #PF 里 r12 常常正是
|
||
# 那条出错 store 的目标对象基址(cr2 = r12 + offset),据此可直接对上是哪个对象。
|
||
.comm _fault_r12, 8, 8
|
||
.comm _fault_rbx, 8, 8
|
||
.comm _fault_rbp, 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
|
||
# 【诊断】无错误码向量(#DE/#UD/#BR/#OF… 含 #UD=6)也要保存完整现场:
|
||
# 此时 [rsp]=RIP、[rsp+8]=CS、[rsp+16]=RFLAGS 是 CPU 刚压下的原始帧,尚未被本宏改动。
|
||
# 不保存的话 isrCommonhandler 只能打出一个 `[06]`,没有 rip 可供反查。
|
||
mov rax, [rsp]
|
||
mov [_fault_rip], rax
|
||
mov rax, [rsp + 8]
|
||
mov [_fault_cs], rax
|
||
mov rax, [rsp + 16]
|
||
mov [_fault_flags], rax
|
||
mov qword ptr [_fault_err], -1 # -1 = 本向量没有错误码(ISR_ERR 写真实错误码)
|
||
mov [_fault_r12], r12
|
||
mov [_fault_rbx], rbx
|
||
mov [_fault_rbp], rbp
|
||
# 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
|
||
# 出错时的 RSP:无错误码帧只有 RIP/CS/RFLAGS 三个 qword ⇒ 原始 rsp = 当前 rsp + 24
|
||
lea rax, [rsp + 24]
|
||
mov [_fault_rsp], rax
|
||
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 [_fault_rsp], rax
|
||
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
|
||
# is_user 作为第 1 个参数(rdi)传给处理函数,供其判断"返回用户态"时是否抢占
|
||
mov rdi, rcx
|
||
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
|
||
# 【诊断】callee-saved 寄存器此时未被任何宏改动,先保存现场(不影响下面的寄存器用法)
|
||
mov [_fault_r12], r12
|
||
mov [_fault_rbx], rbx
|
||
mov [_fault_rbp], rbp
|
||
# 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
|
||
mov [_fault_cs], r9
|
||
mov [_fault_flags], r10
|
||
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
|
||
mov [_fault_cs], r9
|
||
mov [_fault_flags], r10
|
||
# 同上:不再写 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
|
||
# is_user 作为第 1 个参数(rdi)传给处理函数,供其判断"返回用户态"时是否抢占
|
||
mov rdi, rcx
|
||
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
|