feat(diag): 异常现场完整转储(裸串口通路 + 无错误码向量)
- 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 条异常打印完整现场,之后每次只补 '*',避免异常风暴刷爆串口
This commit is contained in:
1 parent
8c31645c0f
commit
0c47d81767
2 files changed
+163
-4
No files matched your search
+126
-3
@@ -76,6 +76,45 @@ extern def isr47()
|
||||
extern def isr80()
|
||||
extern def isr_default()
|
||||
|
||||
# ===========================================================================
|
||||
# 【诊断】异常现场的"裸串口"输出通路
|
||||
#
|
||||
# 为什么不直接用 serial.puts:`[06]` 风暴现场里只能看到裸 `[06]`、看不到任何后续文字,
|
||||
# 说明 serial.puts 这条路当时是断的(缓冲区/锁/驱动对象状态可能在故障中已损坏,或者它自己
|
||||
# 再次触发异常)。而 `[06]` 之所以出得来,是因为它由 isrCommonhandler 开头直接用 `out 0x3F8`
|
||||
# 写出 —— 只要 CPU 还能 out,这条路就一定能出字。
|
||||
# 因此下面几条诊断函数全部走 `asm.outb`,不经过任何驱动/缓冲/全局状态。
|
||||
# ===========================================================================
|
||||
define DIAG_COM1 = 0x3F8
|
||||
|
||||
# 已输出的完整现场条数:异常风暴时只详细打前若干条,之后每来一次只补一个 '*',
|
||||
# 避免把串口刷爆(真正的定位信息全在最前面的几条里)。
|
||||
_diag_dumps: int = 0
|
||||
|
||||
_hex_digits: [17]char = "0123456789ABCDEF"
|
||||
|
||||
def _diag_putc(ch: int):
|
||||
asm.outb(DIAG_COM1, uint8(ch & 0xFF))
|
||||
|
||||
def _diag_puts(s: const str):
|
||||
i: int = 0
|
||||
while s[i] != 0:
|
||||
_diag_putc(int(s[i]))
|
||||
i += 1
|
||||
|
||||
# 定宽十六进制:从低位往高位填再正序吐出(与 vpui._ptr_to_hex 同款,避免变长移位)
|
||||
def _diag_hex(v: uint64, digits: int):
|
||||
buf: [20]char
|
||||
i: int = digits - 1
|
||||
val: uint64 = v
|
||||
while i >= 0:
|
||||
nib: int = int(val & uint64(0xF))
|
||||
buf[i] = _hex_digits[nib]
|
||||
val = val >> 4
|
||||
i -= 1
|
||||
buf[digits] = 0
|
||||
_diag_puts(&buf[0])
|
||||
|
||||
def setGate(num: int, base: uint64, sel: uint16, flags: uint8):
|
||||
global idt
|
||||
idt[num].base_low = uint16(base & 0xFFFF)
|
||||
@@ -111,10 +150,81 @@ def isrCommonhandler(interrupt: int):
|
||||
mov al, 93
|
||||
out dx, al""",
|
||||
op=[ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX])
|
||||
# 【诊断·完整现场】紧接上面裸打出的 `[VV]`,用同一条裸串口通路补齐现场。
|
||||
# isr.s 的两个宏(ISR_NOERR / ISR_ERR)都会填 _fault_*,因此所有向量都有 rip/rsp/cs/flags。
|
||||
# 只详细打前 32 条:异常风暴时后面的每条只补一个 '*',避免把串口刷爆。
|
||||
global _diag_dumps
|
||||
_diag_dumps = _diag_dumps + 1
|
||||
if _diag_dumps <= 32:
|
||||
_dg_rip: uint64 = 0
|
||||
_dg_rsp: uint64 = 0
|
||||
_dg_cs: uint64 = 0
|
||||
_dg_fl: uint64 = 0
|
||||
__asm__(f"mov {asmout(_dg_rip, ASM_DESCR.OUTPUT_REG)}, [_fault_rip]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_dg_rsp, ASM_DESCR.OUTPUT_REG)}, [_fault_rsp]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_dg_cs, ASM_DESCR.OUTPUT_REG)}, [_fault_cs]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_dg_fl, ASM_DESCR.OUTPUT_REG)}, [_fault_flags]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
_diag_puts(" rip=0x")
|
||||
_diag_hex(_dg_rip, 16)
|
||||
_diag_puts(" rsp=0x")
|
||||
_diag_hex(_dg_rsp, 16)
|
||||
_diag_puts(" cs=0x")
|
||||
_diag_hex(_dg_cs, 4)
|
||||
_diag_puts(" fl=0x")
|
||||
_diag_hex(_dg_fl, 8)
|
||||
_diag_puts(" u=")
|
||||
_diag_putc(48 + int(_dg_cs & uint64(3)))
|
||||
# callee-saved 现场(两个宏都已在入口保存):r12 常是出错 store 的目标对象基址
|
||||
_dg_r12: uint64 = 0
|
||||
_dg_rbx: uint64 = 0
|
||||
_dg_rbp: uint64 = 0
|
||||
__asm__(f"mov {asmout(_dg_r12, ASM_DESCR.OUTPUT_REG)}, [_fault_r12]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_dg_rbx, ASM_DESCR.OUTPUT_REG)}, [_fault_rbx]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_dg_rbp, ASM_DESCR.OUTPUT_REG)}, [_fault_rbp]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
_diag_puts(" rbx=0x")
|
||||
_diag_hex(_dg_rbx, 16)
|
||||
_diag_puts(" rbp=0x")
|
||||
_diag_hex(_dg_rbp, 16)
|
||||
_diag_puts(" r12=0x")
|
||||
_diag_hex(_dg_r12, 16)
|
||||
if (interrupt == 8 or interrupt == 10 or interrupt == 11 or interrupt == 12
|
||||
or interrupt == 13 or interrupt == 14 or interrupt == 17):
|
||||
# 只有 ISR_ERR 系列才真的压了错误码;其余向量的 _fault_err 是 -1 哨兵
|
||||
_dg_err: uint64 = 0
|
||||
__asm__(f"mov {asmout(_dg_err, ASM_DESCR.OUTPUT_REG)}, [_fault_err]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
_diag_puts(" err=0x")
|
||||
_diag_hex(_dg_err, 4)
|
||||
else:
|
||||
_diag_puts(" err=none")
|
||||
if interrupt == 6:
|
||||
# #UD:把出错指令的前 8 字节打出来 —— `0F 0B` 就是 ud2(LLVM 插的 trap),
|
||||
# 其余字节说明是"数据/坏跳转目标被当代码执行"。
|
||||
ipb: uint8* = uint8*(_dg_rip)
|
||||
_diag_puts(" insn=")
|
||||
for bi in range(8):
|
||||
_diag_hex(uint64(ipb[bi]), 2)
|
||||
_diag_putc(32)
|
||||
if interrupt == 14:
|
||||
_dg_cr2: uint64 = 0
|
||||
__asm__(f"mov {asmout(_dg_cr2, ASM_DESCR.OUTPUT_REG)}, cr2",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
_diag_puts(" cr2=0x")
|
||||
_diag_hex(_dg_cr2, 16)
|
||||
_diag_putc(10)
|
||||
else:
|
||||
_diag_putc(42)
|
||||
# 【诊断】异常现场:出错 RIP / 错误码 / 出错时 RSP(#PF 另打 cr2)。
|
||||
# 由 isr.s 的 ISR_ERR 在入口写入 _fault_* ⇒ #GP/#PF 自己就能说出位置,不必再反查地址。
|
||||
# 只有带错误码的向量(8/10/11/12/13/14/17,见 isr.s 里用 ISR_ERR 的那些)才会写 _fault_*,
|
||||
# 其余向量读到的是上次残留 ⇒ 仅在真写了时打印,避免误导。
|
||||
# 由 isr.s 的 ISR_NOERR / ISR_ERR 在入口写入 _fault_*(无错误码向量写 -1);
|
||||
# 这一段走 serial.puts,属于"锦上添花":真正可靠的现场在上面那段裸串口输出里,
|
||||
# 这里只在带错误码的向量上补充返回槽三连(serial.puts 自身故障时不会出字,属已知局限)。
|
||||
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
|
||||
@@ -129,6 +239,19 @@ def isrCommonhandler(interrupt: int):
|
||||
_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)
|
||||
# callee-saved 现场(ISR_ERR 入口保存):r12 往往就是出错 store 的目标对象基址
|
||||
# (cr2 = r12 + 常量偏移),据此能直接对上"写的是哪个对象"。
|
||||
_df_r12: uint64 = 0
|
||||
_df_rbx: uint64 = 0
|
||||
_df_rbp: uint64 = 0
|
||||
__asm__(f"mov {asmout(_df_r12, ASM_DESCR.OUTPUT_REG)}, [_fault_r12]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_df_rbx, ASM_DESCR.OUTPUT_REG)}, [_fault_rbx]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov {asmout(_df_rbp, ASM_DESCR.OUTPUT_REG)}, [_fault_rbp]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
viperlib.snprintf(&_dfbuf, 128, " rbx=0x%lx rbp=0x%lx r12=0x%lx\n", _df_rbx, _df_rbp, _df_r12)
|
||||
serial.puts(&_dfbuf)
|
||||
# 返回槽三连:出错 RSP 附近的栈字。
|
||||
# ret=0x… [rsp-8]:若 CPU 是在 `ret` 之后于错地址取指的,这里就是被弹走的坏目标;
|
||||
# ra=0x… [rsp] :调用者的返回地址(真内核地址)⇒ 反查即可定位"哪个函数的返回槽被写坏";
|
||||
|
||||
@@ -7,10 +7,21 @@
|
||||
.comm _yield_lock, 4, 4
|
||||
.comm _fs_lock, 4, 4
|
||||
# 【诊断】异常现场(#GP/#PF 等带错误码的向量在 ISR_ERR 入口保存,isrCommonhandler 打印)。
|
||||
# 只保留最小三件套:出错 RIP / 错误码 / 出错时的 RSP(#PF 另读 cr2)。
|
||||
# 三件套:出错 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 前)恢复。
|
||||
@@ -75,6 +86,19 @@ isr_default:
|
||||
.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
|
||||
@@ -86,6 +110,9 @@ isr\num\()_from_kernel:
|
||||
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
|
||||
@@ -99,6 +126,7 @@ isr\num\()_from_user:
|
||||
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"返回时拿到别人的栈。
|
||||
@@ -263,6 +291,10 @@ isr\num\()_ret_user:
|
||||
.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
|
||||
@@ -285,6 +317,8 @@ isr\num\()_from_kernel:
|
||||
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
|
||||
|
||||
@@ -301,6 +335,8 @@ isr\num\()_from_user:
|
||||
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)
|
||||
|
||||
|
||||
Reference in new issue
Block a user