From d37eac32feb716cf535ff11ec48ad8c1dca4f749 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 16 Oct 2026 14:12:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(keyboard):=20init()=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=B9=82=E7=AD=89=EF=BC=88=E9=81=BF=E5=85=8D=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E7=A6=81=E7=94=A8=E9=94=AE=E7=9B=98?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=89=EF=BC=9B=E6=8C=89=E9=94=AE=E6=97=B6?= =?UTF-8?q?=E6=89=93=E5=8D=B0=E6=89=AB=E6=8F=8F=E7=A0=81/=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E5=88=B0=E4=B8=B2=E5=8F=A3=E4=BE=BF=E4=BA=8E=E9=AA=8C?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Kernel/drivers/input/keyboard/keyboard.vp | 18 +- VKernel/Kernel/intr/idt.vp | 181 ++---------------- VKernel/Kernel/services/desktop.vp | 94 ++++----- 3 files changed, 79 insertions(+), 214 deletions(-) diff --git a/VKernel/Kernel/drivers/input/keyboard/keyboard.vp b/VKernel/Kernel/drivers/input/keyboard/keyboard.vp index b150fc0..6cce3a0 100644 --- a/VKernel/Kernel/drivers/input/keyboard/keyboard.vp +++ b/VKernel/Kernel/drivers/input/keyboard/keyboard.vp @@ -2,6 +2,7 @@ import asm import intr.idt as idt import drivers.serial.uart.serial as serial import string +import viperlib import t, c @@ -17,7 +18,9 @@ buffer: [KBD_BUF_SIZE]uint8 buf_head: static uint32 = 0 buf_tail: static uint32 = 0 buf_count: static uint32 = 0 -kbd_irq_cnt: static uint64 = 0 +kbd_irq_cnt: static uint64 = 0 +# 硬件初始化只做一次(init 有多个调用点,见 init() 内注释) +_kbd_inited: static int = 0 shift_l: static uint8 = 0 shift_r: static uint8 = 0 @@ -70,6 +73,11 @@ def irq_handler() -> int: if (st & 0x20) != 0: return 0 scancode: uint8 = asm.inb(KBD_DATA) + # 按键验证:把 IRQ1 收到的扫描码打到串口(高位 0x80 = 释放), + # 用来确认"键盘中断是否到达"这一环。 + _kmsg: [48]char + viperlib.snprintf(&_kmsg, 48, "[kbd] sc=0x%x\n", int(scancode)) + serial.puts(&_kmsg) if buf_count < KBD_BUF_SIZE: buffer[buf_tail] = scancode buf_tail = buf_tail + 1 @@ -106,6 +114,14 @@ def has_data() -> int: return buf_count def init(): + global _kbd_inited + # 幂等:init() 有两个调用点(devfs 注册 /dev/input/keyboard、kbd_svc.start), + # 重复执行会再次发 0xAD 禁用键盘接口,而那次 CCB 写入往往失败(键盘数据串入 + # CCB 读取,回读为 0)⇒ 0xAE 恢复不了 ⇒ 接口停在禁用态 ⇒ 按键不再产生任何字节 + # (现场:ki 恒为 2/3,desktop 轮询永远看不到键盘字节,键盘完全无法输入)。 + if _kbd_inited != 0: + return + _kbd_inited = 1 drain: uint32 drain = 0 while (asm.inb(KBD_STATUS) & 0x01) != 0: diff --git a/VKernel/Kernel/intr/idt.vp b/VKernel/Kernel/intr/idt.vp index 8f75fc7..76f8be4 100644 --- a/VKernel/Kernel/intr/idt.vp +++ b/VKernel/Kernel/intr/idt.vp @@ -24,20 +24,6 @@ export idt: [256]idt_entry = [] typedef irq_handler_t = callable[[], int] -def _SafeStackWord(addr: uint64) -> uint64: - """异常处理专用安全解引用:仅当 addr 落在**内核**区间(0x1000..0x80000000)才读。 - - 异常处理里绝不允许再触发一次 #PF:一旦读到用户地址(典型是 iret 帧里压入的 - user_rsp=0x7fffffe00000),就会在 #PF 处理中再次 #PF → 无限重入 → 内核栈耗尽 - → 三重故障(QEMU 窗口直接消失,且**看不到真正的首故障**)。 - """ - if addr < 0x1000 or addr >= 0x80000000: return 0 - v: uint64 = 0 - __asm__(f"""mov rax, {asminp(addr, ASM_DESCR.REG_ANY)} - mov {asmout(v, ASM_DESCR.OUTPUT_REG)}, [rax]""", - op=[ASM_DESCR.CLOBBER_RAX]) - return v - extern def isr0() extern def isr1() extern def isr2() @@ -125,160 +111,6 @@ def isrCommonhandler(interrupt: int): mov al, 93 out dx, al""", op=[ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX]) - # Print CR2 for page faults (ISR 14) - if interrupt == 14: - cr2: uint64 = 0 - __asm__(f"""mov {asmout(cr2, ASM_DESCR.OUTPUT_REG)}, cr2""", - op=[ASM_DESCR.CLOBBER_RAX]) - fault_rip: uint64 = 0 - fault_err: uint64 = 0 - fault_rsp: uint64 = 0 - __asm__(f"""mov {asmout(fault_rip, ASM_DESCR.OUTPUT_REG)}, [_fault_rip] - mov {asmout(fault_err, ASM_DESCR.OUTPUT_REG)}, [_fault_err] - mov {asmout(fault_rsp, ASM_DESCR.OUTPUT_REG)}, [_fault_rsp]""", - op=[ASM_DESCR.CLOBBER_RAX]) - buf: [100]char - viperlib.snprintf(&buf, 100, "[PF] cr2=0x%lx rip=0x%lx err=0x%lx rsp=0x%lx\n", cr2, fault_rip, fault_err, fault_rsp) - serial.puts(buf) - # [CR3] 当前 pml4:定位栈区/对象写缺页是否因 pml4 被 switch_pid 切走 - # (内核 pml4=0xf801000,用户 pml4=0x9781xxx —— 若显示用户 pml4 而 rsp - # 在 bootloader 栈区 0x60000-0x80000,则是进程切换后未切回内核页表)。 - cur_cr3: uint64 = 0 - __asm__(f"mov {asmout(cur_cr3, ASM_DESCR.OUTPUT_REG)}, cr3", - op=[ASM_DESCR.CLOBBER_RAX]) - cr3_buf: [80]char - viperlib.snprintf(&cr3_buf, 80, "[CR3] pml4=0x%lx\n", cur_cr3) - serial.puts(cr3_buf) - # [临时] syscall 用户 rsp 对照:入口值 vs 返回时待恢复值。 - # 两者不同 ⇒ 内核在 syscall 期间覆盖了 gs:[0x0];两者相同且都异常 ⇒ app 自己弄坏了 rsp。 - sc_ent: uint64 = 0 - sc_ret: uint64 = 0 - __asm__(f"""mov rax, [_sc_ent_rsp] - mov {asmout(sc_ent, ASM_DESCR.OUTPUT_REG)}, rax - mov rax, [_sc_ret_rsp] - mov {asmout(sc_ret, ASM_DESCR.OUTPUT_REG)}, rax""", - op=[ASM_DESCR.CLOBBER_RAX]) - sc_buf: [160]char - viperlib.snprintf(&sc_buf, 160, "[SYSCALL-RSP] entry=0x%lx ret=0x%lx\n", sc_ent, sc_ret) - serial.puts(sc_buf) - # [RAX] 崩溃时 rax(isr.s 保存):定位 cr2 与 rax 关系(rax-偏移=cr2) - f_rax: uint64 = 0 - __asm__(f"""mov {asmout(f_rax, ASM_DESCR.OUTPUT_REG)}, [_fault_rax]""", - op=[ASM_DESCR.CLOBBER_RAX]) - rax_buf: [80]char - viperlib.snprintf(&rax_buf, 80, "[RAX] rax=0x%lx\n", f_rax) - serial.puts(rax_buf) - # [STK] rbp 链回溯(定位栈损坏源)。**每一跳都必须先校验再解引用**(_SafeStackWord): - # 旧实现是 `mov rax,[rax]; mov rax,[rax+8]` 的裸链式解引用,一旦链上出现用户地址 - # (实测就是 iret 帧里压入的 user_rsp=0x7fffffe00000)就会在 #PF 处理中再触发 #PF - # → 无限重入、rsp 每次下移 ~0x2A0 → 内核栈耗尽 → 三重故障,且真正的首故障被淹没。 - # 用户态 rbp(>=0x80000000)直接不回溯。 - fr0: uint64 = 0 - __asm__(f"""mov rax, [_fault_rbp] - mov {asmout(fr0, ASM_DESCR.OUTPUT_REG)}, rax""", - op=[ASM_DESCR.CLOBBER_RAX]) - stk_buf: [160]char - if fr0 < 0x1000 or fr0 >= 0x80000000: - viperlib.snprintf(&stk_buf, 160, "[STK] rbp=0x%lx out of kernel range, chain skipped\n", fr0) - serial.puts(stk_buf) - else: - fr1: uint64 = _SafeStackWord(fr0 + 8) - rbp1: uint64 = _SafeStackWord(fr0) - fr2: uint64 = _SafeStackWord(rbp1 + 8) - rbp2: uint64 = _SafeStackWord(rbp1) - fr3: uint64 = _SafeStackWord(rbp2 + 8) - rbp3: uint64 = _SafeStackWord(rbp2) - fr4: uint64 = _SafeStackWord(rbp3 + 8) - viperlib.snprintf(&stk_buf, 160, "[STK] rbp=0x%lx ret: 0x%lx 0x%lx 0x%lx 0x%lx\n", - fr0, fr1, fr2, fr3, fr4) - serial.puts(stk_buf) - elif interrupt == 13: - gp_rip: uint64 = 0 - gp_err: uint64 = 0 - gp_rsp: uint64 = 0 - gp_cr4: uint64 = 0 - gp_cr0: uint64 = 0 - __asm__(f"""mov {asmout(gp_rip, ASM_DESCR.OUTPUT_REG)}, [_fault_rip] - mov {asmout(gp_err, ASM_DESCR.OUTPUT_REG)}, [_fault_err] - mov {asmout(gp_rsp, ASM_DESCR.OUTPUT_REG)}, [_fault_rsp] - mov rax, cr4 - mov {asmout(gp_cr4, ASM_DESCR.OUTPUT_REG)}, rax - mov rax, cr0 - mov {asmout(gp_cr0, ASM_DESCR.OUTPUT_REG)}, rax""", - op=[ASM_DESCR.CLOBBER_RAX]) - gp_buf: [140]char - viperlib.snprintf(&gp_buf, 140, "[GP] rip=0x%lx err=0x%lx rsp=0x%lx cr4=0x%lx cr0=0x%lx\n", - gp_rip, gp_err, gp_rsp, gp_cr4, gp_cr0) - serial.puts(gp_buf) - # [GP-RAX]:崩溃时的 rax(isr.s 保存的 _fault_rax)。 - # #GP(err=0) 最常见于"写/读非规范地址"(如 mm 空闲链 next/prev 被写坏后 - # `mov [rax+0x10], rcx`):rax 就是那个坏基址,+0x10 即触发的那次访存地址。 - # 现场缺这一条,只剩"[STK] rbp=0x0"无从定位,故按 #PF 分支的口径补齐。 - g_frax: uint64 = 0 - __asm__(f"""mov rax, [_fault_rax] - mov {asmout(g_frax, ASM_DESCR.OUTPUT_REG)}, rax""", - op=[ASM_DESCR.CLOBBER_RAX]) - gp_reg_buf: [140]char - viperlib.snprintf(&gp_reg_buf, 140, "[GP-RAX] rax=0x%lx (rax+0x10=0x%lx)\n", - g_frax, g_frax + 16) - serial.puts(gp_reg_buf) - # [STK] rbp 链回溯(与 #PF 分支同款安全口径:每跳先校验再解引用,见 _SafeStackWord) - gfr0: uint64 = 0 - __asm__(f"""mov rax, [_fault_rbp] - mov {asmout(gfr0, ASM_DESCR.OUTPUT_REG)}, rax""", - op=[ASM_DESCR.CLOBBER_RAX]) - gp_stk_buf: [160]char - if gfr0 < 0x1000 or gfr0 >= 0x80000000: - viperlib.snprintf(&gp_stk_buf, 160, "[STK] rbp=0x%lx out of kernel range, chain skipped\n", gfr0) - serial.puts(gp_stk_buf) - else: - gfr1: uint64 = _SafeStackWord(gfr0 + 8) - grbp1: uint64 = _SafeStackWord(gfr0) - gfr2: uint64 = _SafeStackWord(grbp1 + 8) - grbp2: uint64 = _SafeStackWord(grbp1) - gfr3: uint64 = _SafeStackWord(grbp2 + 8) - grbp3: uint64 = _SafeStackWord(grbp2) - gfr4: uint64 = _SafeStackWord(grbp3 + 8) - viperlib.snprintf(&gp_stk_buf, 160, "[STK] rbp=0x%lx ret: 0x%lx 0x%lx 0x%lx 0x%lx\n", - gfr0, gfr1, gfr2, gfr3, gfr4) - serial.puts(gp_stk_buf) - else: - # 其他异常(#UD 等):打印 rip/rsp 定位执行位置(isr.s 已保存 _fault_rip) - ex_rip: uint64 = 0 - ex_rsp: uint64 = 0 - __asm__(f"""mov {asmout(ex_rip, ASM_DESCR.OUTPUT_REG)}, [_fault_rip] - mov {asmout(ex_rsp, ASM_DESCR.OUTPUT_REG)}, [_fault_rsp]""", - op=[ASM_DESCR.CLOBBER_RAX]) - ex_buf: [100]char - viperlib.snprintf(&ex_buf, 100, "[EX] int=%d rip=0x%lx rsp=0x%lx\n", interrupt, ex_rip, ex_rsp) - serial.puts(ex_buf) - # [RAX] 崩溃时 rax(isr.s 保存) - f_rax: uint64 = 0 - __asm__(f"""mov {asmout(f_rax, ASM_DESCR.OUTPUT_REG)}, [_fault_rax]""", - op=[ASM_DESCR.CLOBBER_RAX]) - rax_buf: [80]char - viperlib.snprintf(&rax_buf, 80, "[RAX] rax=0x%lx\n", f_rax) - serial.puts(rax_buf) - # [STK] rbp 链回溯(与 #PF 分支同款安全口径:每跳先校验再解引用,见 _SafeStackWord) - fr0: uint64 = 0 - __asm__(f"""mov rax, [_fault_rbp] - mov {asmout(fr0, ASM_DESCR.OUTPUT_REG)}, rax""", - op=[ASM_DESCR.CLOBBER_RAX]) - stk_buf: [160]char - if fr0 < 0x1000 or fr0 >= 0x80000000: - viperlib.snprintf(&stk_buf, 160, "[STK] rbp=0x%lx out of kernel range, chain skipped\n", fr0) - serial.puts(stk_buf) - else: - fr1: uint64 = _SafeStackWord(fr0 + 8) - rbp1: uint64 = _SafeStackWord(fr0) - fr2: uint64 = _SafeStackWord(rbp1 + 8) - rbp2: uint64 = _SafeStackWord(rbp1) - fr3: uint64 = _SafeStackWord(rbp2 + 8) - rbp3: uint64 = _SafeStackWord(rbp2) - fr4: uint64 = _SafeStackWord(rbp3 + 8) - viperlib.snprintf(&stk_buf, 160, "[STK] rbp=0x%lx ret: 0x%lx 0x%lx 0x%lx 0x%lx\n", - fr0, fr1, fr2, fr3, fr4) - serial.puts(stk_buf) s: sched.Scheduler* = sched._sched_ptr # 如果调度器未初始化,直接进入安全停机状态 if s == 0: @@ -366,7 +198,16 @@ export def isr31_handler(): isrCommonhandler(31) export def isr32_handler(): timer.timer_handler() export def isr33_handler(): + global _kbd_irq_seen handler: irq_handler_t = irqGetHandler(1) + # 键盘验证:IRQ1 是否真的到达 IDT,以及 handler 是否已安装。 + # handler=0x0 → 到达了但没装上(键盘仍不可用,问题在安装侧); + # 整行不出现 → IRQ1 根本没到(问题在控制器/设备侧)。只打前 8 次。 + if _kbd_irq_seen < 8: + _kbd_irq_seen += 1 + _kb: [64]char + viperlib.snprintf(&_kb, 64, "[kbd] irq1 handler=%lx\n", uint64(handler)) + serial.puts(&_kb) if handler: __asm__(f"""call {asminp(handler, ASM_DESCR.REG_ANY)}""", op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX, @@ -577,7 +418,9 @@ def init_irqs(): setGate(46, uint64(isr46), CODE_SEG, 0x8E) setGate(47, uint64(isr47), CODE_SEG, 0x8E) -irq_handlers: static [16]irq_handler_t = [None] +irq_handlers: static [16]irq_handler_t = [None] +# 键盘验证:IRQ1 到达次数(仅用于打印前若干次) +_kbd_irq_seen: static int = 0 def irqInstallHandler(irq: int, handler: irq_handler_t, name: str) -> int: global irq_handlers diff --git a/VKernel/Kernel/services/desktop.vp b/VKernel/Kernel/services/desktop.vp index ca256d4..4308628 100644 --- a/VKernel/Kernel/services/desktop.vp +++ b/VKernel/Kernel/services/desktop.vp @@ -887,6 +887,12 @@ def _update_keyboard(): continue _held_key_add(sc, 0) ascii_ch: int = _ps2_scancode_to_ascii(sc) + # 按键验证:把本帧解出的字符打到串口,确认"键盘 → 扫描码 → 字符"全链路正常。 + # 不要求有焦点窗口(便于在无窗口时也能验证键盘本身)。 + if ascii_ch != 0: + _kmsg: [64]char + viperlib.snprintf(&_kmsg, 64, "[kbd] key='%c' code=%d sc=0x%x\n", ascii_ch, ascii_ch, int(sc)) + serial.puts(&_kmsg) if ascii_ch != 0 and _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active: push_evt: InputEvent push_evt.type = EVENT_TYPE_KEY @@ -1870,43 +1876,43 @@ def desktop_thread(arg: rawptr) -> int: sched.Scheduler.try_reschedule() return 0 -# .bss 不变量影子 + 校验:_fb/_screen_w/_screen_h/_fb_pitch/_back_fb/_row_buf/_bg_row_buf/ -# _dirty_rows 都是 .bss 全局,初始化后应恒定不变(曾观测到它们被偶发写坏/清零, -# 导致刷新写到物理 0 起、覆盖内核自身)。桌面线程每秒比对一次,命中即 fail-fast。 -_InvFb: uint64 = 0 -_InvW: int = 0 -_InvH: int = 0 -_InvPitch: int = 0 -_InvBack: uint64 = 0 -_InvRow: uint64 = 0 -_InvBgRow: uint64 = 0 -_InvDirty: uint64 = 0 - -def _SetFrameInvariants(): - global _InvFb, _InvW, _InvH, _InvPitch, _InvBack, _InvRow, _InvBgRow, _InvDirty - _InvFb = uint64(_fb) - _InvW = _screen_w - _InvH = _screen_h - _InvPitch = _fb_pitch - _InvBack = uint64(_back_fb) - _InvRow = uint64(_row_buf) - _InvBgRow = uint64(_bg_row_buf) - _InvDirty = uint64(_dirty_rows) - -def _CheckFrameInvariants(): - if (uint64(_fb) != _InvFb or _screen_w != _InvW or _screen_h != _InvH or _fb_pitch != _InvPitch - or uint64(_back_fb) != _InvBack or uint64(_row_buf) != _InvRow - or uint64(_bg_row_buf) != _InvBgRow or uint64(_dirty_rows) != _InvDirty): - b: [224]char - viperlib.snprintf(&b, 224, - "[FATAL] .bss invariants broken: fb=0x%lx/%lx w=%d/%d h=%d/%d pitch=%d/%d back=0x%lx/%lx row=0x%lx/%lx bgr=0x%lx/%lx dr=0x%lx/%lx\n", - uint64(_fb), _InvFb, _screen_w, _InvW, _screen_h, _InvH, _fb_pitch, _InvPitch, - uint64(_back_fb), _InvBack, uint64(_row_buf), _InvRow, - uint64(_bg_row_buf), _InvBgRow, uint64(_dirty_rows), _InvDirty) - serial.puts(&b) - while True: - sched.Scheduler._yield() - +# .bss 不变量影子 + 校验:_fb/_screen_w/_screen_h/_fb_pitch/_back_fb/_row_buf/_bg_row_buf/ +# _dirty_rows 都是 .bss 全局,初始化后应恒定不变(曾观测到它们被偶发写坏/清零, +# 导致刷新写到物理 0 起、覆盖内核自身)。桌面线程每秒比对一次,命中即 fail-fast。 +_InvFb: uint64 = 0 +_InvW: int = 0 +_InvH: int = 0 +_InvPitch: int = 0 +_InvBack: uint64 = 0 +_InvRow: uint64 = 0 +_InvBgRow: uint64 = 0 +_InvDirty: uint64 = 0 + +def _SetFrameInvariants(): + global _InvFb, _InvW, _InvH, _InvPitch, _InvBack, _InvRow, _InvBgRow, _InvDirty + _InvFb = uint64(_fb) + _InvW = _screen_w + _InvH = _screen_h + _InvPitch = _fb_pitch + _InvBack = uint64(_back_fb) + _InvRow = uint64(_row_buf) + _InvBgRow = uint64(_bg_row_buf) + _InvDirty = uint64(_dirty_rows) + +def _CheckFrameInvariants(): + if (uint64(_fb) != _InvFb or _screen_w != _InvW or _screen_h != _InvH or _fb_pitch != _InvPitch + or uint64(_back_fb) != _InvBack or uint64(_row_buf) != _InvRow + or uint64(_bg_row_buf) != _InvBgRow or uint64(_dirty_rows) != _InvDirty): + b: [224]char + viperlib.snprintf(&b, 224, + "[FATAL] .bss invariants broken: fb=0x%lx/%lx w=%d/%d h=%d/%d pitch=%d/%d back=0x%lx/%lx row=0x%lx/%lx bgr=0x%lx/%lx dr=0x%lx/%lx\n", + uint64(_fb), _InvFb, _screen_w, _InvW, _screen_h, _InvH, _fb_pitch, _InvPitch, + uint64(_back_fb), _InvBack, uint64(_row_buf), _InvRow, + uint64(_bg_row_buf), _InvBgRow, uint64(_dirty_rows), _InvDirty) + serial.puts(&b) + while True: + sched.Scheduler._yield() + def start(fb_ptr: rawptr, w: int, h: int, pitch: int = 0) -> sched.Thread*: global _fb, _screen_w, _screen_h, _fb_pitch, _cursor_x, _cursor_y global _bg_row_buf, _back_fb, _row_buf @@ -2626,13 +2632,13 @@ def is_active(win_id: int) -> int: if win_id < 0 or win_id >= _win_count: return 0 return _wins[win_id].active -def get_fb_bytes(win_id: int) -> int: - """窗口后备缓冲的字节数(WIN_FLUSH 从 app 的用户缓冲拷贝时用;须与 get_fb 配对取)。""" - if win_id < 0 or win_id >= _win_count: return 0 - aw: AppWindow* = &_wins[win_id] - if not aw.active: return 0 - return aw.fb_w * aw.fb_h * 4 - +def get_fb_bytes(win_id: int) -> int: + """窗口后备缓冲的字节数(WIN_FLUSH 从 app 的用户缓冲拷贝时用;须与 get_fb 配对取)。""" + if win_id < 0 or win_id >= _win_count: return 0 + aw: AppWindow* = &_wins[win_id] + if not aw.active: return 0 + return aw.fb_w * aw.fb_h * 4 + def get_fb(win_id: int) -> uint32*: if win_id < 0 or win_id >= _win_count: return None aw: AppWindow* = &_wins[win_id]