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_*)。
This commit is contained in:
TermiNexus committed 2026-10-16 18:07:57 +08:00
1 parent c29c1953df
commit 4e1be2be88
2 files changed
+12 -93

No files matched your search

+10 -16
View File
@@ -15,13 +15,6 @@
# (读 RIP/CS/RFLAGS、算 is_user),若不保存就会破坏被中断代码在这些寄存器里的
# 活跃值 → 概率性内存破坏/异常。出口(iretq 前)恢复。
.comm _isr_scratch, 48, 8
# 【诊断·临时,定位后必须删除】被中断代码的 RIP/CS/RSP。ASR_NOERR/ISR_ERR 在
# _frame_ready 里写入(此时 r8=RIP / r9=CS / rax=RSP / r11=SS)。timer.vp 的心跳
# 探针读它们,用来定位"某个线程卡在用户态哪条指令上"。定时器 1kHz 触发,所以这三个
# 值几乎总是最后一次定时中断的现场。
.comm _intr_rip, 8, 8
.comm _intr_cs, 8, 8
.comm _intr_rsp, 8, 8
.section .text
.intel_syntax noprefix
@@ -113,10 +106,6 @@ isr\num\()_from_user:
isr\num\()_frame_ready:
# r8=RIP, r9=CS, r10=RFLAGS, rax=RSP(0 if kernel), r11=SS(0 if kernel)
# 【诊断·临时】记下被中断代码的 RIP/CS/RSP(只读 r8/r9/rax,不破坏后面要压栈的值)
mov [_intr_rip], r8
mov [_intr_cs], r9
mov [_intr_rsp], rax
# Build uniform frame on stack for iretq later
push r11 # SS
push rax # RSP
@@ -315,10 +304,6 @@ isr\num\()_from_user:
isr\num\()_frame_ready:
# rdx=error_code, r8=RIP, r9=CS, r10=RFLAGS, rax=RSP, r11=SS
# 【诊断·临时】同 ISR_NOERR:记下被中断代码的 RIP/CS/RSP
mov [_intr_rip], r8
mov [_intr_cs], r9
mov [_intr_rsp], rax
# Build uniform frame on stack
push r11 # SS
push rax # RSP
@@ -569,8 +554,17 @@ 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。
push rax # [S+0] = 调用号
#
# 【顺序绝对不能反】栈向下增长,先压的落在**高**地址。本块要的布局(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
+2 -77
View File
@@ -12,78 +12,7 @@ define MILLISECONDS_PER_TICK = (1000 / TIMER_FREQUENCY)
static ticks: volatile uint64 = 0
static seconds: volatile uint32 = 0
static milliseconds: volatile uint32 = 0
static timer_running: volatile bool = False
# ============================================================================
# 【诊断·临时,定位后必须删除】IRQ0 心跳 + 调度器快照。
#
# 症状:某个 app 退出([syscall] exit called)之后 [perf]/[chk] 永不再打印。
# 桌面线程的渲染/打印完全依赖调度器切它回来,而调度器只在 needs_reschedule
# 被 sched_tick(IRQ0)置位、且有线程调用 try_reschedule 时才切换 ⇒ 三种可能:
# A) 本探针也一起停 → IRQ0 / EOI 死了(定时器不再推进)
# B) 探针继续、cur 恒定且该 tid 的 st=1(RUNNING) → 那个线程死循环且不再调用带
# try_reschedule 的 syscall ⇒ needs_reschedule 无人消费 ⇒ 全系统不再切换
# C) 探针继续、cur 在变 → 调度器活着,问题在桌面线程自身
#
# 直接写 COM1 0x3F8(自带 LSR 超时,绝不依赖 serial 驱动、绝不挂住 ISR)。
# 每行格式(`_intr_*` 由 isr.s 在每次中断入口写入 = 最后一次定时中断打断的那条指令):
# `[t] <ticks:8hex> <current_tid:8hex> <thread_count:8hex> <各线程 state 1 位hex> <intr_rip:8hex> <intr_cs:8hex> <intr_rsp:8hex>`
# 卡死线程的现场就是 intr_rip/intr_cs:cs=2B 表示卡在用户态该 rip 处,cs=08 表示卡在内核。
# ============================================================================
_dbg_tick: uint64 = 0
def _dbg_putc(ch: uint8):
tmo: int = 200000
while tmo > 0:
if (asm.inb(0x3FD) & 0x20) != 0:
break
tmo -= 1
asm.outb(0x3F8, ch)
def _dbg_hex32(v: uint64):
sh: int = 28
while sh >= 0:
d: int = int((v >> uint64(sh)) & uint64(15))
if d < 10:
_dbg_putc(uint8(48 + d))
else:
_dbg_putc(uint8(55 + d))
sh -= 4
def _dbg_snapshot():
s: sched.Scheduler* = sched._sched_ptr
_dbg_putc(uint8(10)) # \n
_dbg_putc(uint8(91)) # [
_dbg_putc(uint8(116)) # t
_dbg_putc(uint8(93)) # ]
_dbg_putc(uint8(32)) # ' '
_dbg_hex32(uint64(ticks))
_dbg_putc(uint8(32))
_dbg_hex32(uint64(s.current_tid))
_dbg_putc(uint8(32))
_dbg_hex32(uint64(s.thread_count))
_dbg_putc(uint8(32))
i: int
for i in range(s.thread_count):
st: int = s.threads[i].state
if st < 10:
_dbg_putc(uint8(48 + st))
else:
_dbg_putc(uint8(55 + st))
# 被中断代码的 RIP/CS/RSP(isr.s 写入 _intr_*)——定位"卡在哪条指令"的关键。
ir: uint64 = 0
ic: uint64 = 0
iu: uint64 = 0
__asm__(f"mov {asmout(ir, ASM_DESCR.OUTPUT_REG)}, [_intr_rip]", op=[ASM_DESCR.CLOBBER_MEMORY])
__asm__(f"mov {asmout(ic, ASM_DESCR.OUTPUT_REG)}, [_intr_cs]", op=[ASM_DESCR.CLOBBER_MEMORY])
__asm__(f"mov {asmout(iu, ASM_DESCR.OUTPUT_REG)}, [_intr_rsp]", op=[ASM_DESCR.CLOBBER_MEMORY])
_dbg_putc(uint8(32))
_dbg_hex32(ir)
_dbg_putc(uint8(32))
_dbg_hex32(ic)
_dbg_putc(uint8(32))
_dbg_hex32(iu)
_dbg_putc(uint8(10))
static timer_running: volatile bool = False
def timer_init():
global ticks, seconds, milliseconds, timer_running
@@ -96,7 +25,7 @@ def timer_init():
pit.pit_init(TIMER_FREQUENCY)
def timer_handler():
global ticks, seconds, milliseconds, timer_running, _dbg_tick
global ticks, seconds, milliseconds, timer_running
if not timer_running:
pic.eoi(0)
apic.eoi()
@@ -114,10 +43,6 @@ def timer_handler():
# 只发 PIC EOI 不清 LAPIC ISR → 首个定时中断后后续中断被屏蔽 →
# ticks 不推进 → msleep 永久挂起(QEMU/OVMF 下 APIC 必启用)。
apic.eoi()
# 【诊断·临时】每 1000 tick(≈1s)打一行心跳+调度器快照,见文件头说明。
_dbg_tick = _dbg_tick + 1
if (_dbg_tick % 1000) == 0:
_dbg_snapshot()
def timer_start():
global timer_running