From 7e7204a09adb8d12547c72898dea258019d3b443 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 17 Oct 2026 16:23:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(mm,explorer):=20slab=20=E9=A1=B5=E4=B8=A5?= =?UTF-8?q?=E6=A0=BC=E5=BD=92=E5=B1=9E=E4=B8=8E=E5=88=86=E9=85=8D=E4=BD=8D?= =?UTF-8?q?=E5=9B=BE=EF=BC=9B=E5=85=B3=E9=97=AD=E6=8C=89=E9=92=AE=E6=8C=89?= =?UTF-8?q?=E4=B8=8B=E6=80=81=E4=B8=8E=E6=9D=BE=E6=89=8B=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ① [mm] BAD blk 的解码:site=2=free_block、reason=1=magic 失配;blk 实际是 slab_page+16(页在 0x1b56020),sz 是该页 free_head,而非块大小。被踩的是页头前 8~16 字节,最可能是无锁 buddy 在 usb.init(sched.enable 之后)与用户进程 MMAP 并发 malloc 时重复授予同一 8KB 块。② mm 加固:_slab_find_page 不再只看 magic,新增 _slab_page_owns(block_ptr 回指、指针在对象区内、按 obj_size 对齐、序号 < total_objs);slab_page 新增 alloc_bits,只有已分配对象可 free,重复释放/野指针被拒,避免 free_count 提前到 total_objs 把仍在用的页拆回 buddy;free_obj 拒绝已全空继续收对象;_create_page 失败路径清零 magic。③ hid_ascii 仅只读且已有边界检查,非本次堆损坏来源;usb_kbd 把 code >= 64 改为显式早返回。④ 关闭按钮:按下只置 close_down 并置脏(按钮画按下态:CLASSIC 40,40,70→20,20,45;WIN7/XP 红底 200,50,50→140,25,25;WIN10/11 补深灰底),松手且仍命中才发 CLOSE + used=0,移出则撤销按下态恢复;几何复用 _close_btn_* 同一套函数。 --- Apps/Explorer/desk_server.vp | 86 ++++++++++++++++------- VKernel/Kernel/drivers/usb/hid/usb_kbd.vp | 19 ++--- VKernel/Kernel/mm/mm.vp | 81 +++++++++++++++++++-- 3 files changed, 146 insertions(+), 40 deletions(-) diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp index 6166d1e..3d3f7bd 100644 --- a/Apps/Explorer/desk_server.vp +++ b/Apps/Explorer/desk_server.vp @@ -144,8 +144,9 @@ class DeskServer: pcy: int focused: int next_z: int - btn_down: int - drag_win: int + btn_down: int + close_down: int # 正在被按下的关闭键所属窗口(-1 = 无) + drag_win: int drag_dx: int drag_dy: int # 复用缓冲 @@ -189,6 +190,7 @@ class DeskServer: self.focused = -1 self.next_z = 1 self.btn_down = 0 + self.close_down = -1 self.drag_win = -1 self.drag_dx = 0 self.drag_dy = 0 @@ -679,7 +681,7 @@ class DeskServer: if hi > lo: self.row_fill(bx + lo, yy, hi - lo, col) - def _draw_close_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int): + def _draw_close_row(self, bx: int, by: int, bw: int, bh: int, yy: int, style: int, pressed: int): lry: int = yy - by if lry < 0 or lry >= bh: return cx: int = bx + bw / 2 @@ -688,12 +690,19 @@ class DeskServer: if bw >= 30: d = 4 x_col: uint32 = self.rgb(uint32(200), uint32(200), uint32(200)) if style == STYLE_CLASSIC: - self.row_fill(bx, yy, bw, self.rgb(uint32(40), uint32(40), uint32(70))) + base_col: uint32 = self.rgb(uint32(40), uint32(40), uint32(70)) + if pressed != 0: base_col = self.rgb(uint32(20), uint32(20), uint32(45)) + self.row_fill(bx, yy, bw, base_col) elif style == STYLE_WIN7 or style == STYLE_WINXP: - self._round_fill_row(bx, by, bw, bh, yy, self.rgb(uint32(200), uint32(50), uint32(50)), 3) + base_col2: uint32 = self.rgb(uint32(200), uint32(50), uint32(50)) + if pressed != 0: base_col2 = self.rgb(uint32(140), uint32(25), uint32(25)) + self._round_fill_row(bx, by, bw, bh, yy, base_col2, 3) x_col = self.rgb(uint32(255), uint32(255), uint32(255)) else: x_col = self.rgb(uint32(150), uint32(150), uint32(150)) + # WIN10/WIN11 平时无底色,按下时补一块深灰底做按压反馈 + if pressed != 0: + self.row_fill(bx, yy, bw, self.rgb(uint32(70), uint32(70), uint32(70))) i: int = yy - cy if i >= 0 - d and i <= d: self.row_fill(cx + i, yy, 1, x_col) @@ -739,7 +748,17 @@ class DeskServer: if style == STYLE_WIN7 or style == STYLE_WINXP: return 5 return 4 - def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int): + # 关闭键命中:与 _draw_window_btns 共用同一套几何 + def _close_btn_hit(self, s: desk.WinSlot*, x: int, y: int) -> int: + cw: int = self._close_btn_w(s.style) + ch: int = self._close_btn_h(s.style) + bx: int = s.ux + s.w - cw - self._close_btn_pad(s.style) + by: int = s.uy + (TITLE_H - ch) / 2 + if x >= bx and x < bx + cw and y >= by and y < by + ch: + return 1 + return 0 + + def _draw_window_btns(self, s: desk.WinSlot*, y0: int, y1: int, th: int, close_pressed: int): wx: int = s.ux wy: int = s.uy ww: int = s.w @@ -757,7 +776,7 @@ class DeskServer: yend: int = y1 if yend > by + bh: yend = by + bh while yy < yend: - self._draw_close_row(close_x, by, bw, bh, yy, style) + self._draw_close_row(close_x, by, bw, bh, yy, style, close_pressed) self._draw_min_row(min_x, by, bw, bh, yy, style) self._draw_max_row(max_x, by, bw, bh, yy, style) yy += 1 @@ -1038,7 +1057,9 @@ class DeskServer: title_alpha, buf, fw, fh, content_y0) yy += 1 - self._draw_window_btns(s, y0, y1, th) + close_pressed: int = 0 + if self.close_down == win: close_pressed = 1 + self._draw_window_btns(s, y0, y1, th, close_pressed) if s.title[0] != 0: text_y: int = wy + (th - vpui.CHAR_H) / 2 if text_y + vpui.CHAR_H > y0 and text_y < y1: @@ -1157,6 +1178,7 @@ class DeskServer: if self.focused == win: self.focused = -1 # 拖拽中的窗口被销毁:必须清掉 drag_win,否则后续移动会继续操作这个死槽位。 if self.drag_win == win: self.drag_win = -1 + if self.close_down == win: self.close_down = -1 self.mark_all_windows() self.mark_titles() elif ty == uint32(desk.DESK_REQ_MOVE): @@ -1287,6 +1309,12 @@ class DeskServer: self.mark(self.cy - 1, CURSOR_BMP_H + 2) self.pcx = self.cx self.pcy = self.cy + # 按住关闭键时移出按钮:撤销按下态并置脏恢复 + if self.close_down >= 0: + cm: desk.WinSlot* = self.slot(self.close_down) + if cm.used == 0 or self._close_btn_hit(cm, self.cx, self.cy) == 0: + if cm.used != 0: self.mark(cm.uy, TITLE_H) + self.close_down = -1 # 悬停:把 HOVER 投给光标下那个窗口(窗口内坐标) if self.btn_down == 0: hw: int = self.hit_test(self.cx, self.cy) @@ -1297,6 +1325,7 @@ class DeskServer: self.cx - hs.ux, self.cy - hs.uy - TITLE_H - TITLE_GAP, 0) if saw_press != 0: self.btn_down = 1 + self.close_down = -1 # 新手势开始:清掉上一次可能丢失释放沿留下的按下态 # 任务栏图标优先:命中则激活并跳过窗口命中/拖动 hit: int = -1 tw: int = self._taskbar_icon_hit(self.cx, self.cy) @@ -1306,25 +1335,13 @@ class DeskServer: hit = self.hit_test(self.cx, self.cy) if hit >= 0: s: desk.WinSlot* = self.slot(hit) - # 关闭键热区按样式尺寸算,必须与 _draw_window_btns 一致 - cw: int = self._close_btn_w(s.style) - ch: int = self._close_btn_h(s.style) - bx: int = s.ux + s.w - cw - self._close_btn_pad(s.style) - by: int = s.uy + (TITLE_H - ch) / 2 - in_close: int = 0 - if self.cx >= bx and self.cx < bx + cw and self.cy >= by and self.cy < by + ch: - in_close = 1 + # 关闭键热区复用 _close_btn_hit(与 _draw_window_btns 同一套几何) + in_close: int = self._close_btn_hit(s, self.cx, self.cy) self.focus(hit) if in_close != 0: - # 关闭:通知一次 CLOSE 后把槽位置空回收;window.vp 的 is_active() - # 见到 used==0 即返回 0,app 的 while 循环自然退出。 - # 像素缓冲不回收(arena 是 bump 分配),只放槽位。 - self.push_event(hit, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0, 0) - self.mark_win(s.uy, s.h) - s.used = 0 - if self.focused == hit: self.focused = -1 - if self.drag_win == hit: self.drag_win = -1 - self.mark_titles() + # 只进入按下态:松手时才真正关窗,保证按下反馈至少能刷出一帧。 + self.close_down = hit + self.mark(s.uy, TITLE_H) elif self.cy < s.uy + TITLE_H: # 标题栏(非关闭按钮)⇒ 开始拖动 self.drag_win = hit @@ -1339,7 +1356,24 @@ class DeskServer: # 否则 btn_down 卡在 1,后续点击永远检测不到按下沿。 if cur == 0 and self.btn_down != 0: self.btn_down = 0 - if self.drag_win < 0: + cw_win: int = self.close_down + self.close_down = -1 + if cw_win >= 0: + cs: desk.WinSlot* = self.slot(cw_win) + if cs.used != 0: + self.mark(cs.uy, TITLE_H) + if self._close_btn_hit(cs, self.cx, self.cy) != 0: + # 关闭:通知一次 CLOSE 后把槽位置空回收;window.vp 的 is_active() + # 见到 used==0 即返回 0,app 的 while 循环自然退出。 + # 像素缓冲不回收(arena 是 bump 分配),只放槽位。 + self.push_event(cw_win, uint32(desk.DESK_EVT_CLOSE), 0, 0, 0, 0) + self.mark_win(cs.uy, cs.h) + cs.used = 0 + if self.focused == cw_win: self.focused = -1 + if self.drag_win == cw_win: self.drag_win = -1 + self.mark_all_windows() + self.mark_titles() + elif self.drag_win < 0: # 松手:投 RELEASE 给光标下的窗口 rw: int = self.hit_test(self.cx, self.cy) if rw >= 0: diff --git a/VKernel/Kernel/drivers/usb/hid/usb_kbd.vp b/VKernel/Kernel/drivers/usb/hid/usb_kbd.vp index 72e0381..0f2c426 100644 --- a/VKernel/Kernel/drivers/usb/hid/usb_kbd.vp +++ b/VKernel/Kernel/drivers/usb/hid/usb_kbd.vp @@ -63,15 +63,16 @@ def _decode_press(code: uint8, modifier: uint8): elif code == uint8(0x4C): keyboard.inject_csi_tilde(uint8(0x33)) # Delete elif code == uint8(0x4B): keyboard.inject_csi_tilde(uint8(0x35)) # PageUp elif code == uint8(0x4E): keyboard.inject_csi_tilde(uint8(0x36)) # PageDown - elif int(code) < 64: - shifted: bool = (modifier & uint8(0x22)) != 0 - if kbd_caps != 0 and code >= uint8(0x04) and code <= uint8(0x1D): - shifted = not shifted - ch: char = hid_ascii[int(code)] - if shifted: - ch = hid_ascii_shift[int(code)] - if ch != '\0': - keyboard.inject_byte(uint8(ch)) + # usage ID 最大 0xE7;ASCII 表只有 64 项,越界前必须先拒绝(表是只读的,但读越界同样非法)。 + if int(code) >= 64: return + shifted: bool = (modifier & uint8(0x22)) != 0 + if kbd_caps != 0 and code >= uint8(0x04) and code <= uint8(0x1D): + shifted = not shifted + ch: char = hid_ascii[int(code)] + if shifted: + ch = hid_ascii_shift[int(code)] + if ch != '\0': + keyboard.inject_byte(uint8(ch)) def init() -> int: global kbd_dev, kbd_initialized, kbd_slot_id diff --git a/VKernel/Kernel/mm/mm.vp b/VKernel/Kernel/mm/mm.vp index 815f31b..8398ca8 100644 --- a/VKernel/Kernel/mm/mm.vp +++ b/VKernel/Kernel/mm/mm.vp @@ -66,9 +66,10 @@ class __buddy_system: end_addr: rawptr # 内存结束地址 -define SLAB_MAGIC = 0x534C4142 -define SLAB_NUM_SIZES = 6 -define SLAB_THRESHOLD = 1024 +define SLAB_MAGIC = 0x534C4142 +define SLAB_NUM_SIZES = 6 +define SLAB_THRESHOLD = 1024 +define SLAB_BITMAP_BYTES = 32 class slab_page: magic: uint32 @@ -80,6 +81,10 @@ class slab_page: block_ptr: memory_block* next: slab_page* prev: slab_page* + # 每对象 1 bit:只有置位的对象才能被 free。释放未分配对象(重复释放/野指针撞上 + # 页内空闲对象)会让 free_count 提前到达 total_objs ⇒ 页被拆回 buddy,而页内仍 + # 有活对象,随后它的 free 就只剩 magic=0 的旧页头(现场 0x1b56030 即 page+16)。 + alloc_bits: [SLAB_BITMAP_BYTES]uint8 # 4K对齐函数 @@ -326,18 +331,62 @@ def _slab_write_ptr(addr: rawptr, val: rawptr): mov [{asminp(addr, ASM_DESCR.REG_ANY)}], rax""", op=[ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_MEMORY]) +# 对象在页内的序号(-1 = 不在对象边界上) +def _slab_bit_index(page: slab_page*, ptr: rawptr) -> int: + if page.obj_size == 0: return -1 + hdr_off: uint64 = memory_block.__sizeof__() + block: uint64 = uint64(page) - hdr_off + data: uint64 = block + uint64(page.data_offset) + if uint64(ptr) < data: return -1 + return int((uint64(ptr) - data) / uint64(page.obj_size)) + +def _slab_bit_set(page: slab_page*, idx: int): + if idx < 0 or idx >= SLAB_BITMAP_BYTES * 8: return + byte_idx: int = idx / 8 + page.alloc_bits[byte_idx] = uint8(page.alloc_bits[byte_idx] | uint8(1 << (idx & 7))) + +def _slab_bit_clear(page: slab_page*, idx: int): + if idx < 0 or idx >= SLAB_BITMAP_BYTES * 8: return + byte_idx: int = idx / 8 + page.alloc_bits[byte_idx] = uint8(page.alloc_bits[byte_idx] & ~uint8(1 << (idx & 7))) + +def _slab_bit_test(page: slab_page*, idx: int) -> int: + if idx < 0 or idx >= SLAB_BITMAP_BYTES * 8: return 0 + byte_idx: int = idx / 8 + mask: uint8 = uint8(1 << (idx & 7)) + if (page.alloc_bits[byte_idx] & mask) != 0: return 1 + return 0 + +# slab 页归属校验:页头必须自洽(block_ptr 指回本页所在块),且 ptr 必须落在页内 +# [data_offset, data_offset + total*obj) 范围并按其对象大小对齐。只查 magic 会把 +# 大块分配头部残留的“看起来像页头”的数据误判为 slab 页,free 会写进页外并把仍在 +# 使用的块还给 buddy(现场:页头 magic 被清零后 free(err) 落到 buddy 路径)。 +def _slab_page_owns(page: slab_page*, ptr: rawptr, hdr_off: uint64) -> int: + if page.magic != SLAB_MAGIC: return 0 + obj: uint64 = uint64(page.obj_size) + if obj == 0: return 0 + block: uint64 = uint64(page) - hdr_off + if uint64(page.block_ptr) != block: return 0 + if uint64(page.data_offset) < hdr_off: return 0 + data: uint64 = block + uint64(page.data_offset) + if uint64(ptr) < data: return 0 + off: uint64 = uint64(ptr) - data + if (off % obj) != 0: return 0 + if (off / obj) >= uint64(page.total_objs): return 0 + return 1 + def _slab_find_page(ptr: rawptr) -> slab_page*: if ptr is None: return None hdr_off: uint64 = memory_block.__sizeof__() page_addr: uint64 = uint64(ptr) & ~uint64(0xFFF) page: slab_page* = slab_page*(page_addr + hdr_off) - if page.magic == SLAB_MAGIC: + if _slab_page_owns(page, ptr, hdr_off) != 0: os: uint16 = page.obj_size if os == 32 or os == 64 or os == 128 or os == 256 or os == 512 or os == 1024: return page page_addr2: uint64 = page_addr - 4096 page2: slab_page* = slab_page*(page_addr2 + hdr_off) - if page2.magic == SLAB_MAGIC: + if _slab_page_owns(page2, ptr, hdr_off) != 0: os2: uint16 = page2.obj_size if os2 == 32 or os2 == 64 or os2 == 128 or os2 == 256 or os2 == 512 or os2 == 1024: return page2 @@ -373,15 +422,19 @@ class _SlabAllocator: page.block_ptr = block page.next = None page.prev = None + for bi in range(SLAB_BITMAP_BYTES): + page.alloc_bits[bi] = 0 data_start: uint64 = uint64(block) + d_off # 加固:下面那个建链循环是分配器里唯一"成片写指针"的地方,一旦 total 因下溢/截断而 # 离谱,就会把 nxt 指针写到自己页外、覆盖相邻块头。先把 total 与地址范围校验成自洽。 if block.size < uint64(d_off) + uint64(aligned_size): serial.puts("[mm] slab _create_page: block too small for page header - refused\n") + page.magic = 0 # 归还前抹掉页头,避免后续把它误认成有效 slab 页 BuddySystemObject.free_block(block) return None if uint64(total) * uint64(aligned_size) + uint64(d_off) > block.size: serial.puts("[mm] slab _create_page: total*obj_size+d_off > block.size (would write outside page) - refused\n") + page.magic = 0 # 同上 BuddySystemObject.free_block(block) return None page.free_head = rawptr(data_start) @@ -406,11 +459,29 @@ class _SlabAllocator: self.caches[size_class].prev = page self.caches[size_class] = page obj: rawptr = page.free_head + _slab_bit_set(page, _slab_bit_index(page, obj)) page.free_head = _slab_read_ptr(obj) page.free_count -= 1 return obj def free_obj(self, ptr: rawptr, page: slab_page*): + # 只有“已分配”的对象能被 free:否则 free_count 会提前到 total_objs,页被拆回 + # buddy 而页内仍有活对象(后续 free 只看到 magic=0 的旧页头)。 + bi: int = _slab_bit_index(page, ptr) + if _slab_bit_test(page, bi) == 0: + _fd: [112]char + viperlib.snprintf(&_fd, 112, "[mm] slab free_obj: page=0x%lx ptr=0x%lx idx=%d not allocated - refused\n", + uint64(page), uint64(ptr), bi) + serial.puts(&_fd) + return + # 已全空的页也不能再收对象(计数/指针已不可信) + if page.free_count >= page.total_objs: + _fo: [96]char + viperlib.snprintf(&_fo, 96, "[mm] slab free_obj: page=0x%lx free=%d total=%d - refused\n", + uint64(page), int(page.free_count), int(page.total_objs)) + serial.puts(&_fo) + return + _slab_bit_clear(page, bi) _slab_write_ptr(ptr, page.free_head) page.free_head = ptr page.free_count += 1