chore(kernel): 恢复最小异常现场 dump;EXIT 增加不返回硬保护
isr.s:ISR_ERR 两条入口写 _fault_rip/_fault_err/_fault_rsp;idt.vp:带错误码向量打印 rip/err/rsp(#PF 追加 cr2),返回槽三连仅在出错 RSP 落在内核栈范围时读取(用户栈下方页未映射,读 [rsp-8] 会触发次生 #PF)。syscall.vp:EXIT 在 exit_current_thread 之后禁止返回(返回会一路 ret 回 app 的 _start 末尾、弹到 0 取指,现场 [0E] rip=0x0 err=0x15)。
This commit is contained in:
1 parent
f8b37564da
commit
06d24ae1aa
3 files changed
+90
-27
No files matched your search
@@ -111,6 +111,49 @@ def isrCommonhandler(interrupt: int):
|
||||
mov al, 93
|
||||
out dx, al""",
|
||||
op=[ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX])
|
||||
# 【诊断】异常现场:出错 RIP / 错误码 / 出错时 RSP(#PF 另打 cr2)。
|
||||
# 由 isr.s 的 ISR_ERR 在入口写入 _fault_* ⇒ #GP/#PF 自己就能说出位置,不必再反查地址。
|
||||
# 只有带错误码的向量(8/10/11/12/13/14/17,见 isr.s 里用 ISR_ERR 的那些)才会写 _fault_*,
|
||||
# 其余向量读到的是上次残留 ⇒ 仅在真写了时打印,避免误导。
|
||||
if (interrupt == 8 or interrupt == 10 or interrupt == 11 or interrupt == 12
|
||||
or interrupt == 13 or interrupt == 14 or interrupt == 17):
|
||||
_df_rip: uint64 = 0
|
||||
_df_err: uint64 = 0
|
||||
_df_rsp: uint64 = 0
|
||||
__asm__(f"mov {asmout(_df_rip, ASM_DESCR.OUTPUT_REG)}, [_fault_rip]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_df_err, ASM_DESCR.OUTPUT_REG)}, [_fault_err]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_df_rsp, ASM_DESCR.OUTPUT_REG)}, [_fault_rsp]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
_dfbuf: [128]char
|
||||
viperlib.snprintf(&_dfbuf, 128, " rip=0x%lx err=0x%lx rsp=0x%lx\n", _df_rip, _df_err, _df_rsp)
|
||||
serial.puts(&_dfbuf)
|
||||
# 返回槽三连:出错 RSP 附近的栈字。
|
||||
# ret=0x… [rsp-8]:若 CPU 是在 `ret` 之后于错地址取指的,这里就是被弹走的坏目标;
|
||||
# ra=0x… [rsp] :调用者的返回地址(真内核地址)⇒ 反查即可定位"哪个函数的返回槽被写坏";
|
||||
# ra2=0x… [rsp+8]:再上一层,用于交叉确认。
|
||||
# 返回槽三连:只在**内核栈**情形下读 —— 用户态出错时 _fault_rsp 是用户栈指针,
|
||||
# 它下方那一页往往没映射,读 [rsp-8] 会再触发一次 #PF(现场:紧跟一行
|
||||
# [0E] cr2=0x7fffffde0000,就是这里读出来的次生故障)。
|
||||
if _df_rsp >= 0x100000 and _df_rsp < 0x80000000:
|
||||
_df_ret: uint64 = 0
|
||||
_df_ra: uint64 = 0
|
||||
_df_ra2: uint64 = 0
|
||||
__asm__(f"mov {asmout(_df_ret, ASM_DESCR.OUTPUT_REG)}, [{asminp(_df_rsp, ASM_DESCR.REG_ANY)} - 8]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_df_ra, ASM_DESCR.OUTPUT_REG)}, [{asminp(_df_rsp, ASM_DESCR.REG_ANY)}]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_df_ra2, ASM_DESCR.OUTPUT_REG)}, [{asminp(_df_rsp, ASM_DESCR.REG_ANY)} + 8]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
viperlib.snprintf(&_dfbuf, 128, " ret=0x%lx ra=0x%lx ra2=0x%lx\n", _df_ret, _df_ra, _df_ra2)
|
||||
serial.puts(&_dfbuf)
|
||||
if interrupt == 14:
|
||||
_df_cr2: uint64 = 0
|
||||
__asm__(f"mov {asmout(_df_cr2, ASM_DESCR.OUTPUT_REG)}, cr2",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
viperlib.snprintf(&_dfbuf, 128, " cr2=0x%lx\n", _df_cr2)
|
||||
serial.puts(&_dfbuf)
|
||||
s: sched.Scheduler* = sched._sched_ptr
|
||||
# 如果调度器未初始化,直接进入安全停机状态
|
||||
if s == 0:
|
||||
|
||||
+35
-27
@@ -6,11 +6,11 @@
|
||||
.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
|
||||
.comm _fault_rbp, 8, 8
|
||||
.comm _fault_rax, 8, 8
|
||||
# ISR 入口暂存 caller-saved 寄存器(rax/rcx/r8-r11):本宏会用它们当临时寄存器
|
||||
# (读 RIP/CS/RFLAGS、算 is_user),若不保存就会破坏被中断代码在这些寄存器里的
|
||||
# 活跃值 → 概率性内存破坏/异常。出口(iretq 前)恢复。
|
||||
@@ -75,8 +75,6 @@ isr_default:
|
||||
.globl isr\num
|
||||
isr\num:
|
||||
ISR_SAVE_CLOBBERED
|
||||
# 入口最早保存崩溃 rax(后续 mov rax,[rsp+8] 读 CS 会覆盖它)
|
||||
mov [_fault_rax], rax
|
||||
# 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
|
||||
@@ -102,8 +100,8 @@ isr\num\()_from_user:
|
||||
mov r10, [rsp + 16] # RFLAGS
|
||||
mov rax, [rsp + 24] # RSP (user)
|
||||
mov r11, [rsp + 32] # SS (user)
|
||||
# Save user RSP to per_cpu for potential later use
|
||||
mov gs:[0x0], rax
|
||||
# 不再把用户 RSP 写进 per_cpu.user_rsp:该槽已无读者(syscall 返回改为从本线程内核栈取),
|
||||
# 而它是"每 CPU 一个" —— 别的线程写它会让"正挂起的 syscall"返回时拿到别人的栈。
|
||||
add rsp, 40 # remove the 5 CPU-pushed qwords
|
||||
|
||||
isr\num\()_frame_ready:
|
||||
@@ -128,7 +126,6 @@ isr\num\()_frame_ready:
|
||||
push rsi
|
||||
push rdi
|
||||
push rbp
|
||||
mov [_fault_rbp], rbp
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
@@ -137,9 +134,6 @@ isr\num\()_frame_ready:
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
# Save faulting RIP and error code for debug
|
||||
mov [_fault_rip], r8
|
||||
mov [_fault_err], rdx
|
||||
call isr\num\()_handler
|
||||
pop r15
|
||||
pop r14
|
||||
@@ -267,8 +261,6 @@ isr\num\()_ret_user:
|
||||
.globl isr\num
|
||||
isr\num:
|
||||
ISR_SAVE_CLOBBERED
|
||||
# 入口最早保存崩溃 rax(后续 mov rax,[rsp+16] 读 CS 会覆盖它)
|
||||
mov [_fault_rax], rax
|
||||
# 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
|
||||
@@ -286,7 +278,10 @@ isr\num\()_from_kernel:
|
||||
mov r10, [rsp + 24] # RFLAGS
|
||||
xor r11, r11 # SS = 0
|
||||
xor rax, rax # RSP = 0
|
||||
lea rcx, [rsp + 32] # faulting RSP = entry_rsp + 32 (err + RIP + CS + RFLAGS)
|
||||
# 【诊断】保存异常现场: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
|
||||
@@ -300,8 +295,11 @@ isr\num\()_from_user:
|
||||
mov r10, [rsp + 24] # RFLAGS
|
||||
mov rax, [rsp + 32] # RSP (user)
|
||||
mov r11, [rsp + 40] # SS (user)
|
||||
mov [_fault_rsp], rax # faulting RSP = user RSP
|
||||
mov gs:[0x0], rax # save user RSP
|
||||
# 【诊断】同 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:
|
||||
@@ -326,7 +324,6 @@ isr\num\()_frame_ready:
|
||||
push rsi
|
||||
push rdi
|
||||
push rbp
|
||||
mov [_fault_rbp], rbp
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
@@ -335,9 +332,6 @@ isr\num\()_frame_ready:
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
# Save faulting RIP and error code for debug
|
||||
mov [_fault_rip], r8
|
||||
mov [_fault_err], rdx
|
||||
call isr\num\()_handler
|
||||
pop r15
|
||||
pop r14
|
||||
@@ -444,11 +438,15 @@ syscall_entry:
|
||||
jnc syscall_from_kernel
|
||||
syscall_from_user:
|
||||
swapgs
|
||||
mov gs:[0x0], rsp
|
||||
mov rsp, gs:[0x8]
|
||||
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
|
||||
@@ -505,13 +503,20 @@ syscall_common:
|
||||
cmp qword ptr [rsp], 0
|
||||
jne syscall_ret_kernel
|
||||
syscall_ret_user:
|
||||
mov rsp, gs:[0x0] # 用户路径直接切回用户栈(本层标记槽留在内核栈,下次入口由 TSS.RSP0 重置)
|
||||
# 【修复】用户 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
|
||||
push r11
|
||||
popfq
|
||||
sysretq
|
||||
syscall_ret_kernel:
|
||||
add rsp, 8 # 内核路径:弹掉本层标记槽,恢复到入口 rsp
|
||||
add rsp, 16 # 内核路径:弹掉 占位槽 + 来源标记,恢复到入口 rsp
|
||||
push r11
|
||||
popfq
|
||||
jmp rcx
|
||||
@@ -554,8 +559,11 @@ isr80:
|
||||
test byte ptr [rsp + 24], 3 # 入口帧 CS([S+24])的 RPL;内存操作数,不占用寄存器
|
||||
jz isr80_frame_ready # 内核态入口:无需 swapgs
|
||||
swapgs
|
||||
mov rax, [rsp + 40] # RSP (user)
|
||||
mov gs:[0x0], rax # per_cpu.user_rsp(与 syscall 路径同款簿记)
|
||||
# 注意:这里**不要**写 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 压下的原始帧**
|
||||
|
||||
@@ -110,6 +110,18 @@ def DRAW_TEXT(win_id: int, x: int, y: int, text: const str, color: uint32) -> in
|
||||
def EXIT(code: int) -> int:
|
||||
serial.puts("[syscall] exit called\n")
|
||||
desktop.destroy_by_pid(process.current())
|
||||
# 真正的退出:退役本线程并让出,**绝不返回用户态**。
|
||||
# 只 destroy 窗口不够:app 的 _start 在 process.exit(0) 之后还有 `return 0`,而内核是以
|
||||
# rsp = USER_STACK_VTOP 进入 _start 的(elf.vp:379),VTOP 正是用户栈映射
|
||||
# [VTOP-64K, VTOP) 的上界之外那一字节 ⇒ 一旦 _start 收尾 `ret` 就会去读 [VTOP]:
|
||||
# #PF cr2=rsp=0x7fffffe00000,rip 在 _start 内,rbp=0,rax=退出码(实测现场)。
|
||||
# 口径与异常终止路径一致(idt.vp 的 free_user_thread + state=THREAD_DONE + 不再回用户态)。
|
||||
sched.Scheduler.exit_current_thread()
|
||||
# 【硬保护】这里绝不允许返回:一旦返回就会沿调用链回到 app 的 _start 末尾,那里 `ret`
|
||||
# 会弹到一个 0 返回地址并去 0 取指(现场:[0E] rip=0x0 err=0x15 = 用户态取指缺页,
|
||||
# 紧随 [syscall] exit called,tid=5)。停留在此让调度器彻底不再调度本线程。
|
||||
while True:
|
||||
sched.Scheduler._yield()
|
||||
return 0
|
||||
|
||||
def WAIT_WINDOW(win_id: int) -> int:
|
||||
|
||||
Reference in new issue
Block a user