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:
1 parent
c29c1953df
commit
4e1be2be88
2 files changed
+12
-93
No files matched your search
+10
-16
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user