diff --git a/.gitignore b/.gitignore index 70fd5c5..ffdf319 100644 --- a/.gitignore +++ b/.gitignore @@ -97,4 +97,8 @@ _UNCADK/ # 编译 **/output/ **/temp/ -**/__vpcbuild__/ \ No newline at end of file +**/__vpcbuild__/ +# 本地大文件 / 临时文件 +Vmware/ +NotoSansSC/ +1.py diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp index 8cabf4e..bad5281 100644 --- a/Apps/Explorer/desk_server.vp +++ b/Apps/Explorer/desk_server.vp @@ -5,6 +5,9 @@ import vpsdk.process as process import vpsdk.syscall as syscall import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表 import vpsdk.fonttools.fonttools as fonttools +import vpsdk.imagesee.imagesee as imagesee +import vpsdk.imagesee.image as image +import vpsdk.stdlib as stdlib from stdint import * import string @@ -55,6 +58,10 @@ define MIN_WIN_W = 160 define MIN_WIN_H = 100 # 窗口阴影(0 = 关闭) define SHADOW_ENABLED = 1 +# 画面同步开关: +# 1 = 每帧合成前等待垂直回扫(减少撕裂,VMware 下可能受 0x3DA 可用性影响) +# 0 = 不等回扫,最低输入延迟(拖动最跟手,但可能出现撕裂) +define SCREEN_SYNC = 1 define SHADOW_LEFT = 8 define SHADOW_RIGHT = 12 define SHADOW_TOP = 4 @@ -262,6 +269,35 @@ def _corner_alpha(px: int, py: int, cx: int, cy: int, r: int) -> int: return 255 +# --------------------------------------------------------------------------- +# 把已合成的一整段像素提交到 framebuffer。 +# +# 用 non-temporal store(movnti)+ sfence:即使 framebuffer 是回写映射, +# 也绕过数据缓存直接写到内存,扫描输出能立刻看到;同时 8 字节顺序写会合并 +# 成写合并缓冲,比逐字节/逐像素写快得多。QEMU TCG 下等价于普通写,不受影响。 +# --------------------------------------------------------------------------- +def _fb_copy(dst: uint64, src: uint64, nbytes: uint64): + if nbytes == 0: return + if (nbytes & uint64(7)) == 0: + qwords: uint64 = nbytes >> 3 + __asm__(f"""mov rdi, {asminp(dst, ASM_DESCR.REG_ANY)} + mov rsi, {asminp(src, ASM_DESCR.REG_ANY)} + mov rcx, {asminp(qwords, ASM_DESCR.REG_ANY)} + Lfbcopy: + mov rax, [rsi] + movnti qword ptr [rdi], rax + add rsi, 8 + add rdi, 8 + dec rcx + jnz Lfbcopy + sfence""", + op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX, + ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDI, + ASM_DESCR.CLOBBER_RSI]) + else: + string.memcpy(rawptr(dst), rawptr(src), nbytes) + + class DeskServer: # 屏幕 fb: uint32* # /dev/fb0 映射后的线性地址 @@ -271,6 +307,7 @@ class DeskServer: pitch_px: int fmt: uint32 # 0 = RGBA, 1 = BGRA bg_row: uint32* # 一行背景的预构造副本 + bg_full: uint32* # 壁纸(整屏);None = 使用 bg_row 纯色背景 back: uint32* # 后备缓冲:绘制先落这里,整帧后逐行拷到 fb # 共享区 shm: uint64 @@ -310,6 +347,14 @@ class DeskServer: resize_oh: int resize_sx: int resize_sy: int + # 内容双缓冲:vis_* 是当前正在合成的内容;s.fb_off/fb_w/fb_h 是应用正在 + # 绘制的新内容。应用 FLUSH 后才交换,避免 resize 过程中按新步长读旧内容。 + vis_off: [desk.DESK_MAX_WIN]uint32 + vis_cap: [desk.DESK_MAX_WIN]uint32 + vis_w: [desk.DESK_MAX_WIN]int + vis_h: [desk.DESK_MAX_WIN]int + spare_off: [desk.DESK_MAX_WIN]uint32 + spare_cap: [desk.DESK_MAX_WIN]uint32 # 标题栏按钮的 hover 状态(用于变色和刷新) hover_btn_win: int hover_btn_kind: int @@ -323,6 +368,79 @@ class DeskServer: return (uint32(255) << 24) | (r << 16) | (g << 8) | b return (uint32(255) << 24) | (b << 16) | (g << 8) | r + # 桌面壁纸:/sys/images/screen/background.jpg,等比“ cover”裁切铺满屏幕。 + # 加载失败时保持 init() 里预构造的纯色 bg_row,不影响启动。 + def _load_background(self): + img: image.Image* = imagesee.load("/sys/images/screen/background.jpg") + if img is None: + # FAT32 is shared with other booting apps; retry once before + # falling back to the solid color. + process.yield_cpu() + img = imagesee.load("/sys/images/screen/background.jpg") + if img is None: + devfs.serial_puts("[explorer] background: not found (solid color)\n") + return + w: int = self.fb_w + h: int = self.fb_h + if w <= 0 or h <= 0: return + buf: uint32* = uint32*(stdlib.malloc(uint64(w * h * 4))) + if buf is None: return + iw: int = img.width + ih: int = img.height + if iw <= 0 or ih <= 0: return + sw: int = iw + sh: int = ih + ox: int = 0 + oy: int = 0 + if iw * h > ih * w: + # 图片更宽:按高度铺满,左右裁切 + sw = (ih * w) / h + if sw < 1: sw = 1 + ox = (iw - sw) / 2 + else: + sh = (iw * h) / w + if sh < 1: sh = 1 + oy = (ih - sh) / 2 + xoff: int32* = int32*(stdlib.malloc(uint64(w * 4))) + if xoff is None: return + x: int = 0 + while x < w: + sx: int = ox + (x * sw) / w + if sx < 0: sx = 0 + if sx >= iw: sx = iw - 1 + xoff[x] = int32(sx) + x = x + 1 + y: int = 0 + while y < h: + sy: int = oy + (y * sh) / h + if sy < 0: sy = 0 + if sy >= ih: sy = ih - 1 + srow: int = sy * iw + drow: int = y * w + if self.fmt == uint32(1): + # ImageSee 的 0xAARRGGBB 与 BGRA framebuffer 内存布局一致 + x = 0 + while x < w: + buf[drow + x] = img.pixels[srow + int(xoff[x])] | uint32(0xFF000000) + x = x + 1 + else: + x = 0 + while x < w: + c: uint32 = img.pixels[srow + int(xoff[x])] + r: uint32 = (c >> 16) & uint32(0xFF) + g: uint32 = (c >> 8) & uint32(0xFF) + b: uint32 = c & uint32(0xFF) + buf[drow + x] = (uint32(0xFF) << 24) | (b << 16) | (g << 8) | r + x = x + 1 + y = y + 1 + self.bg_full = buf + devfs.serial_puts("[explorer] background: image loaded\n") + + def bg_src(self, y: int) -> uint32*: + if self.bg_full is not None: + return uint32*(uint64(self.bg_full) + uint64(y * self.fb_w) * uint64(4)) + return self.bg_row + # ---- 按 alpha/255 把 fg 混到 bg 上(RGBA/BGRA 同为 R/B@0,16、G@8,可共用 SWAR)---- def _blend(self, bg: uint32, fg: uint32, a: int) -> uint32: ia: int = 255 - a @@ -370,6 +488,14 @@ class DeskServer: self.resize_oh = 0 self.resize_sx = 0 self.resize_sy = 0 + i0: int = 0 + for i0 in range(desk.DESK_MAX_WIN): + self.vis_off[i0] = uint32(0) + self.vis_cap[i0] = uint32(0) + self.vis_w[i0] = 0 + self.vis_h[i0] = 0 + self.spare_off[i0] = uint32(0) + self.spare_cap[i0] = uint32(0) self.hover_btn_win = -1 self.hover_btn_kind = 0 # 预构造一行背景,渲染时整行 memcpy @@ -380,6 +506,9 @@ class DeskServer: col: uint32 = self.rgb(uint32(25), uint32(25), uint32(35)) for i in range(w): self.bg_row[i] = col + # 可选壁纸:整屏缓冲,失败则继续用上面的纯色行 + self.bg_full = None + self._load_background() # 后备缓冲:整屏像素,行距 = fb_w(与 framebuffer 的 pitch 无关) back_va: uint64 = syscall.mmap(0, uint64(w) * uint64(h) * uint64(4), uint64(syscall.PROT_READ | syscall.PROT_WRITE), @@ -517,24 +646,41 @@ class DeskServer: # 调整窗口外框几何。像素缓冲只在容量不足时扩容:arena 是 bump 分配, # 容量内复用可避免反复最大化/还原把 arena 耗尽。返回 1 成功 / 0 失败。 - def _set_window_rect(self, s: desk.WinSlot*, x: int, y: int, w: int, h: int) -> int: + def _set_window_rect(self, s: desk.WinSlot*, win: int, x: int, y: int, w: int, h: int) -> int: ch: int = self._content_h(h) need: int = w * ch if w <= 0 or need <= 0: return 0 - if need > int(s.fb_cap): - # 交互缩放时按 1.5 倍增长,避免拖拽过程中每加几像素就切一块新 arena; - # 最大化的一次性大跨度仍会直接按 need 分配。 - alloc_px: int = need - grow: int = int(s.fb_cap) + int(s.fb_cap) / 2 - if grow > alloc_px: alloc_px = grow - off: uint32 = desk.desk_arena_alloc(self.shm, uint32(alloc_px * 4)) - if off == uint32(0): return 0 - s.fb_off = off - s.fb_cap = uint32(alloc_px) - b: uint32* = uint32*(self.shm + uint64(off)) - col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50)) - for k in range(alloc_px): - b[k] = col + # 纯移动:内容尺寸没变,不切缓冲 + if w == int(s.fb_w) and ch == int(s.fb_h): + s.ux = x + s.uy = y + s.w = w + s.h = h + return 1 + target_off: uint32 = s.fb_off + target_cap: uint32 = s.fb_cap + # 当前绘制目标若正在显示(上次已 FLUSH),或容量不足,就换到备用/新缓冲。 + # 这样 resize 期间合成器仍读旧 vis 缓冲,绝不会按新步长读旧内容。 + if target_off == self.vis_off[win] or need > int(target_cap): + if self.spare_off[win] != uint32(0) and need <= int(self.spare_cap[win]): + target_off = self.spare_off[win] + target_cap = self.spare_cap[win] + self.spare_off[win] = uint32(0) + self.spare_cap[win] = uint32(0) + else: + alloc_px: int = need + grow: int = int(target_cap) + int(target_cap) / 2 + if grow > alloc_px: alloc_px = grow + off: uint32 = desk.desk_arena_alloc(self.shm, uint32(alloc_px * 4)) + if off == uint32(0): return 0 + # 旧 target 还没显示过,可留作下一次 resize 的备用 + if s.fb_off != self.vis_off[win] and s.fb_off != uint32(0): + self.spare_off[win] = s.fb_off + self.spare_cap[win] = s.fb_cap + target_off = off + target_cap = uint32(alloc_px) + s.fb_off = target_off + s.fb_cap = target_cap s.ux = x s.uy = y s.w = w @@ -577,7 +723,7 @@ class DeskServer: if ny + nh > self.fb_h: nh = self.fb_h - ny if nx == s.ux and ny == s.uy and nw == s.w and nh == s.h: return self.mark_win(s.uy, s.h) - if self._set_window_rect(s, nx, ny, nw, nh) != 0: + if self._set_window_rect(s, self.resize_win, nx, ny, nw, nh) != 0: self.push_event(self.resize_win, uint32(desk.DESK_EVT_RESIZE), int(s.fb_w), int(s.fb_h), 0, 0) self.mark_win(s.uy, s.h) @@ -635,7 +781,7 @@ class DeskServer: mw: int = self.fb_w mh: int = self.fb_h - TASKBAR_H if mh < TITLE_H + TITLE_GAP + 1: return - if self._set_window_rect(s, 0, 0, mw, mh) == 0: return + if self._set_window_rect(s, win, 0, 0, mw, mh) == 0: return s.restore_x = ox s.restore_y = oy s.restore_w = ow @@ -652,7 +798,7 @@ class DeskServer: if s.used == 0 or s.maximized == 0: return 0 oy: int = s.uy oh: int = s.h - if self._set_window_rect(s, s.restore_x, s.restore_y, s.restore_w, s.restore_h) == 0: + if self._set_window_rect(s, win, s.restore_x, s.restore_y, s.restore_w, s.restore_h) == 0: return 0 s.maximized = 0 self.mark_win(oy, oh) @@ -727,6 +873,12 @@ class DeskServer: s.fb_cap = uint32(cap_px) s.fb_w = uint32(w) s.fb_h = uint32(content_h) + self.vis_off[i] = buf_off + self.vis_cap[i] = uint32(cap_px) + self.vis_w[i] = w + self.vis_h[i] = content_h + self.spare_off[i] = uint32(0) + self.spare_cap[i] = uint32(0) string.memset(&s.title, 0, desk.DESK_TITLE_MAX) # 像素缓冲先清成窗口底色,避免 app 还没画就露出垃圾 buf: uint32* = uint32*(self.shm + uint64(buf_off)) @@ -986,8 +1138,13 @@ class DeskServer: content_row: int = yy - content_y0 if row_is_title != 0 and title_alpha < 255: self._blend_row(wx + 1, yy, ww - 2, row_col, title_alpha) - elif rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh: - self.row_blit(wx + 1, yy, buf, content_row * fw + 1, ww - 2) + elif rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh and buf is not None: + blit_w: int = ww - 2 + if blit_w > fw - 2: blit_w = fw - 2 + if blit_w > 0: + self.row_blit(wx + 1, yy, buf, content_row * fw + 1, blit_w) + if blit_w < ww - 2: + self.row_fill(wx + 1 + blit_w, yy, (ww - 2) - blit_w, row_col) else: self.row_fill(wx, yy, ww, row_col) if rel == 0 or rel == wh - 1: @@ -1005,7 +1162,7 @@ class DeskServer: wh: int = s.h content_row: int = yy - content_y0 do_blit: int = 0 - if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh: + if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh and buf is not None: do_blit = 1 cyc: int = r if rel >= r: cyc = wh - r - 1 @@ -1026,7 +1183,7 @@ class DeskServer: if _is_in_corner(rx, rel, cxc, cyc, r) != 0: on_border: int = _is_on_border(rx, rel, ww, wh, r) idx: int = yy * self.fb_w + px - if on_border == 0 and do_blit != 0: + if on_border == 0 and do_blit != 0 and rx < fw: self.back[idx] = buf[content_row * fw + rx] px += 1 continue @@ -1052,7 +1209,12 @@ class DeskServer: if rel == 0 or rel == wh - 1: self.row_fill(mid0, yy, midw, outline_col) elif do_blit != 0: - self.row_blit(mid0, yy, buf, content_row * fw + r, midw) + mid_blit_w: int = midw + if mid_blit_w > fw - r: mid_blit_w = fw - r + if mid_blit_w > 0: + self.row_blit(mid0, yy, buf, content_row * fw + r, mid_blit_w) + if mid_blit_w < midw: + self.row_fill(mid0 + mid_blit_w, yy, midw - mid_blit_w, row_col) elif row_is_title != 0 and title_alpha < 255: self._blend_row(mid0, yy, midw, row_col, title_alpha) else: @@ -1069,7 +1231,7 @@ class DeskServer: if _is_in_corner(rx, rel, cxr, cyc, r) != 0: on_border: int = _is_on_border(rx, rel, ww, wh, r) idx: int = yy * self.fb_w + px - if on_border == 0 and do_blit != 0: + if on_border == 0 and do_blit != 0 and rx < fw: self.back[idx] = buf[content_row * fw + rx] px += 1 continue @@ -1446,7 +1608,7 @@ class DeskServer: w: int = order[i] if yy < sy[w] or yy >= sy[w] + sh[w]: continue if sx[w] > xx: - self.row_blit(xx, yy, self.bg_row, xx, sx[w] - xx) + self.row_blit(xx, yy, self.bg_src(yy), xx, sx[w] - xx) ex: int = sx[w] + sw[w] if ex > xx: xx = ex if smode[w] != 0: @@ -1457,9 +1619,9 @@ class DeskServer: elif rel < WIN_RADIUS or rel >= sh[w] - WIN_RADIUS: need = 1 if need != 0: - self.row_blit(sx[w], yy, self.bg_row, sx[w], sw[w]) + self.row_blit(sx[w], yy, self.bg_src(yy), sx[w], sw[w]) if xx < self.fb_w: - self.row_blit(xx, yy, self.bg_row, xx, self.fb_w - xx) + self.row_blit(xx, yy, self.bg_src(yy), xx, self.fb_w - xx) yy += 1 # 2) 窗口 for i in range(cnt): @@ -1546,13 +1708,22 @@ class DeskServer: self._draw_shadow(s, y0, y1, shadow_r) if wy + wh <= y0 or wy >= y1: return - buf: uint32* = uint32*(self.shm + uint64(s.fb_off)) - fw: int = int(s.fb_w) - fh: int = int(s.fb_h) - # 槽表被写坏时宁可跳过该窗口,也不要按越界 fb_off 读像素把整个合成器带崩。 - need_bytes: uint64 = uint64(fw) * uint64(fh) * uint64(4) - if s.fb_off < uint32(desk.DESK_OFF_ARENA) or uint64(s.fb_off) + need_bytes > uint64(desk.DESK_SHM_SIZE): - return + # 合成器只读“最后一次 FLUSH 的可见缓冲”。resize 期间 s.fb_* 已经是新尺寸, + # 但应用还没画完,绝不能拿新步长去读旧缓冲(否则就是截图里的乱码)。 + v_off: uint32 = self.vis_off[win] + fw: int = self.vis_w[win] + fh: int = self.vis_h[win] + buf: uint32* = None + if v_off >= uint32(desk.DESK_OFF_ARENA) and fw > 0 and fh > 0: + need_bytes: uint64 = uint64(fw) * uint64(fh) * uint64(4) + if uint64(v_off) + need_bytes <= uint64(desk.DESK_SHM_SIZE): + buf = uint32*(self.shm + uint64(v_off)) + else: + fw = 0 + fh = 0 + else: + fw = 0 + fh = 0 content_y0: int = wy + th + TITLE_GAP r: int = WIN_RADIUS yy: int = y0 @@ -1667,10 +1838,13 @@ class DeskServer: def render(self): if self.dirty_any == 0: return - devfs.fb_wait_vblank(self.fb_fd) - # 逐脏段渲染并立刻提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、 - # 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交(4MB memcpy),帧率直接崩。 - # 段内先全画进 back 再逐行提交,保证每一行提交出去的都是完整一帧的内容。 + if SCREEN_SYNC != 0: + # 可选:等垂直回扫,减少撕裂。内核侧是有界轮询(2048 次), + # 不会像旧版 2×100000 次端口读那样拖慢一帧。 + devfs.fb_wait_vblank(self.fb_fd) + # 逐脏段渲染并提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、 + # 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交,帧率直接崩。 + # 段内先全画进 back 再整段提交,保证提交出去的每一行都是完整一帧的内容。 y: int = 0 while y < self.fb_h: if self.dirty[y] == 0: @@ -1681,14 +1855,19 @@ class DeskServer: y += 1 y1: int = y self.render_range(y0, y1) - yy: int = y0 - while yy < y1: - if self.dirty[yy] != 0: - # fb 行距 = pitch_px,back 行距 = fb_w - string.memcpy(rawptr(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4)), - rawptr(uint64(self.back) + uint64(yy * self.fb_w) * uint64(4)), - uint64(self.fb_w) * uint64(4)) - yy += 1 + # back 与 fb 行距一致时整段一次 memcpy:大范围拖动时比逐行 1080 次 + # rep movsb 少很多固定开销;行距不同才退回逐行。 + if self.pitch_px == self.fb_w: + _fb_copy(uint64(self.fb) + uint64(y0 * self.pitch_px) * uint64(4), + uint64(self.back) + uint64(y0 * self.fb_w) * uint64(4), + uint64((y1 - y0) * self.fb_w) * uint64(4)) + else: + yy: int = y0 + while yy < y1: + _fb_copy(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4), + uint64(self.back) + uint64(yy * self.fb_w) * uint64(4), + uint64(self.fb_w) * uint64(4)) + yy += 1 self.clear_dirty() # ---------------- 事件投递 ---------------- @@ -1806,7 +1985,7 @@ class DeskServer: self.mark_win(s.uy, s.h) elif ty == uint32(desk.DESK_REQ_RESIZE): self.mark_win(s.uy, s.h) - if self._set_window_rect(s, s.ux, s.uy, m.a, m.b) != 0: + if self._set_window_rect(s, win, s.ux, s.uy, m.a, m.b) != 0: s.maximized = 0 self.mark_win(s.uy, s.h) elif ty == uint32(desk.DESK_REQ_TITLE): @@ -1819,6 +1998,16 @@ class DeskServer: if self.hit_test(self.cx, self.cy) == win: self._mark_cursor_band() elif ty == uint32(desk.DESK_REQ_FLUSH): + # 应用已经按 s.fb_w/fb_h 画完一帧:此刻才把"正在显示的内容"切到 + # 新缓冲。旧的 vis 缓冲留作下一次 resize 的备用。 + old_off: uint32 = self.vis_off[win] + if old_off != s.fb_off: + self.spare_off[win] = old_off + self.spare_cap[win] = self.vis_cap[win] + self.vis_off[win] = s.fb_off + self.vis_cap[win] = s.fb_cap + self.vis_w[win] = int(s.fb_w) + self.vis_h[win] = int(s.fb_h) # 最小化窗口仍在后台绘制/FLUSH:不要置脏,否则合成器每帧都要重画背景。 if s.minimized == 0: self.mark(s.uy, s.h) diff --git a/Apps/ImageSee/linker.ld b/Apps/ImageSee/linker.ld new file mode 100644 index 0000000..0d5a4d5 --- /dev/null +++ b/Apps/ImageSee/linker.ld @@ -0,0 +1,39 @@ +ENTRY(__entry) + +SECTIONS { + . = 0x400000; + + .text : { + __entry = .; + *(.text.startup) + *(.text) + *(.text.*) + } + + .rodata : { + *(.rodata) + *(.rodata.*) + } + + .data : { + *(.data) + *(.data.*) + } + + .bss : { + __bss_start = .; + *(.bss) + *(.bss.*) + __bss_end = .; + } + + /DISCARD/ : { + *(.comment) + *(.note) + *(.eh_frame) + *(.eh_frame_hdr) + *(.reloc) + *(.rela) + *(.debug*) + } +} diff --git a/Apps/ImageSee/main.vp b/Apps/ImageSee/main.vp new file mode 100644 index 0000000..cfc5b43 --- /dev/null +++ b/Apps/ImageSee/main.vp @@ -0,0 +1,146 @@ +# ImageSee demo: decode PNG and JPEG with vpsdk.imagesee and show them. + +import vpsdk.vpui as vpui +import vpsdk.window as window +import vpsdk.process as process +import vpsdk.stdlib as stdlib +import vpsdk.imagesee.imagesee as imagesee +import vpsdk.imagesee.image as im +import vpsdk.stdio as stdio +import string +from stdint import * + +define WIN_X = 40 +define WIN_Y = 36 +define WIN_W = 1120 +define WIN_H = 616 +define CANVAS_W = 536 +define CANVAS_H = 520 +define BG = 0xFF14141E +define PANEL = 0xFF20202C +define BORDER = 0xFF3A3A50 + +# Fill dst[0..w*h) with a dark checkerboard so transparent parts are visible. +def _checker(dst: uint32*, w: int, h: int): + y: int = 0 + while y < h: + x: int = 0 + while x < w: + c: uint32 = uint32(PANEL) + if ((x >> 4) + (y >> 4)) & 1: + c = uint32(BG) + dst[y * w + x] = c + x = x + 1 + y = y + 1 + +def _draw_fit(dst: uint32*, dw: int, dh: int, img: im.Image*, + ox: int, oy: int, bw: int, bh: int): + if img is None: return + tw: int = bw + th: int = (tw * img.height) / img.width + if th > bh: + th = bh + tw = (th * img.width) / img.height + if tw < 1 or th < 1: return + x0: int = ox + (bw - tw) / 2 + y0: int = oy + (bh - th) / 2 + y: int = 0 + while y < th: + sy: int = (y * img.height) / th + if sy >= img.height: sy = img.height - 1 + x: int = 0 + while x < tw: + sx: int = (x * img.width) / tw + if sx >= img.width: sx = img.width - 1 + ty: int = y0 + y + tx: int = x0 + x + if tx >= 0 and ty >= 0 and tx < dw and ty < dh: + dst[ty * dw + tx] = img.pixels[sy * img.width + sx] + x = x + 1 + y = y + 1 + +def _border(dst: uint32*, dw: int, dh: int, x: int, y: int, w: int, h: int, c: uint32): + i: int = 0 + while i < w: + if x + i >= 0 and x + i < dw: + if y >= 0 and y < dh: dst[y * dw + x + i] = c + if y + h - 1 >= 0 and y + h - 1 < dh: dst[(y + h - 1) * dw + x + i] = c + i = i + 1 + i = 0 + while i < h: + if y + i >= 0 and y + i < dh: + if x >= 0 and x < dw: dst[(y + i) * dw + x] = c + if x + w - 1 >= 0 and x + w - 1 < dw: dst[(y + i) * dw + x + w - 1] = c + i = i + 1 + +def _report(path: const str) -> im.Image*: + img: im.Image* = imagesee.load(path) + if img is None: + stdio.printf("[imagesee] %s: FAILED stage=%d\n", path, imagesee.last_error()) + return None + stdio.printf("[imagesee] %s: %s %dx%d sum=%x\n", + path, imagesee.format_name(img), img.width, img.height, + imagesee.checksum(img)) + return img + +def _panel_failed(dst: uint32*, w: int, h: int): + y: int = 0 + while y < h: + x: int = 0 + while x < w: + c: uint32 = uint32(0xFF300018) + if ((x >> 5) + (y >> 5)) & 1: + c = uint32(0xFF001830) + dst[y * w + x] = c + x = x + 1 + y = y + 1 + +@__attribute__(section(".text.startup"), aligned(16)) +export def _start() -> int: + stdio.printf("[imagesee] app start\n") + + png_img: im.Image* = _report("/IMAGES/SAMPLE.PNG") + jpg_img: im.Image* = _report("/IMAGES/PHOTO.JPG") + _report("/IMAGES/GRAY.JPG") + _report("/IMAGES/PAL.PNG") + _report("/IMAGES/INTER.PNG") + + root: vpui.Tk = vpui.Tk() + root.title("ImageSee - PNG | JPEG") + root.set_style(window.STYLE_WIN11) + root.geometry(WIN_X, WIN_Y, WIN_W, WIN_H) + root.bg_color = uint32(BG) + master: vpui.Tk* = &root + + title: vpui.Label = vpui.Label(master, "ImageSee left: PNG right: JPEG", uint32(0xFFE0E0F0)) + root.pack(0, vpui.PACK_TOP, vpui.FILL_X, 0, 12, 6) + + fb1: uint32* = uint32*(stdlib.malloc(uint64(CANVAS_W * CANVAS_H * 4))) + fb2: uint32* = uint32*(stdlib.malloc(uint64(CANVAS_W * CANVAS_H * 4))) + if fb1 is None or fb2 is None: + process.exit(1) + return 0 + _checker(fb1, CANVAS_W, CANVAS_H) + _checker(fb2, CANVAS_W, CANVAS_H) + if png_img is None: + _panel_failed(fb1, CANVAS_W, CANVAS_H) + else: + _draw_fit(fb1, CANVAS_W, CANVAS_H, png_img, 8, 8, CANVAS_W - 16, CANVAS_H - 16) + if jpg_img is None: + _panel_failed(fb2, CANVAS_W, CANVAS_H) + else: + _draw_fit(fb2, CANVAS_W, CANVAS_H, jpg_img, 8, 8, CANVAS_W - 16, CANVAS_H - 16) + _border(fb1, CANVAS_W, CANVAS_H, 0, 0, CANVAS_W, CANVAS_H, uint32(BORDER)) + _border(fb2, CANVAS_W, CANVAS_H, 0, 0, CANVAS_W, CANVAS_H, uint32(BORDER)) + + c1: vpui.FBCanvas = vpui.FBCanvas(master, CANVAS_W, CANVAS_H) + c1.set_fb(fb1, CANVAS_W, CANVAS_H) + root.pack(1, vpui.PACK_LEFT, 0, 1, 10, 8) + c2: vpui.FBCanvas = vpui.FBCanvas(master, CANVAS_W, CANVAS_H) + c2.set_fb(fb2, CANVAS_W, CANVAS_H) + root.pack(2, vpui.PACK_LEFT, 0, 1, 10, 8) + + stdio.printf("[imagesee] window ready\n") + root.mainloop() + process.exit(0) + return 0 diff --git a/Apps/ImageSee/project.vpj b/Apps/ImageSee/project.vpj new file mode 100644 index 0000000..28d1a27 --- /dev/null +++ b/Apps/ImageSee/project.vpj @@ -0,0 +1,31 @@ +{ + "name": "ImageSee", + "version": "1.0.0", + "source_dir": "./", + "build_dir": "./__vpcbuild__", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"] + }, + "linker": { + "cmd": "ld.lld.exe", + "flags": [ + "-m", "elf_x86_64", + "-T", "linker.ld", + "--oformat", "elf64-x86-64", + "--strip-all" + ], + "output": "imagesee.elf" + }, + "includes": ["../..", "../../../../includes"], + "target": { + "triple": "x86_64-none-elf", + "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "target": "llvm", + "strict_mode": true + } +} + diff --git a/Assets/background.jpg b/Assets/background.jpg new file mode 100644 index 0000000..d21f4a1 Binary files /dev/null and b/Assets/background.jpg differ diff --git a/Assets/images/gray.jpg b/Assets/images/gray.jpg new file mode 100644 index 0000000..22d6354 Binary files /dev/null and b/Assets/images/gray.jpg differ diff --git a/Assets/images/inter.png b/Assets/images/inter.png new file mode 100644 index 0000000..3556f7a Binary files /dev/null and b/Assets/images/inter.png differ diff --git a/Assets/images/pal.png b/Assets/images/pal.png new file mode 100644 index 0000000..860c11b Binary files /dev/null and b/Assets/images/pal.png differ diff --git a/Assets/images/photo.jpg b/Assets/images/photo.jpg new file mode 100644 index 0000000..d42ba69 Binary files /dev/null and b/Assets/images/photo.jpg differ diff --git a/Assets/images/sample.png b/Assets/images/sample.png new file mode 100644 index 0000000..726e125 Binary files /dev/null and b/Assets/images/sample.png differ diff --git a/README.md b/README.md index ab15cd5..f902d40 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,11 @@ A bare-metal 64-bit operating system written in the [Viper](https://git.gvsds.co - **VKernel** —— 64 位微内核(x86_64-none-elf 目标) - **UEFI 引导器** —— `boot/boot.c`(GNU-EFI 编译为 `disk/EFI/BOOT/bootx64.efi`) - **FAT32 驱动** —— 自研实现,支持挂载、目录、文件操作 -- **4 个用户应用** —— HelloWorld / Terminal / Gargantua / Scene3D +- **7 个用户应用** —— HelloWorld / Terminal / Gargantua / Scene3D / Explorer / FontDemo / ImageSee - **1 个动态库** —— SerialLogger -- **ViperPOS SDK** —— 用户态开发库(系统调用、窗口、文件系统、进程) +- **ViperPOS SDK** —— 用户态开发库(系统调用、窗口、文件系统、进程、图像解码) +- **syscall ABI 计划** —— 与 Linux x86-64 对齐的路线见 [`docs/syscall.md`](docs/syscall.md) +- **ImageSee** —— PNG/JPEG 解码库与示例应用见 [`docs/imagesee.md`](docs/imagesee.md) ### 目录结构 @@ -185,9 +187,11 @@ Main components: - **VKernel** — 64-bit microkernel (target `x86_64-none-elf`) - **UEFI bootloader** — `boot/boot.c` (compiled by GNU-EFI to `disk/EFI/BOOT/bootx64.efi`) - **FAT32 driver** — self-implemented, supports mount/dir/file operations -- **4 user applications** — HelloWorld / Terminal / Gargantua / Scene3D +- **7 user applications** — HelloWorld / Terminal / Gargantua / Scene3D / Explorer / FontDemo / ImageSee - **1 dynamic library** — SerialLogger -- **ViperPOS SDK** — user-space development kit (syscall, window, fs, process) +- **ViperPOS SDK** — user-space development kit (syscall, window, fs, process, image decoding) +- **Syscall ABI plan** — Linux x86-64 alignment roadmap in [`docs/syscall.md`](docs/syscall.md) +- **ImageSee** — PNG/JPEG decoder library and demo app, see [`docs/imagesee.md`](docs/imagesee.md) ### Directory layout diff --git a/Scripts/disk.ps1 b/Scripts/disk.ps1 index 10971fa..6116a3f 100644 --- a/Scripts/disk.ps1 +++ b/Scripts/disk.ps1 @@ -88,3 +88,82 @@ Write-Host "Unmounting disk image..." imdisk -D -m $MOUNT_LETTER Write-Host "Disk image created: $DISK_IMG" -ForegroundColor Green + +# ========================================================== +# Refresh the VMware disk next to the .vmx +# ========================================================== +# Vmware/ViperOS.vmdk is a monolithicFlat descriptor; the data lives in +# ViperOS-flat.vmdk. Both are regenerated from disk.img so "Open VM" always +# sees the image that was just packed. The .vmx already points at +# ide0:0.fileName = "ViperOS.vmdk"; it uses EFI firmware for bootx64.efi. +$VMWARE_DIR = Join-Path $PROJECT_DIR "Vmware" +$VMWARE_VMDK = Join-Path $VMWARE_DIR "ViperOS.vmdk" +$VMWARE_FLAT = Join-Path $VMWARE_DIR "ViperOS-flat.vmdk" +$VMWARE_VMX = Join-Path $VMWARE_DIR "Viper OS.vmx" + +if (Test-Path $VMWARE_DIR) { + Write-Host "=== Updating VMware disk ===" -ForegroundColor Cyan + $vmRunning = Get-Process -Name "vmware-vmx" -ErrorAction SilentlyContinue + if ($vmRunning) { + Write-Host "Warning: a VMware VM is running; close it and re-run to refresh the VMDK." -ForegroundColor Yellow + } else { + # A leftover .lck from a crash keeps VMware from opening the VM. + $vmLock = "$VMWARE_VMX.lck" + if (Test-Path $vmLock) { + Write-Host "Removing stale VMware lock: $vmLock" -ForegroundColor Yellow + Remove-Item -Recurse -Force $vmLock -ErrorAction SilentlyContinue + } + $size = (Get-Item $DISK_IMG).Length + $sectors = [int64]($size / 512) + $cyl = [int64][math]::Floor($sectors / (16 * 63)) + + # Keep the existing CID/UUID so VMware treats it as the same disk. + $cid = "fffffffe" + $uuid = "60 00 C2 96 88 c0 09 6d-c8 30 f7 71 57 ce 47 0d" + $longid = "6af48e7def7fae6bfa57a4affffffffe" + if (Test-Path $VMWARE_VMDK) { + foreach ($l in (Get-Content $VMWARE_VMDK -ErrorAction SilentlyContinue)) { + if ($l -match '^CID=([0-9a-fA-F]+)') { $cid = $Matches[1] } + if ($l -match '^ddb\.uuid\s*=\s*"(.+)"') { $uuid = $Matches[1] } + if ($l -match '^ddb\.longContentID\s*=\s*"(.+)"') { $longid = $Matches[1] } + } + } + + $descriptor = @" +# Disk DescriptorFile +version=1 +encoding="GBK" +CID=$cid +parentCID=ffffffff +createType="monolithicFlat" + +# Extent description +RW $sectors FLAT "ViperOS-flat.vmdk" 0 + +# The Disk Data Base +#DDB + +ddb.adapterType = "ide" +ddb.geometry.cylinders = "$cyl" +ddb.geometry.heads = "16" +ddb.geometry.sectors = "63" +ddb.longContentID = "$longid" +ddb.uuid = "$uuid" +ddb.virtualHWVersion = "21" +"@ + + try { + if (Test-Path $VMWARE_FLAT) { Remove-Item -Force $VMWARE_FLAT } + Copy-Item -Force $DISK_IMG $VMWARE_FLAT + Set-Content -Path $VMWARE_VMDK -Value $descriptor -Encoding ASCII + if (-not (Select-String -Path $VMWARE_VMX -Pattern 'ide0:0\.fileName\s*=\s*"ViperOS\.vmdk"' -Quiet)) { + Write-Host "Note: $VMWARE_VMX does not reference ViperOS.vmdk; check ide0:0.fileName." -ForegroundColor Yellow + } + Write-Host "VMDK updated: $VMWARE_VMDK ($sectors sectors, $([math]::Round($size / 1MB)) MiB)" -ForegroundColor Green + } catch { + Write-Host "Warning: failed to refresh the VMware disk: $($_.Exception.Message)" -ForegroundColor Yellow + } + } +} else { + Write-Host "Note: Vmware directory not found; skipping VMDK update." -ForegroundColor DarkGray +} diff --git a/Scripts/gen_imagesee_assets.py b/Scripts/gen_imagesee_assets.py new file mode 100644 index 0000000..cfcd021 --- /dev/null +++ b/Scripts/gen_imagesee_assets.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""Generate the sample images shipped with the ImageSee demo. + +Run from the ViperOS root: + python Scripts/gen_imagesee_assets.py + +The generated files live in Assets/images/ and are copied to /IMAGES on the +ViperOS disk by build.py. Requires Pillow (only for generating the assets; +the OS itself does not need it). +""" + +import os +import struct +import zlib +from PIL import Image, ImageDraw, ImageFilter + + +W, H = 320, 220 +OUT = os.path.join("Assets", "images") +BG_OUT = os.path.join("Assets", "background.jpg") + + +def test_pattern(): + img = Image.new("RGBA", (W, H), (0, 0, 0, 255)) + px = img.load() + for y in range(H): + for x in range(W): + r = (x * 255) // (W - 1) + g = (y * 255) // (H - 1) + b = 255 - ((x + y) * 255) // (W + H - 2) + px[x, y] = (r, g, b, 255) + d = ImageDraw.Draw(img) + d.rectangle([12, 12, 92, 72], fill=(255, 255, 255, 255)) + d.rectangle([22, 22, 82, 62], fill=(0, 0, 0, 255)) + d.rectangle([108, 12, 188, 72], fill=(230, 40, 40, 255)) + d.rectangle([198, 12, 278, 72], fill=(40, 200, 80, 255)) + d.rectangle([288, 12, 308, 72], fill=(40, 80, 230, 255)) + d.ellipse([40, 100, 160, 210], fill=(255, 220, 40, 255)) + d.ellipse([150, 90, 250, 190], outline=(255, 255, 255, 255), width=5) + d.line([0, H - 1, W - 1, 80], fill=(255, 0, 255, 255), width=3) + d.line([0, 80, W - 1, H - 1], fill=(0, 255, 255, 255), width=3) + for i in range(0, 40): + d.rectangle([280 - i, 160 + i, 286 - i, 166 + i], fill=(i * 6, i * 6, i * 6, 255)) + return img + + +def png_chunk(typ: bytes, data: bytes) -> bytes: + c = struct.pack(">I", len(data)) + typ + data + c += struct.pack(">I", zlib.crc32(typ + data) & 0xFFFFFFFF) + return c + + +def write_interlaced_png(path, img): + """Adam7 interlaced RGBA PNG (filter 0), to exercise the pass decoder.""" + w, h = img.size + px = img.load() + passes = [(0, 0, 8, 8), (4, 0, 8, 8), (0, 4, 4, 8), (2, 0, 4, 4), + (0, 2, 2, 4), (1, 0, 2, 2), (0, 1, 1, 2)] + raw = bytearray() + for x0, y0, dx, dy in passes: + pw = (w - x0 + dx - 1) // dx if w > x0 else 0 + ph = (h - y0 + dy - 1) // dy if h > y0 else 0 + if pw <= 0 or ph <= 0: + continue + for ry in range(ph): + raw.append(0) # filter: None + y = y0 + ry * dy + for rx in range(pw): + x = x0 + rx * dx + r, g, b, a = px[x, y] + raw += bytes((r, g, b, a)) + ihdr = struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 1) + out = b"\x89PNG\r\n\x1a\n" + png_chunk(b"IHDR", ihdr) + out += png_chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + out += png_chunk(b"IEND", b"") + with open(path, "wb") as f: + f.write(out) + + +def make_background(): + """960x540 desktop wallpaper shipped as /sys/images/screen/background.jpg.""" + w, h = 960, 540 + base = Image.new("RGB", (w, h)) + px = base.load() + for y in range(h): + t = y / (h - 1) + r = int(12 + 34 * t) + g = int(16 + 20 * t) + b = int(34 + 44 * t) + for x in range(w): + # subtle diagonal tint + u = (x / (w - 1) + t) * 0.5 + px[x, y] = (min(255, r + int(10 * u)), + min(255, g + int(6 * u)), + min(255, b + int(18 * u))) + overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0)) + d = ImageDraw.Draw(overlay) + blobs = [ + (140, 420, 210, (30, 120, 190, 120)), + (330, 140, 170, (150, 40, 180, 110)), + (700, 360, 260, (20, 90, 160, 120)), + (820, 110, 150, (220, 60, 130, 90)), + (520, 470, 190, (40, 170, 170, 90)), + ] + for cx, cy, rr, col in blobs: + d.ellipse([cx - rr, cy - rr, cx + rr, cy + rr], fill=col) + overlay = overlay.filter(ImageFilter.GaussianBlur(30)) + base = Image.alpha_composite(base.convert("RGBA"), overlay).convert("RGB") + # vignette keeps the corners dark so white window chrome stays readable + mask = Image.new("L", (w, h)) + mp = mask.load() + for y in range(h): + for x in range(w): + dx = (x - w / 2) / (w / 2) + dy = (y - h / 2) / (h / 2) + d2 = dx * dx + dy * dy + v = 255 - int(min(150, d2 * 110)) + mp[x, y] = max(0, v) + base = Image.composite(base, Image.new("RGB", (w, h), (0, 0, 0)), mask) + base.save(BG_OUT, "JPEG", quality=88, subsampling=2, optimize=False) + print("[assets] wrote", BG_OUT) + + +def main(): + os.makedirs(OUT, exist_ok=True) + img = test_pattern() + + # Opaque RGBA PNG (exercises color type 6, non-interlaced). + img.save(os.path.join(OUT, "sample.png"), "PNG") + + # Palette PNG with one transparent color (color type 3 + tRNS). + pal = img.convert("RGB").quantize(colors=64, method=Image.MEDIANCUT) + pal.info["transparency"] = 0 + pal.save(os.path.join(OUT, "pal.png"), "PNG", transparency=0) + + # Baseline 4:2:0 JPEG. + img.convert("RGB").save(os.path.join(OUT, "photo.jpg"), "JPEG", + quality=90, subsampling=2, optimize=False) + + # Grayscale JPEG (single component path). + img.convert("L").save(os.path.join(OUT, "gray.jpg"), "JPEG", quality=90) + + # Interlaced RGBA PNG with a vertical alpha ramp (Adam7 + alpha). + inter = img.copy() + ip = inter.load() + for y in range(H): + for x in range(W): + r, g, b, a = ip[x, y] + ip[x, y] = (r, g, b, (x * 255) // (W - 1)) + write_interlaced_png(os.path.join(OUT, "inter.png"), inter) + + make_background() + + # Reference samples for the app's serial output. + print("[assets] reference samples (PNG/JPEG decode with PIL):") + for name in ("sample.png", "photo.jpg", "gray.jpg", + "pal.png", "inter.png"): + path = os.path.join(OUT, name) + ref = Image.open(path).convert("RGBA") + pts = [(ref.width // 4, ref.height // 3), + (ref.width // 2, ref.height // 2), + ((ref.width * 3) // 4, (ref.height * 2) // 3)] + vals = [ref.getpixel(p) for p in pts] + print(f"[assets] {name:12s} {ref.width}x{ref.height} " + + " ".join(f"{p[0]},{p[1]},{p[2]}" for p in vals)) + print("[assets] wrote", OUT) + + +if __name__ == "__main__": + main() diff --git a/Scripts/syscall_table.py b/Scripts/syscall_table.py new file mode 100644 index 0000000..1ce80f1 --- /dev/null +++ b/Scripts/syscall_table.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +"""Canonical ViperOS syscall table. + +The kernel and vpsdk keep separate copies (vpsdk must not depend on VKernel), +so both files are generated from this list. Never edit the generated block +by hand: + + # BEGIN GENERATED SYSCALL TABLE + ... + # END GENERATED SYSCALL TABLE + +Usage: + python Scripts/syscall_table.py --write # regenerate both files + python Scripts/syscall_table.py --check # fail if out of sync +""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + +TARGETS = [ + ROOT / "VKernel" / "Kernel" / "intr" / "syscall.vp", + ROOT / "vpsdk" / "syscall.vp", +] + +BEGIN = "# BEGIN GENERATED SYSCALL TABLE" +END = "# END GENERATED SYSCALL TABLE" + +# (kind, payload...) kind: "section" | "entry" +# Entry numbers follow Linux x86-64; Linux calls we do not implement yet are +# simply absent (the dispatcher returns -ENOSYS for unknown numbers). +TABLE = [ + ("section", "Linux 对齐段(号 = Linux x86-64 的号)"), + ("entry", "READ", 0, "read(fd, buf, count)"), + ("entry", "WRITE", 1, "write(fd, buf, count)"), + ("entry", "OPEN", 2, "open(path, flags) - Linux O_*"), + ("entry", "CLOSE", 3, "close(fd)"), + ("entry", "STAT", 4, "stat(path, fileinfo*) - legacy struct"), + ("entry", "FSTAT", 5, "fstat(fd, struct stat*)"), + ("entry", "POLL", 7, "poll(fds, nfds, timeout) - stub 0"), + ("entry", "SEEK", 8, "lseek(fd, offset, whence) -> new offset"), + ("entry", "MMAP", 9, "mmap(addr, len, prot, flags, fd, off)"), + ("entry", "MPROTECT", 10, "mprotect(addr, len, prot)"), + ("entry", "MUNMAP", 11, "munmap(addr, len)"), + ("entry", "BRK", 12, "brk(addr) -> new break"), + ("entry", "RT_SIGACTION", 13, "rt_sigaction() - bookkeeping"), + ("entry", "RT_SIGPROCMASK", 14, "rt_sigprocmask() - bookkeeping"), + ("entry", "RT_SIGRETURN", 15, "rt_sigreturn()"), + ("entry", "IOCTL", 16, "ioctl(fd, request, arg)"), + ("entry", "PREAD64", 17, "pread64(fd, buf, count, offset)"), + ("entry", "PWRITE64", 18, "pwrite64(fd, buf, count, offset)"), + ("entry", "READV", 19, "readv(fd, iov, iovcnt)"), + ("entry", "WRITEV", 20, "writev(fd, iov, iovcnt)"), + ("entry", "ACCESS", 21, "access(path, mode)"), + ("entry", "PIPE", 22, "pipe(int[2])"), + ("entry", "YIELD", 24, "sched_yield()"), + ("entry", "MREMAP", 25, "mremap(old, oldsz, newsz, flags, newaddr)"), + ("entry", "MADVISE", 28, "madvise(addr, len, advice)"), + ("entry", "DUP", 32, "dup(fd)"), + ("entry", "DUP2", 33, "dup2(oldfd, newfd)"), + ("entry", "NANOSLEEP", 35, "nanosleep(req, rem)"), + ("entry", "GETPID", 39, "getpid()"), + ("entry", "CLONE", 56, "clone(flags, stack, ptid, ctid, tls)"), + ("entry", "FORK", 57, "fork() - copy address space"), + ("entry", "SPAWN", 59, "execve(path, argv, envp)"), + ("entry", "THREAD_EXIT", 60, "exit(): retire current thread"), + ("entry", "WAIT4", 61, "wait4(pid, status, options, rusage)"), + ("entry", "KILL", 62, "kill(pid, sig) - bookkeeping"), + ("entry", "UNAME", 63, "uname(utsname*)"), + ("entry", "FCNTL", 72, "fcntl(fd, cmd, arg)"), + ("entry", "GETCWD", 79, "getcwd(buf, size)"), + ("entry", "CHDIR", 80, "chdir(path)"), + ("entry", "RENAME", 82, "rename(old, new)"), + ("entry", "MKDIR", 83, "mkdir(path)"), + ("entry", "RMDIR", 84, "rmdir(path)"), + ("entry", "REMOVE", 87, "unlink(path)"), + ("entry", "READLINK", 89, "readlink(path, buf, size) - no symlinks"), + ("entry", "GETUID", 102, "getuid()"), + ("entry", "GETGID", 104, "getgid()"), + ("entry", "GETEUID", 107, "geteuid()"), + ("entry", "GETEGID", 108, "getegid()"), + ("entry", "GETTIMEOFDAY", 96, "gettimeofday(tv, tz)"), + ("entry", "GETPPID", 110, "getppid()"), + ("entry", "ARCH_PRCTL", 158, "arch_prctl(code, addr)"), + ("entry", "REBOOT_LINUX", 169, "reboot(magic1, magic2, cmd, arg)"), + ("entry", "GETTID", 186, "gettid()"), + ("entry", "FUTEX", 202, "futex(uaddr, op, val, timeout)"), + ("entry", "GETDENTS64", 217, "getdents64(fd, buf, count)"), + ("entry", "SET_TID_ADDRESS", 218, "set_tid_address(tidptr)"), + ("entry", "CLOCK_GETTIME", 228, "clock_gettime(clockid, timespec*)"), + ("entry", "CLOCK_GETRES", 229, "clock_getres(clockid, timespec*)"), + ("entry", "EXIT", 231, "exit_group(): terminate all threads"), + ("entry", "OPENAT", 257, "openat(dirfd, path, flags, mode)"), + ("entry", "NEWFSTATAT", 262, "newfstatat(dirfd, path, stat*, flags)"), + ("entry", "UNLINKAT", 263, "unlinkat(dirfd, path, flags)"), + ("entry", "READLINKAT", 267, "readlinkat(dirfd, path, buf, size)"), + ("entry", "FACCESSAT", 269, "faccessat(dirfd, path, mode, flags)"), + ("entry", "SET_ROBUST_LIST", 273, "set_robust_list(head, len)"), + ("entry", "DUP3", 292, "dup3(oldfd, newfd, flags)"), + ("entry", "PIPE2", 293, "pipe2(int[2], flags)"), + ("entry", "PRLIMIT64", 302, "prlimit64(pid, resource, new, old)"), + ("entry", "GETRANDOM", 318, "getrandom(buf, len, flags)"), + ("entry", "STATX", 332, "statx(...) - -ENOSYS, glibc falls back"), + ("entry", "RSEQ", 334, "rseq(rseq, len, flags) - -ENOSYS"), + ("section", "临时私有段(0x1000+):Linux 无对应,逐条迁出"), + ("entry", "LOAD_SO", 0x1006, "TODO(userspace): dynamic library loading"), + ("entry", "SO_CALL1", 0x1007, "TODO(userspace): dynamic library call"), + ("entry", "REBOOT", 0x1008, "TODO(linux): reboot(169)"), + ("entry", "SHUTDOWN", 0x1009, "TODO(linux): reboot(RB_POWER_OFF)"), + ("entry", "SPAWN_PROC", 0x100A, "TODO(userspace): Terminal run helper"), + ("entry", "THREAD_CREATE_OLD", 0x100B, "TODO(userspace): ViperOS thread_create"), +] + + +def render() -> str: + out = ["enum Syscall:"] + for item in TABLE: + if item[0] == "section": + out.append(f" # ---- {item[1]} ----") + continue + _, name, num, comment = item + value = f"0x{num:04X}" if num >= 0x1000 else f"{num}" + out.append(f" {name:<18}= {value:<7}# {comment}") + return "\n".join(out) + "\n" + + +def split_lines(text: str): + """Split keeping line endings (the tree mixes CRLF and a few LF lines).""" + return text.splitlines(keepends=True) + + +def replace_block(text: str) -> str: + lines = split_lines(text) + b = e = -1 + for i, line in enumerate(lines): + stripped = line.strip() + if stripped == BEGIN: + b = i + elif stripped == END: + e = i + break + if b < 0 or e < 0 or e <= b: + raise SystemExit(f"markers not found: {BEGIN} / {END}") + newline = "\r\n" if lines[b].endswith("\r\n") else "\n" + block = [ln + newline for ln in render().splitlines()] + return "".join(lines[: b + 1] + block + lines[e:]) + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--check", action="store_true") + ap.add_argument("--write", action="store_true") + args = ap.parse_args() + if not args.check and not args.write: + ap.error("pass --check or --write") + + stale = [] + for path in TARGETS: + if not path.exists(): + print(f"[syscall] missing {path}") + return 1 + with open(path, encoding="utf-8", newline="") as fh: + old = fh.read() + new = replace_block(old) + if new != old: + stale.append(path) + if args.write: + with open(path, "w", encoding="utf-8", newline="") as fh: + fh.write(new) + print(f"[syscall] updated {path.relative_to(ROOT)}") + else: + print(f"[syscall] ok {path.relative_to(ROOT)}") + + if args.check and stale: + print("[syscall] out of sync; run: python Scripts/syscall_table.py --write") + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/VKernel/Kernel/drivers/devfs/devfs.vp b/VKernel/Kernel/drivers/devfs/devfs.vp index 2209962..413de1e 100644 --- a/VKernel/Kernel/drivers/devfs/devfs.vp +++ b/VKernel/Kernel/drivers/devfs/devfs.vp @@ -111,6 +111,10 @@ virtual class devfs_file: # mmap 支持:默认不可映射(返回 -1)。可映射设备(/dev/fb0)覆写它, # 把自己的物理区间交给内核 mmap 去映射进用户地址空间。 def mmap_info(self, fd: int, out_phys: uint64*, out_size: uint64*) -> int: return -1 + # 1 = 映射该设备的用户页要按 MMIO 处理(PTE_PCD,禁用缓存)。 + # framebuffer 必须返回 1:VMware/真机上写回缓存的 MMIO 写入可能不会立刻 + # 反映到扫描输出,表现为系统还在跑但屏幕停在旧画面。共享内存(/dev/desk)返回 0。 + def mmap_uncached(self) -> int: return 0 class _DevFSObject: @@ -288,6 +292,12 @@ class _DevFSObject: file_ptr: devfs_file* = fd_table[fd].file return file_ptr.mmap_info(fd, out_phys, out_size) + def get_mmap_uncached(self, fd: int) -> int: + if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file: + return 0 + file_ptr: devfs_file* = fd_table[fd].file + return file_ptr.mmap_uncached() + def list_directory(self, path: const str, buffer: str, size: uint32) -> int: @@ -332,6 +342,9 @@ def devfs_ioctl(fd: int, cmd: uint32, arg: rawptr) -> int: def devfs_get_mmap_info(fd: int, out_phys: uint64*, out_size: uint64*) -> int: return DevFSObject.get_mmap_info(fd, out_phys, out_size) +def devfs_mmap_uncached(fd: int) -> int: + return DevFSObject.get_mmap_uncached(fd) + def devfs_list_directory(path: const str, buffer: str, size: uint32) -> int: return DevFSObject.list_directory(path, buffer, size) @@ -527,11 +540,15 @@ virtual class framebuffer_device(devfs_file): if request == FB_WAIT_VBLANK: # 等进入垂直回扫期(0x3DA bit3)。返回等待消耗的迭代数:到上限即超时 #(当前没有 VGA 回扫信号),调用方可据此判断同步是否真的生效。 - t: int = 100000 - while (asm.inb(0x3DA) & 0x08) != 0 and t > 0: t -= 1 - t2: int = 100000 - while (asm.inb(0x3DA) & 0x08) == 0 and t2 > 0: t2 -= 1 - return 100000 - t2 + t: int = 2048 + while (asm.inb(0x3DA) & 0x08) != 0 and t > 0: + t -= 1 + asm.pause() + t2: int = 2048 + while (asm.inb(0x3DA) & 0x08) == 0 and t2 > 0: + t2 -= 1 + asm.pause() + return 2048 - t2 if request != FB_GET_INFO or not arg: return -1 info: FbInfo* = FbInfo*(uint64(arg)) @@ -548,6 +565,8 @@ virtual class framebuffer_device(devfs_file): if out_size: *out_size = self.fb_size return 0 + def mmap_uncached(self) -> int: return 1 + fb0_file: framebuffer_device* = framebuffer_device() def devfs_register_fb0(fb: rawptr, width: int, height: int, pitch: int, format: int, size: uint64) -> int: diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_diskio.vp b/VKernel/Kernel/drivers/fs/fat32/fat32_diskio.vp index b7ddc56..b188194 100644 --- a/VKernel/Kernel/drivers/fs/fat32/fat32_diskio.vp +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_diskio.vp @@ -31,12 +31,10 @@ def read(pdrv: uint8, buff: uint8*, sector: uint32, count: uint32) -> types.DRES if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY drive_lock.lock() - i: uint32 - for i in range(count): - rd_res: int = ide.readSector(sector + i, buff + (i * 512)) - if rd_res != 0: - drive_lock.unlock() - return types.DRESULT.RES_ERROR + rd_res: int = ide.readSectors(sector, buff, count) + if rd_res != 0: + drive_lock.unlock() + return types.DRESULT.RES_ERROR drive_lock.unlock() return types.DRESULT.RES_OK diff --git a/VKernel/Kernel/drivers/fs/fat32/fat32_types.vp b/VKernel/Kernel/drivers/fs/fat32/fat32_types.vp index 356f718..c5de502 100644 --- a/VKernel/Kernel/drivers/fs/fat32/fat32_types.vp +++ b/VKernel/Kernel/drivers/fs/fat32/fat32_types.vp @@ -6,6 +6,10 @@ import fat32_time as ftime import viperlib import drivers.serial.uart.serial as serial +# Max sectors per coalesced file read (64 KiB); the IDE layer further caps DMA +# commands at 128 sectors. +define DMA_READ_CLUSTER_CAP = 128 + def _read32(buf: uint8*, off: uint32) -> uint32: return uint32(buf[off]) | (uint32(buf[off + 1]) << 8) | (uint32(buf[off + 2]) << 16) | (uint32(buf[off + 3]) << 24) @@ -1525,6 +1529,39 @@ class fileobj: if self.file_pos >= self.file_size: break if self.first_cluster < 2: break if self.current_cluster < 2 or vol.is_eoc(self.current_cluster): break + # Fast path: at a cluster boundary, coalesce contiguous clusters and + # issue one multi-sector read (BMIDE DMA) instead of one PIO command + # per 512-byte sector. Capped at 64 KiB per call; the loop continues. + if (self.file_pos % vol.cluster_size) == 0 and remaining >= vol.cluster_size: + max_bytes: uint32 = remaining + fleft: uint32 = self.file_size - self.file_pos + if max_bytes > fleft: max_bytes = fleft + max_clusters: uint32 = max_bytes / vol.cluster_size + cap_clusters: uint32 = (uint32(DMA_READ_CLUSTER_CAP) * uint32(512)) / vol.cluster_size + if max_clusters > cap_clusters: max_clusters = cap_clusters + run: uint32 = 0 + c: uint32 = self.current_cluster + next_c: uint32 = 0 + while run < max_clusters: + if c < 2 or vol.is_eoc(c): break + run = run + 1 + next_c = vol.cluster_get_next(c) + if next_c != c + 1: break + c = next_c + if run >= 2: + sectors: uint32 = run * uint32(vol.cluster_sectors) + abs_sect: uint64 = vol.cluster_to_sector(self.current_cluster) + uint64(vol.hidden_sectors) + vol.lock.lock() + res_run: DRESULT = diskio.read(vol.pdrv, buf_ptr, uint32(abs_sect), sectors) + vol.lock.unlock() + if res_run != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR + nbytes: uint32 = sectors * uint32(512) + buf_ptr = buf_ptr + nbytes + self.file_pos = self.file_pos + nbytes + total_read = total_read + nbytes + remaining = remaining - nbytes + self.current_cluster = next_c + continue cluster_offset: uint32 = self.file_pos % vol.cluster_size sector_in_cluster: uint32 = cluster_offset / 512 offset_in_sector: uint32 = cluster_offset % 512 diff --git a/VKernel/Kernel/drivers/storage/ide/ide.vp b/VKernel/Kernel/drivers/storage/ide/ide.vp index 2f25d42..505df6d 100644 --- a/VKernel/Kernel/drivers/storage/ide/ide.vp +++ b/VKernel/Kernel/drivers/storage/ide/ide.vp @@ -1,5 +1,9 @@ import asm import drivers.usb.pci as pci +import drivers.serial.uart.serial as serial +import paging.paging as paging +import viperlib +import string enum IDError: @@ -21,6 +25,119 @@ define SECTOR_SIZE = 512 _ide_initialized: int = 0 +# ---- Bus-master IDE (BMIDE) ---- +# One 64 KiB PRD entry per command; the DMA buffer is 64 KiB aligned in BSS so +# a single entry never crosses a 64 KiB boundary. +define DMA_MAX_SECTORS = 16 # 16 * 512 = 8 KiB per DMA command +define DMA_AREA_SIZE = 131072 +define DMA_SCRATCH_LBA = 0x7FFF0 # near the end of the 256 MiB image; safe to restore +define DMA_AREA_SIZE = 131072 +_bm_base: uint16 = 0 +_bm_ok: int = 0 +_dma_area: [DMA_AREA_SIZE]uint8 +_dma_buf: uint64 = 0 +_dma_buf_pa: uint64 = 0 +_prdt_area: [64]uint64 +_prdt: uint64 = 0 +_prdt_pa: uint64 = 0 +_dma_logged: int = 0 +_dma_tried: int = 0 +_dma_dir: uint8 = 0x08 +_pci_cmd: uint16 = 0 + +def _mfence(): + __asm__("mfence", op=[ASM_DESCR.CLOBBER_MEMORY]) + +def _clflush_range(addr: uint64, nbytes: uint64): + a: uint64 = addr & ~uint64(63) + end: uint64 = addr + nbytes + while a < end: + __asm__(f"clflush [{asminp(a, ASM_DESCR.REG_ANY)}]", + op=[ASM_DESCR.CLOBBER_MEMORY]) + a = a + uint64(64) + +def _ide_reset(): + asm.outb(0x3F6, 0x04) + for _ in range(10000): asm.nop() + asm.outb(0x3F6, 0x00) + for _ in range(10000): asm.nop() + +# Ask the drive to switch to a DMA transfer mode. After SRST the drive is in +# its default PIO mode; READ DMA (0xC8) may be aborted until SET FEATURES runs. +# use_mdma=0: Ultra DMA (word 88), use_mdma=1: multiword DMA (word 63). +def _set_dma_mode(use_mdma: int) -> int: + err: IDError = waitReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: return -1 + asm.outb(IDE_baseport_PRIMARY + 6, uint8(0xE0)) + asm.outb(IDE_baseport_PRIMARY + 7, uint8(0xEC)) # IDENTIFY + err = waitDataReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: return -1 + ident: [256]uint16 + i: int = 0 + while i < 256: + ident[i] = asm.inw(IDE_baseport_PRIMARY) + i = i + 1 + mode: int = -1 + value: int = 0 + if use_mdma != 0: + # word 63 bits 0..7: multiword DMA modes 0..7 (we use 0..2) + m: int = 2 + while m >= 0: + if ident[63] & uint16(1 << m): + mode = m + break + m = m - 1 + if mode < 0: return -1 + value = 0x20 | mode + else: + # word 88: Ultra DMA modes supported (bits 0..6). Pick the highest mode + # the drive advertises: the firmware/controller timing is usually already + # programmed for it. + m = 6 + while m >= 0: + if ident[88] & uint16(1 << m): + mode = m + break + m = m - 1 + if mode < 0: return -1 + value = 0x40 | mode + err = waitReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: return -1 + asm.outb(IDE_baseport_PRIMARY + 1, uint8(0x03)) # SET FEATURES: transfer mode + asm.outb(IDE_baseport_PRIMARY + 2, uint8(value)) + asm.outb(IDE_baseport_PRIMARY + 6, uint8(0xE0)) + asm.outb(IDE_baseport_PRIMARY + 7, uint8(0xEF)) + err = waitReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: return -1 + st: uint8 = asm.inb(IDE_baseport_PRIMARY + 7) + if st & uint8(0x01): return -1 + return mode + +# Read a scratch sector with DMA and compare against the PIO copy. A wrong +# bus-master direction bit may write the scratch sector; restore it via PIO. +def _dma_selftest(dr: uint8) -> int: + global _dma_dir + _dma_dir = dr + ref: [512]uint8 + rr: int = readSector(uint32(DMA_SCRATCH_LBA), &ref[0]) + if rr != IDError.IDE_ERR_NONE: return -1 + r: int = _dma_read(uint32(DMA_SCRATCH_LBA), 1) + ok: int = 0 + if r == IDError.IDE_ERR_NONE: + if string.memcmp(rawptr(_dma_buf), rawptr(&ref[0]), 512) == 0: ok = 1 + if ok == 0: + writeSector(uint32(DMA_SCRATCH_LBA), &ref[0]) + return -1 + return 0 + +# Reset, set the requested DMA class, and run the direction selftest. +def _try_dma(use_mdma: int, dr: uint8) -> int: + _ide_reset() + m: int = _set_dma_mode(use_mdma) + if m < 0: return -1 + if _dma_selftest(dr) == 0: return m + return -1 + def waitReady(baseport: uint16) -> static IDError: timeout: int = 100000 for _ in range(timeout, 0, -1): @@ -64,7 +181,8 @@ def flushStaleData(): p = p + 1 def init() -> int: - global _ide_initialized + global _ide_initialized, _bm_base, _bm_ok, _dma_buf, _prdt, _pci_cmd + global _dma_buf_pa, _prdt_pa if _ide_initialized: return IDError.IDE_ERR_NONE bus: uint8 dev_n: uint8 @@ -75,14 +193,56 @@ def init() -> int: vendor: uint16 = pci.pci_read16(bus, dev_n, func, 0x00) if vendor == 0xFFFF: continue cls: uint32 = pci.pci_get_class(bus, dev_n, func) - if cls == 0x010180 or cls == 0x0101: + # class/subclass = 0x0101 (IDE); progIF bit7 = bus-master capable. + # VMware's PIIX4 reports progIF 0x8A (not exactly 0x80), so do + # not compare the whole 24-bit class/progIF word. + cls_sub: uint32 = cls & uint32(0xFFFF00) + proto: uint32 = cls & uint32(0xFF) + if cls_sub == uint32(0x010100) and (proto & uint32(0x80)) != 0: + bar4: uint32 = pci.pci_read32(bus, dev_n, func, 0x20) + bar0: uint32 = pci.pci_read32(bus, dev_n, func, 0x10) + lb0: [128]char + viperlib.snprintf(&lb0, 128, "[ide] pci %d:%d.%d cls=0x%x bar0=0x%x bar4=0x%x\n", + uint32(bus), uint32(dev_n), uint32(func), + cls, bar0, bar4) + serial.puts(&lb0) cmd: uint16 = pci.pci_read16(bus, dev_n, func, 0x04) pci.pci_write32(bus, dev_n, func, 0x04, uint32(cmd | 0x0007)) + if _bm_base == 0 and (bar4 & uint32(1)) != 0: + _bm_base = uint16(bar4 & uint32(0xFFFC)) + _pci_cmd = pci.pci_read16(bus, dev_n, func, 0x04) asm.outb(0x3F6, 0x04) for _ in range(10000): asm.nop() asm.outb(0x3F6, 0x00) for _ in range(10000): asm.nop() status: uint8 = asm.inb(0x1F7) + if _bm_base != 0: + _dma_buf = (uint64(&_dma_area[0]) + uint64(0xFFFF)) & ~uint64(0xFFFF) + _prdt = (uint64(&_prdt_area[0]) + uint64(15)) & ~uint64(15) + _dma_buf_pa = paging.GetPagePhys(_dma_buf) + if _dma_buf_pa != 0: _dma_buf_pa = _dma_buf_pa + (_dma_buf & uint64(0xFFF)) + _prdt_pa = paging.GetPagePhys(_prdt) + if _prdt_pa != 0: _prdt_pa = _prdt_pa + (_prdt & uint64(0xFFF)) + lb: [160]char + viperlib.snprintf(&lb, 160, "[ide] BMIDE base=0x%x pci_cmd=0x%x dma_va=0x%x dma_pa=0x%x prdt_va=0x%x prdt_pa=0x%x\n", + uint32(_bm_base), uint32(_pci_cmd), uint32(_dma_buf), uint32(_dma_buf_pa), + uint32(_prdt), uint32(_prdt_pa)) + serial.puts(&lb) + _bm_ok = 1 + dm: int = _try_dma(0, uint8(0x08)) + if dm < 0: dm = _try_dma(0, uint8(0x00)) + if dm < 0: dm = _try_dma(1, uint8(0x08)) + if dm < 0: dm = _try_dma(1, uint8(0x00)) + if dm >= 0: + mb: [96]char + viperlib.snprintf(&mb, 96, "[ide] DMA selftest ok mode=%d dir=0x%x\n", + dm, uint32(_dma_dir)) + serial.puts(&mb) + else: + _bm_ok = 0 + serial.puts("[ide] DMA unavailable, using PIO\n") + else: + serial.puts("[ide] BMIDE unavailable, using PIO\n") _ide_initialized = 1 return IDError.IDE_ERR_NONE @@ -130,6 +290,116 @@ def readSector(lba: uint32, buffer: rawptr) -> int: # serial.puts(dbg) return IDError.IDE_ERR_NONE +# One bus-master DMA command (<= 64 KiB) into the internal aligned buffer. +def _dma_read(lba: uint32, sectors: uint32) -> int: + global _bm_ok, _dma_logged, _dma_tried, _dma_dir + if _bm_ok == 0 or _dma_buf == 0 or _prdt == 0: + return IDError.IDE_ERR_DEVICE_NOT_READY + if sectors == 0 or sectors > uint32(DMA_MAX_SECTORS): + return IDError.IDE_ERR_SECTOR_COUNT + err: IDError = waitReady(IDE_baseport_PRIMARY) + if err != IDError.IDE_ERR_NONE: + return err + # If a previous PIO command left data in the drive buffer (DRQ=1), command + # block writes are ignored. Drain it before starting the DMA command. + flushStaleData() + # Stop the engine, set direction = device -> memory. + asm.outb(_bm_base, uint8(0x00)) + bytes: uint32 = sectors * uint32(512) + buf_pa: uint64 = _dma_buf_pa + if buf_pa == 0: buf_pa = _dma_buf + prdt_pa: uint64 = _prdt_pa + if prdt_pa == 0: prdt_pa = _prdt + prd: uint32* = uint32*(_prdt) + prd[0] = uint32(buf_pa) + # count == 0 means 64 KiB; bit15 is EOT. + prd[1] = (bytes & uint32(0xFFFF)) | uint32(0x8000) + if _dma_tried == 0: + _dma_tried = 1 + dbg: [160]char + viperlib.snprintf(&dbg, 160, "[ide] dma try lba=0x%x n=%u buf=0x%x prdt=0x%x prd=%x,%x\n", + lba, sectors, uint32(buf_pa), uint32(prdt_pa), prd[0], prd[1]) + serial.puts(&dbg) + _clflush_range(_prdt, uint64(8)) + _clflush_range(_dma_buf, uint64(bytes)) + _mfence() + asm.outl(_bm_base + uint16(4), uint32(prdt_pa)) + asm.outb(_bm_base + uint16(2), uint8(0x06)) # clear error/IRQ + # Start the bus-master engine first; the PIIX4 waits for the drive's DMA + # request. QEMU tolerates either order. + asm.outb(_bm_base, uint8(_dma_dir | uint8(0x01))) # start + direction + # ATA READ DMA (LBA28, 0xC8) + asm.outb(IDE_baseport_PRIMARY + 2, uint8(sectors & uint32(0xFF))) + asm.outb(IDE_baseport_PRIMARY + 3, uint8(lba & uint32(0xFF))) + asm.outb(IDE_baseport_PRIMARY + 4, uint8((lba >> 8) & uint32(0xFF))) + asm.outb(IDE_baseport_PRIMARY + 5, uint8((lba >> 16) & uint32(0xFF))) + asm.outb(IDE_baseport_PRIMARY + 6, uint8(uint32(0xE0) | ((lba >> 24) & uint32(0x0F)))) + asm.outb(IDE_baseport_PRIMARY + 7, uint8(0xC8)) + ok: int = 0 + t: int = 2000000 + st: uint8 = 0 + while t > 0: + st = asm.inb(_bm_base + uint16(2)) + if st & uint8(0x02): break # error + if st & uint8(0x04): ok = 1; break # interrupt/completion + t = t - 1 + asm.pause() + asm.outb(_bm_base, uint8(0x00)) # stop + asm.outb(_bm_base + uint16(2), uint8(0x06)) + ata: uint8 = asm.inb(IDE_baseport_PRIMARY + 7) + if ok == 0: + fb: [96]char + viperlib.snprintf(&fb, 96, "[ide] DMA timeout st=0x%x ata=0x%x\n", uint32(st), uint32(ata)) + serial.puts(&fb) + return IDError.IDE_ERR_TIMEOUT + if ata & uint8(0x01): + fb2: [96]char + viperlib.snprintf(&fb2, 96, "[ide] DMA ata error st=0x%x ata=0x%x\n", uint32(st), uint32(ata)) + serial.puts(&fb2) + return IDError.IDE_ERR_DEVICE_ERROR + _clflush_range(_dma_buf, uint64(bytes)) + _mfence() + if _dma_logged == 0: + _dma_logged = 1 + serial.puts("[ide] DMA read ok\n") + return IDError.IDE_ERR_NONE + +# Multi-sector read: DMA for >=2 sectors, PIO for a single sector or after a +# DMA failure. Caller must hold the drive lock. +def readSectors(lba: uint32, buffer: rawptr, count: uint32) -> int: + global _bm_ok + if buffer is None: return IDError.IDE_ERR_BUFFER_NULL + if count == 0: return IDError.IDE_ERR_SECTOR_COUNT + if _bm_ok == 0 or count < 2: + i: uint32 = 0 + while i < count: + r: int = readSector(lba + i, rawptr(uint64(buffer) + uint64(i) * uint64(512))) + if r != IDError.IDE_ERR_NONE: return r + i = i + 1 + return IDError.IDE_ERR_NONE + remaining: uint32 = count + cur: uint32 = lba + dst: uint64 = uint64(buffer) + while remaining > 0: + chunk: uint32 = remaining + if chunk > uint32(DMA_MAX_SECTORS): chunk = uint32(DMA_MAX_SECTORS) + r = _dma_read(cur, chunk) + if r != IDError.IDE_ERR_NONE: + serial.puts("[ide] DMA failed, falling back to PIO\n") + _bm_ok = 0 + _ide_reset() + i = 0 + while i < remaining: + rr: int = readSector(cur + i, rawptr(dst + uint64(i) * uint64(512))) + if rr != IDError.IDE_ERR_NONE: return rr + i = i + 1 + return IDError.IDE_ERR_NONE + string.memcpy(rawptr(dst), rawptr(_dma_buf), uint64(chunk) * uint64(512)) + dst = dst + uint64(chunk) * uint64(512) + cur = cur + chunk + remaining = remaining - chunk + return IDError.IDE_ERR_NONE + def writeSector(lba: uint32, buffer: const rawptr) -> int: if buffer is None: return IDError.IDE_ERR_BUFFER_NULL err: IDError = waitReady(IDE_baseport_PRIMARY) diff --git a/VKernel/Kernel/execrunner/elf.vp b/VKernel/Kernel/execrunner/elf.vp index 95c8ea0..78d446b 100644 --- a/VKernel/Kernel/execrunner/elf.vp +++ b/VKernel/Kernel/execrunner/elf.vp @@ -32,6 +32,7 @@ define ET_DYN = 3 define EM_X86_64 = 62 define PT_LOAD = 1 define PT_DYNAMIC = 2 +define PT_INTERP = 3 define PF_X = 1 define PF_W = 2 define PF_R = 4 @@ -120,7 +121,7 @@ define MAX_SHDRS = 64 define MAX_DYNS = 64 define MAX_RELAS = 256 -def load_elf(path: const str, do_map: int = 1) -> rawptr: +def load_elf(path: const str, do_map: int = 1, base: uint64 = 0) -> rawptr: fp: fat32_types.fileobj* = fat32.open(path, fat32_types.FA_READ) if fp is None: err: uint32 = fat32.last_error() @@ -180,6 +181,10 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr: serial.puts("[elf] not ET_EXEC or ET_DYN\n") fat32.close(fp) return None + if ehdr.e_type == ET_EXEC and base != 0: + serial.puts("[elf] ET_EXEC cannot be relocated\n") + fat32.close(fp) + return None if ehdr.e_machine != EM_X86_64: serial.puts("[elf] not x86_64\n") @@ -246,7 +251,12 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr: viperlib.snprintf(&cr2, 80, "[elf] vaddr 0x%lx-0x%lx pages=%lu\n", lowest_vaddr, highest_vaddr_end, total_pages) serial.puts(cr2) - raw_alloc: rawptr = mm.malloc(total_pages * 0x1000 + 0x1000) + # Keep the image offset of the first LOAD segment: the final page table maps + # whole pages, so the physical buffer must start at a page boundary that + # corresponds to `virt_page_start` below. + low_off: uint64 = lowest_vaddr & uint64(0xFFF) + alloc_sz: uint64 = total_pages * uint64(0x1000) + uint64(0x2000) + raw_alloc: rawptr = mm.malloc(alloc_sz) _db_ra: [80]char viperlib.snprintf(&_db_ra, 80, "[elf] malloc ret raw_alloc=0x%lx\n", uint64(raw_alloc)) serial.puts(_db_ra) @@ -255,7 +265,8 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr: fat32.close(fp) return None - phys_base_aligned: uint64 = (uint64(raw_alloc) + uint64(0xFFF)) & ~uint64(0xFFF) + phys_page_start_u: uint64 = (uint64(raw_alloc) + uint64(0xFFF)) & ~uint64(0xFFF) + phys_base_aligned: uint64 = phys_page_start_u + low_off phys_base: rawptr = rawptr(phys_base_aligned) string.memset(phys_base, 0, total_pages * 0x1000) @@ -280,9 +291,9 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr: fat32.close(fp) - virt_page_start: uint64 = lowest_vaddr & ~uint64(0xFFF) - phys_page_start: uint64 = uint64(phys_base) & ~uint64(0xFFF) - map_end: uint64 = (highest_vaddr_end + 0xFFF) & ~uint64(0xFFF) + virt_page_start: uint64 = (base + lowest_vaddr) & ~uint64(0xFFF) + phys_page_start: uint64 = phys_page_start_u + map_end: uint64 = (base + highest_vaddr_end + uint64(0xFFF)) & ~uint64(0xFFF) num_map_pages: uint64 = (map_end - virt_page_start) // 0x1000 # Store mapping info for spawn_elf (used when do_map=0) @@ -298,7 +309,7 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr: page_flags: uint64 = paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER paging.MapPage(virt_addr, phys_addr, page_flags) - entry_addr: rawptr = rawptr(entry_point) + entry_addr: rawptr = rawptr(base + entry_point) return entry_addr @@ -319,7 +330,7 @@ def run_elf(path: const str) -> int: serial.puts("[elf] returned from entry\n") return 0 -define MAX_SPAWNED = 8 +define MAX_SPAWNED = 32 _elf_entries: [MAX_SPAWNED]rawptr # User stack virtual address: top of 48-bit user space, 64KB below @@ -336,6 +347,153 @@ _last_elf_phys_page_start: uint64 = 0 _last_elf_virt_page_start: uint64 = 0 _last_elf_num_map_pages: uint64 = 0 +# ============================================================================ +# Linux process startup stack: argc/argv/envp/auxv +# ============================================================================ + +define AT_NULL = 0 +define AT_PHDR = 3 +define AT_PHENT = 4 +define AT_PHNUM = 5 +define AT_PAGESZ = 6 +define AT_BASE = 7 +define AT_FLAGS = 8 +define AT_ENTRY = 9 +define AT_UID = 11 +define AT_EUID = 12 +define AT_GID = 13 +define AT_EGID = 14 +define AT_PLATFORM = 15 +define AT_HWCAP = 16 +define AT_CLKTCK = 17 +define AT_SECURE = 23 +define AT_RANDOM = 25 +define AT_EXECFN = 31 + +_rng_stack_state: uint64 = 0x243F6A8885A308D3 + +def _stack_rand() -> uint8: + global _rng_stack_state + _rng_stack_state = _rng_stack_state * uint64(6364136223846793005) + uint64(1442695040888963407) + return uint8((_rng_stack_state >> 33) & uint64(0xFF)) + +def _stk_u64(buf: uint8*, u: uint64*, v: uint64): + if u[0] < uint64(8): return + u[0] = u[0] - uint64(8) + q: uint64* = uint64*(uint64(buf) + u[0]) + q[0] = v + +def _stk_str(buf: uint8*, u: uint64*, s: const str, base_va: uint64) -> uint64: + n: uint64 = uint64(int(string.strlen(s)) + 1) + if n > uint64(256): n = uint64(256) + if u[0] < n: return uint64(0) + u[0] = u[0] - n + dst: uint8* = uint8*(uint64(buf) + u[0]) + i: int = 0 + while uint64(i) < n: + dst[i] = uint8(s[i]) + i += 1 + dst[n - uint64(1)] = uint8(0) + return base_va + u[0] + +# Build the stack image in a 4KB scratch buffer, then copy it to the top of the +# process' 64KB user stack. Pointers are virtual addresses (USER_STACK_VTOP-based). +def _build_argv_stack(p: proc.Process*, path: const str, argv: rawptr, envp: rawptr, + phdr_va: uint64, entry_va: uint64, phent: uint64, phnum: uint64, + interp_base: uint64) -> int: + buf: [4096]uint8 + string.memset(&buf[0], 0, 4096) + base_va: uint64 = USER_STACK_VTOP - uint64(4096) + u: uint64 = uint64(4096) + path_va: uint64 = _stk_str(&buf[0], &u, path, base_va) + argv_va: [17]uint64 + env_va: [17]uint64 + argc: int = 0 + envc: int = 0 + if argv is not None: + av: str* = str*(argv) + while argc < 16: + sp: str = av[argc] + if sp is None: break + argv_va[argc] = _stk_str(&buf[0], &u, sp, base_va) + argc += 1 + if argc == 0: + argv_va[0] = path_va + argc = 1 + if envp is not None: + ev: str* = str*(envp) + while envc < 16: + sp2: str = ev[envc] + if sp2 is None: break + env_va[envc] = _stk_str(&buf[0], &u, sp2, base_va) + envc += 1 + plat_va: uint64 = _stk_str(&buf[0], &u, "x86_64", base_va) + u = u - uint64(16) + ri: int = 0 + while ri < 16: + rp: uint8* = uint8*(uint64(&buf[0]) + u + uint64(ri)) + rp[0] = _stack_rand() + ri += 1 + random_va: uint64 = base_va + u + # keep every pushed word 8-byte aligned; the gap below the strings is unused + u = u & ~uint64(7) + aux_t: [24]uint64 + aux_v: [24]uint64 + naux: int = 0 + aux_t[naux] = uint64(AT_PHDR); aux_v[naux] = phdr_va; naux += 1 + aux_t[naux] = uint64(AT_PHENT); aux_v[naux] = phent; naux += 1 + aux_t[naux] = uint64(AT_PHNUM); aux_v[naux] = phnum; naux += 1 + aux_t[naux] = uint64(AT_PAGESZ); aux_v[naux] = uint64(4096); naux += 1 + aux_t[naux] = uint64(AT_BASE); aux_v[naux] = interp_base; naux += 1 + aux_t[naux] = uint64(AT_FLAGS); aux_v[naux] = uint64(0); naux += 1 + aux_t[naux] = uint64(AT_ENTRY); aux_v[naux] = entry_va; naux += 1 + aux_t[naux] = uint64(AT_UID); aux_v[naux] = uint64(0); naux += 1 + aux_t[naux] = uint64(AT_EUID); aux_v[naux] = uint64(0); naux += 1 + aux_t[naux] = uint64(AT_GID); aux_v[naux] = uint64(0); naux += 1 + aux_t[naux] = uint64(AT_EGID); aux_v[naux] = uint64(0); naux += 1 + aux_t[naux] = uint64(AT_PLATFORM); aux_v[naux] = plat_va; naux += 1 + aux_t[naux] = uint64(AT_HWCAP); aux_v[naux] = uint64(0); naux += 1 + aux_t[naux] = uint64(AT_CLKTCK); aux_v[naux] = uint64(100); naux += 1 + aux_t[naux] = uint64(AT_SECURE); aux_v[naux] = uint64(0); naux += 1 + aux_t[naux] = uint64(AT_RANDOM); aux_v[naux] = random_va; naux += 1 + aux_t[naux] = uint64(AT_EXECFN); aux_v[naux] = path_va; naux += 1 + # memory (low -> high): argc, argv[], NULL, envp[], NULL, auxv[], AT_NULL + # stack grows down, so push AT_NULL first and the rest in reverse. + _stk_u64(&buf[0], &u, uint64(0)) + _stk_u64(&buf[0], &u, uint64(0)) + ai: int = naux - 1 + while ai >= 0: + _stk_u64(&buf[0], &u, aux_v[ai]) + _stk_u64(&buf[0], &u, aux_t[ai]) + ai -= 1 + _stk_u64(&buf[0], &u, uint64(0)) + ai = envc - 1 + while ai >= 0: + _stk_u64(&buf[0], &u, env_va[ai]) + ai -= 1 + _stk_u64(&buf[0], &u, uint64(0)) + ai = argc - 1 + while ai >= 0: + _stk_u64(&buf[0], &u, argv_va[ai]) + ai -= 1 + _stk_u64(&buf[0], &u, uint64(argc)) + used: uint64 = uint64(4096) - u + if used == 0 or used > uint64(4096): return -1 + dst: uint8* = uint8*(p.user_stack - used) + src: uint8* = uint8*(uint64(&buf[0]) + u) + string.memcpy(dst, src, used) + p.entry_rsp = USER_STACK_VTOP - used + return 0 + +# Map the most recent load_elf() result into the *active* (cloned) PML4. +def _map_last_elf(): + mi: uint64 = 0 + while mi < _last_elf_num_map_pages: + paging.MapPage(_last_elf_virt_page_start + mi * uint64(0x1000), + _last_elf_phys_page_start + mi * uint64(0x1000), + paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) + mi += 1 + def _elf_entry_wrapper(arg: rawptr) -> int: pid: int = int(uint64(arg)) _db1: [64]char @@ -376,13 +534,24 @@ def _elf_entry_wrapper(arg: rawptr) -> int: # will set RFLAGS.IF from the saved frame (0x202) for user mode. # This prevents timer interrupt preemption between TSS setup and iretq. # Drop to Ring 3 user mode via iretq - proc.drop_to_user_mode(entry_fn, user_stack_vtop, cur_p.kern_stack) + user_rsp: uint64 = USER_STACK_VTOP + if cur_p.entry_rsp != 0: + user_rsp = cur_p.entry_rsp + proc.drop_to_user_mode(entry_fn, user_rsp, cur_p.kern_stack) cur: proc.Process* = proc.ProcessManager.current() cur.exit() return 0 -def spawn_elf(path: const str) -> int: +def spawn_elf(path: const str, argv: rawptr = None, envp: rawptr = None) -> int: if ELF_LOAD == 0: return -1 # [临时] 见文件头 ELF_LOAD 开关说明 + phdr_va: uint64 = 0 + phent_u: uint64 = 0 + phnum_u: uint64 = 0 + main_base: uint64 = 0 + main_entry: uint64 = 0 + interp_len: int = 0 + interp_path: [256]char + string.memset(&interp_path[0], 0, 256) fat32.lock() name_start: int = 0 si: int = 0 @@ -408,17 +577,35 @@ def spawn_elf(path: const str) -> int: fat32.read(fp_scan, &ehdr_buf_s, 64) ehdr_s: Elf64_Ehdr* = &ehdr_buf_s if ehdr_s.e_type == ET_EXEC or ehdr_s.e_type == ET_DYN: + if ehdr_s.e_type == ET_DYN: + main_base = uint64(0x60000000) + main_entry = main_base + ehdr_s.e_entry phoff_s: uint64 = ehdr_s.e_phoff phentsize_s: uint16 = ehdr_s.e_phentsize phnum_s: uint16 = ehdr_s.e_phnum if phentsize_s > 0 and phnum_s > 0 and phnum_s <= MAX_PHDRS: phdr_bufs_s: [MAX_PHDRS]Elf64_Phdr string.memset(&phdr_bufs_s, 0, MAX_PHDRS * 56) + phent_u = uint64(phentsize_s) + phnum_u = uint64(phnum_s) found_load_s: int = 0 for pi_s in range(phnum_s): fat32.seek(fp_scan, uint32(phoff_s + uint64(pi_s) * uint64(phentsize_s))) fat32.read(fp_scan, &phdr_bufs_s[pi_s], 56) + if phdr_bufs_s[pi_s].p_type == PT_INTERP: + ilen: uint64 = uint64(phdr_bufs_s[pi_s].p_filesz) + if ilen > uint64(255): ilen = uint64(255) + if ilen > 0: + fat32.seek(fp_scan, uint32(phdr_bufs_s[pi_s].p_offset)) + ir: int = int(fat32.read(fp_scan, &interp_path[0], uint32(ilen))) + if ir > 0: + interp_len = ir + interp_path[interp_len] = 0 if phdr_bufs_s[pi_s].p_type == PT_LOAD: + ph_off_s: uint64 = uint64(phdr_bufs_s[pi_s].p_offset) + ph_fsz_s: uint64 = uint64(phdr_bufs_s[pi_s].p_filesz) + if ehdr_s.e_phoff >= ph_off_s and ehdr_s.e_phoff < ph_off_s + ph_fsz_s: + phdr_va = main_base + uint64(phdr_bufs_s[pi_s].p_vaddr) + (uint64(ehdr_s.e_phoff) - ph_off_s) seg_end_s: uint64 = phdr_bufs_s[pi_s].p_vaddr + phdr_bufs_s[pi_s].p_memsz if found_load_s == 0: lowest_vaddr = phdr_bufs_s[pi_s].p_vaddr @@ -431,20 +618,10 @@ def spawn_elf(path: const str) -> int: highest_vaddr_end = seg_end_s fat32.close(fp_scan) - isolate_va: uint64 = lowest_vaddr + isolate_va: uint64 = main_base + lowest_vaddr if isolate_va == 0: isolate_va = 0x400000 - # Load ELF to physical memory WITHOUT mapping in kernel PML4. - # do_map=0: load_elf stores mapping info in globals for spawn_elf to use. - # ROOT CAUSE FIX: previously load_elf mapped ELF vaddr (e.g. 0x400000) into - # kernel PML4, overwriting identity mapping of buddy memory_block headers. - entry: rawptr = load_elf(path, 0) - if entry is None: - serial.puts("[elf] spawn: load failed\n") - fat32.unlock() - return -1 - # Clone kernel PML4 (identity mapping intact, no ELF mapping yet) proc_pml4: uint64 = paging.clone_for_process(isolate_va) slog1: [80]char @@ -455,21 +632,40 @@ def spawn_elf(path: const str) -> int: fat32.unlock() return -1 - # Map ELF pages in the CLONED PML4 (not kernel's). - # This prevents overwriting kernel identity mapping at 0x400000 which - # would corrupt buddy system memory_block headers. - # Must disable interrupts: @pml4 global is used by MapPage and a timer - # interrupt mid-section would call switch_pid, corrupting @pml4. + # Load the main image and (if present) the interpreter, mapping each into + # the cloned PML4 immediately so the next load can reuse the globals. + # Interrupts stay off: @pml4 is global. asm.cli() _kernel_cr3: uint64 = 0 __asm__(f"""mov {asmout(_kernel_cr3, ASM_DESCR.OUTPUT_REG)}, cr3""", op=[ASM_DESCR.CLOBBER_MEMORY]) __asm__(f"mov cr3, {asminp(proc_pml4, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY]) paging.set_active_pml4(proc_pml4) - mi: uint64 - for mi in range(_last_elf_num_map_pages): - _va: uint64 = _last_elf_virt_page_start + mi * 0x1000 - _pa: uint64 = _last_elf_phys_page_start + mi * 0x1000 - paging.MapPage(_va, _pa, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) + + entry: rawptr = load_elf(path, 0, main_base) + if entry is None: + __asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY]) + paging.set_active_pml4(_kernel_cr3) + asm.sti() + serial.puts("[elf] spawn: load failed\n") + fat32.unlock() + return -1 + _map_last_elf() + + interp_entry: rawptr = None + interp_base: uint64 = uint64(0) + if interp_len > 0: + interp_base = uint64(0x70000000) + interp_entry = load_elf(&interp_path[0], 0, interp_base) + if interp_entry is None: + __asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY]) + paging.set_active_pml4(_kernel_cr3) + asm.sti() + serial.puts("[elf] spawn: interpreter load failed\n") + fat32.unlock() + return -1 + _map_last_elf() + entry = interp_entry + __asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY]) paging.set_active_pml4(_kernel_cr3) asm.sti() @@ -482,8 +678,14 @@ def spawn_elf(path: const str) -> int: serial.puts("[elf] spawn: create_process failed\n") fat32.unlock() return -2 - p.elf_base = uint64(entry) + p.elf_base = main_base p.pml4_root = proc_pml4 + p.parent_pid = proc.current() + if _build_argv_stack(p, path, argv, envp, phdr_va, main_entry, phent_u, phnum_u, interp_base) != 0: + serial.puts("[elf] spawn: startup stack build failed\n") + p.exit() + fat32.unlock() + return -4 if p.pid >= 0 and p.pid < MAX_SPAWNED: _elf_entries[p.pid] = entry diff --git a/VKernel/Kernel/intr/syscall.vp b/VKernel/Kernel/intr/syscall.vp index af1d63e..b4b25a0 100644 --- a/VKernel/Kernel/intr/syscall.vp +++ b/VKernel/Kernel/intr/syscall.vp @@ -10,48 +10,131 @@ import paging.paging as paging import execrunner.elf as elf import drivers.devfs.devfs as devfs import platform.acpi.acpi as acpi +import platform.pch.timer as timer +import platform.pch.rtc as rtc +import intr.gdt as gdt +import drivers.core.cpu.cpu as cpu +import abi.linux as abi import asm +# ---------------------------------------------------------------------------- +# Syscall entry frame (set up by the compiler-provided syscall_entry): +# rbp+0x00 r15 +0x08 r14 +0x10 r13 +0x18 r12 +# +0x20 r10 +0x28 r9 +0x30 r8 +0x38 rbp +# +0x40 rdi +0x48 rsi +0x50 rdx +0x58 rbx +# +0x60 r11(user flags) +0x68 rcx(user rip) +# +0x70 from-user flag +0x78 user rsp +# The handler itself never touches rbp, so a naked read is stable. +# ---------------------------------------------------------------------------- +define FR_R15 = 0 +define FR_R14 = 8 +define FR_R13 = 16 +define FR_R12 = 24 +define FR_R10 = 32 +define FR_R9 = 40 +define FR_R8 = 48 +define FR_RBP = 56 +define FR_RDI = 64 +define FR_RSI = 72 +define FR_RDX = 80 +define FR_RBX = 88 +define FR_FLAGS = 96 +define FR_RIP = 104 +define FR_FROMU = 112 +define FR_RSP = 120 + +# Entry trampoline state (naked syscall_handler stores the entry rbp here). +export _entry_frame: uint64 = 0 +export _handler_ptr: rawptr = None + + # ============================================================================ -# 系统调用号表(唯一权威来源)。与 Linux x86-64 ABI **同名同号**: -# · Linux 有同名调用 → 直接用它的号;Linux 有而我们未实现的**留空不占号** -# (12 brk / 39 getpid / 84 rmdir / 217 getdents64 / 257 openat … 需要时按号补上); -# · 我们独有的能力**不再新增 syscall**,一律走 devfs 设备文件(open/read/write) -# 或下沉到用户层(见下面「临时私有段」的逐条迁移标注)。 -# 注意:vpsdk/syscall.vp 有一份逐字对应的镜像(vpsdk 不允许依赖 VKernel), -# 增删/改号/改名必须两边同步。 +# 系统调用号表(由 Scripts/syscall_table.py 生成,禁止手改)。 +# 生成源:Scripts/syscall_table.py;build.py 在编译前自动同步。 # ============================================================================ +# BEGIN GENERATED SYSCALL TABLE enum Syscall: - # ---- Linux 对齐段(号 = Linux x86-64 的号)---- - READ = 0 # read(fd, buf, count) - WRITE = 1 # write(fd, buf, count) - OPEN = 2 # open(path, flags) - CLOSE = 3 # close(fd) - STAT = 4 # stat(path, info)(结构体仍是本系统的 fileinfo) - SEEK = 8 # lseek(fd, offset, whence)(whence 目前恒 0 = SEEK_SET) - MMAP = 9 # mmap(size) → VA(ABI 待向 Linux 的 addr/len/prot/flags/fd/off 靠拢) - MUNMAP = 11 # munmap(ptr, size)(目前空实现) - IOCTL = 16 # ioctl(fd, request, arg):设备控制(当前用于 /dev/fb0 查几何) - YIELD = 24 # sched_yield() - GETPID = 39 # getpid():返回当前进程 pid(app 用它过滤 /dev/desk 事件) - THREAD_CREATE = 56 # clone():建用户线程,返回 tid - SPAWN = 59 # execve():加载并运行新 ELF,返回 pid - THREAD_EXIT = 60 # exit():退役当前线程 - MKDIR = 83 # mkdir(path) - REMOVE = 87 # unlink(path) - EXIT = 231 # exit_group():退役当前线程(窗口由 explorer 侧回收) - # ---- 临时私有段(0x1000+):Linux 无对应,且**逐条都会被消掉** ---- - # 目标形态:私有能力不占 syscall 号 → 改 devfs 设备文件。 - # S3e 删掉 UI 那 11 个;S4 删掉 SERIAL_PUTS(改 /dev/serial0);S5 删掉 TELL/SIZE - # (改为 lseek(fd,0,SEEK_CUR) / lseek(fd,0,SEEK_END),见 SEEK 的 whence 支持)。 - OPENDIR = 0x1003 # TODO(linux):openat(O_DIRECTORY) + getdents64 - READDIR = 0x1004 # TODO(linux):getdents64 - CLOSEDIR = 0x1005 # TODO(linux):close - LOAD_SO = 0x1006 # TODO(userspace): dynamic library loading - SO_CALL1 = 0x1007 # TODO(userspace): dynamic library call - REBOOT = 0x1008 # reboot() (ViperOS-specific) - SHUTDOWN = 0x1009 # shutdown() (ViperOS-specific) + # ---- Linux 对齐段(号 = Linux x86-64 的号) ---- + READ = 0 # read(fd, buf, count) + WRITE = 1 # write(fd, buf, count) + OPEN = 2 # open(path, flags) - Linux O_* + CLOSE = 3 # close(fd) + STAT = 4 # stat(path, fileinfo*) - legacy struct + FSTAT = 5 # fstat(fd, struct stat*) + POLL = 7 # poll(fds, nfds, timeout) - stub 0 + SEEK = 8 # lseek(fd, offset, whence) -> new offset + MMAP = 9 # mmap(addr, len, prot, flags, fd, off) + MPROTECT = 10 # mprotect(addr, len, prot) + MUNMAP = 11 # munmap(addr, len) + BRK = 12 # brk(addr) -> new break + RT_SIGACTION = 13 # rt_sigaction() - bookkeeping + RT_SIGPROCMASK = 14 # rt_sigprocmask() - bookkeeping + RT_SIGRETURN = 15 # rt_sigreturn() + IOCTL = 16 # ioctl(fd, request, arg) + PREAD64 = 17 # pread64(fd, buf, count, offset) + PWRITE64 = 18 # pwrite64(fd, buf, count, offset) + READV = 19 # readv(fd, iov, iovcnt) + WRITEV = 20 # writev(fd, iov, iovcnt) + ACCESS = 21 # access(path, mode) + PIPE = 22 # pipe(int[2]) + YIELD = 24 # sched_yield() + MREMAP = 25 # mremap(old, oldsz, newsz, flags, newaddr) + MADVISE = 28 # madvise(addr, len, advice) + DUP = 32 # dup(fd) + DUP2 = 33 # dup2(oldfd, newfd) + NANOSLEEP = 35 # nanosleep(req, rem) + GETPID = 39 # getpid() + CLONE = 56 # clone(flags, stack, ptid, ctid, tls) + FORK = 57 # fork() - copy address space + SPAWN = 59 # execve(path, argv, envp) + THREAD_EXIT = 60 # exit(): retire current thread + WAIT4 = 61 # wait4(pid, status, options, rusage) + KILL = 62 # kill(pid, sig) - bookkeeping + UNAME = 63 # uname(utsname*) + FCNTL = 72 # fcntl(fd, cmd, arg) + GETCWD = 79 # getcwd(buf, size) + CHDIR = 80 # chdir(path) + RENAME = 82 # rename(old, new) + MKDIR = 83 # mkdir(path) + RMDIR = 84 # rmdir(path) + REMOVE = 87 # unlink(path) + READLINK = 89 # readlink(path, buf, size) - no symlinks + GETUID = 102 # getuid() + GETGID = 104 # getgid() + GETEUID = 107 # geteuid() + GETEGID = 108 # getegid() + GETTIMEOFDAY = 96 # gettimeofday(tv, tz) + GETPPID = 110 # getppid() + ARCH_PRCTL = 158 # arch_prctl(code, addr) + REBOOT_LINUX = 169 # reboot(magic1, magic2, cmd, arg) + GETTID = 186 # gettid() + FUTEX = 202 # futex(uaddr, op, val, timeout) + GETDENTS64 = 217 # getdents64(fd, buf, count) + SET_TID_ADDRESS = 218 # set_tid_address(tidptr) + CLOCK_GETTIME = 228 # clock_gettime(clockid, timespec*) + CLOCK_GETRES = 229 # clock_getres(clockid, timespec*) + EXIT = 231 # exit_group(): terminate all threads + OPENAT = 257 # openat(dirfd, path, flags, mode) + NEWFSTATAT = 262 # newfstatat(dirfd, path, stat*, flags) + UNLINKAT = 263 # unlinkat(dirfd, path, flags) + READLINKAT = 267 # readlinkat(dirfd, path, buf, size) + FACCESSAT = 269 # faccessat(dirfd, path, mode, flags) + SET_ROBUST_LIST = 273 # set_robust_list(head, len) + DUP3 = 292 # dup3(oldfd, newfd, flags) + PIPE2 = 293 # pipe2(int[2], flags) + PRLIMIT64 = 302 # prlimit64(pid, resource, new, old) + GETRANDOM = 318 # getrandom(buf, len, flags) + STATX = 332 # statx(...) - -ENOSYS, glibc falls back + RSEQ = 334 # rseq(rseq, len, flags) - -ENOSYS + # ---- 临时私有段(0x1000+):Linux 无对应,逐条迁出 ---- + LOAD_SO = 0x1006 # TODO(userspace): dynamic library loading + SO_CALL1 = 0x1007 # TODO(userspace): dynamic library call + REBOOT = 0x1008 # TODO(linux): reboot(169) + SHUTDOWN = 0x1009 # TODO(linux): reboot(RB_POWER_OFF) + SPAWN_PROC = 0x100A # TODO(userspace): Terminal run helper + THREAD_CREATE_OLD = 0x100B # TODO(userspace): ViperOS thread_create +# END GENERATED SYSCALL TABLE define IA32_EFER = 0xC0000080 define IA32_STAR = 0xC0000081 @@ -110,6 +193,8 @@ def _fd_alloc(p: process.Process*) -> int: for i in range(process.PROC_MAX_FDS): if not p.fd_used[i]: p.fd_used[i] = 1 + p.fd_flags[i] = 0 + p.fd_shared[i] = 0 return i return -1 @@ -117,6 +202,20 @@ def _fd_free(p: process.Process*, fd: int): if fd >= 0 and fd < process.PROC_MAX_FDS: p.fd_used[fd] = None p.fd_table[fd] = None + p.fd_kind[fd] = 0 + p.fd_flags[fd] = 0 + p.fd_shared[fd] = 0 + +# dup()/dup2() share the same underlying object; the last close wins. +def _fd_other_ref(p: process.Process*, fd: int) -> int: + fp: uint64 = uint64(p.fd_table[fd]) + k: uint8 = p.fd_kind[fd] + for i in range(process.PROC_MAX_FDS): + if i == fd: continue + if not p.fd_used[i]: continue + if p.fd_kind[i] != k: continue + if uint64(p.fd_table[i]) == fp: return 1 + return 0 def _dir_alloc(p: process.Process*) -> int: for i in range(process.PROC_MAX_DIRS): @@ -130,20 +229,128 @@ def _dir_free(p: process.Process*, dd: int): p.dir_used[dd] = None p.dir_table[dd] = None +# ============================================================================ +# Linux ABI helpers +# ============================================================================ + +# All handlers return int: non-negative on success, -errno on failure. +def _err(e: int) -> int: + return 0 - e + +def _fr_errno(r: int) -> int: + """Translate a FAT32 FRESULT into -errno (0 stays 0).""" + if r == 0: return 0 + if r == int(fat32_types.FRESULT.FR_NO_FILE) or r == int(fat32_types.FRESULT.FR_NO_PATH): + return _err(abi.ENOENT) + if r == int(fat32_types.FRESULT.FR_INVALID_NAME): + return _err(abi.EINVAL) + if r == int(fat32_types.FRESULT.FR_DENIED): + return _err(abi.EACCES) + if r == int(fat32_types.FRESULT.FR_EXIST): + return _err(abi.EEXIST) + if r == int(fat32_types.FRESULT.FR_INVALID_OBJECT): + return _err(abi.EBADF) + if r == int(fat32_types.FRESULT.FR_WRITE_PROTECTED): + return _err(abi.EROFS) + if r == int(fat32_types.FRESULT.FR_NOT_ENOUGH_CORE): + return _err(abi.ENOMEM) + if r == int(fat32_types.FRESULT.FR_TOO_MANY_OPEN_FILES): + return _err(abi.EMFILE) + if r == int(fat32_types.FRESULT.FR_INVALID_PARAMETER): + return _err(abi.EINVAL) + if r == int(fat32_types.FRESULT.FR_TIMEOUT): + return _err(abi.EIO) + if r == int(fat32_types.FRESULT.FR_LOCKED): + return _err(abi.EBUSY) + return _err(abi.EIO) + +def _open_mode(flags: uint32) -> uint8: + """Linux O_* -> FatFs FA_* access/create mode.""" + m: int = 0 + acc: uint32 = flags & uint32(abi.O_ACCMODE) + if acc == uint32(abi.O_RDONLY): + m = int(fat32_types.FA_READ) + elif acc == uint32(abi.O_WRONLY): + m = int(fat32_types.FA_WRITE) + else: + m = int(fat32_types.FA_READ) | int(fat32_types.FA_WRITE) + if (flags & uint32(abi.O_EXCL)) != 0 and (flags & uint32(abi.O_CREAT)) != 0: + m = m | int(fat32_types.FA_CREATE_NEW) + elif (flags & uint32(abi.O_TRUNC)) != 0: + m = m | int(fat32_types.FA_CREATE_ALWAYS) + elif (flags & uint32(abi.O_CREAT)) != 0: + m = m | int(fat32_types.FA_OPEN_ALWAYS) + elif (flags & uint32(abi.O_APPEND)) != 0: + m = m | int(fat32_types.FA_OPEN_APPEND) + return uint8(m) + +# Resolve `path` against `cwd` (dirfd == AT_FDCWD) or require an absolute path. +# Directory-relative lookups from a dir fd are added with openat (P1). +def _resolve_path(dirfd: int, path: const str, out: char*) -> int: + if path is None: return _err(abi.EFAULT) + n: int = 0 + if path[0] == 47: + while path[n] != 0: + if n >= abi.PATH_MAX - 1: return _err(abi.ENAMETOOLONG) + out[n] = path[n] + n += 1 + out[n] = 0 + return 0 + if dirfd != abi.AT_FDCWD: + return _err(abi.ENOTSUP) + p: process.Process* = _current_proc() + c: int = 0 + while p.cwd[c] != 0: + if n >= abi.PATH_MAX - 1: return _err(abi.ENAMETOOLONG) + out[n] = p.cwd[c] + n += 1 + c += 1 + if n > 0 and out[n - 1] != 47: + out[n] = 47 + n += 1 + i: int = 0 + while path[i] != 0: + if n >= abi.PATH_MAX - 1: return _err(abi.ENAMETOOLONG) + out[n] = path[i] + n += 1 + i += 1 + out[n] = 0 + return 0 + +# Fill the common fields of a Linux struct stat. Times stay 0: FAT32 stores +# DOS timestamps, which are not converted yet (P1 with newfstatat). +def _fill_stat(st: abi.linux_stat*, size: uint64, mode: uint32, ino: uint64): + string.memset(rawptr(st), 0, uint64(abi.SIZEOF_LINUX_STAT)) + st.st_ino = ino + st.st_nlink = 1 + st.st_mode = mode + st.st_size = size + st.st_blksize = 512 + st.st_blocks = (size + 511) / 512 + +# Software DRNG for getrandom(): no hardware entropy requirement, only used +# for stack canaries and similar non-cryptographic purposes (see docs). +_rng_state: uint64 = 0x9E3779B97F4A7C15 + +def _rng_next() -> uint64: + global _rng_state + # 64-bit LCG; the high bits are returned because low LCG bits are weak. + _rng_state = _rng_state * uint64(6364136223846793005) + uint64(1442695040888963407) + return _rng_state >> 16 + def EXIT(code: int) -> int: serial.puts("[syscall] exit called\n") + p: process.Process* = _current_proc() + p.exit_code = code # S3e:不再在这里拆窗口 —— 窗口/输入已整体交给用户态图形壳 explorer(/dev/desk 共享区), # 内核不再持有任何窗口语义。 - # 真正的退出:退役本线程并让出,**绝不返回用户态**。 + # Linux exit_group:退役本进程的**所有**线程,绝不返回用户态。 + # (THREAD_EXIT=60 只退当前线程。) # 只 destroy 窗口不够:app 的 _start 在 process.exit(0) 之后还有 `return 0`,而内核是以 - # rsp = USER_STACK_VTOP 进入 _start 的(elf.vp:379),VTOP 正是用户栈映射 - # [VTOP-64K, VTOP) 的上界之外那一字节 ⇒ 一旦 _start 收尾 `ret` 就会去读 [VTOP]: + # rsp = USER_STACK_VTOP 进入 _start 的(elf.vp),一旦收尾 `ret` 就会去读 [VTOP]: # #PF cr2=rsp=0x7fffffe00000,rip 在 _start 内,rbp=0,rax=退出码(实测现场)。 - # 口径与异常终止路径一致(idt.vp 的 free_user_thread + state=THREAD_DONE + 不再回用户态)。 - sched.Scheduler.exit_current_thread() - # 【硬保护】这里绝不允许返回:一旦返回就会沿调用链回到 app 的 _start 末尾,那里 `ret` - # 会弹到一个 0 返回地址并去 0 取指(现场:[0E] rip=0x0 err=0x15 = 用户态取指缺页, - # 紧随 [syscall] exit called,tid=5)。停留在此让调度器彻底不再调度本线程。 + sched.Scheduler.exit_process(p.pid) + # 【硬保护】exit_process 不返回;这里仅作兜底。 while True: sched.Scheduler._yield() return 0 @@ -185,19 +392,19 @@ def SO_CALL1(sym_name_ptr: str, arg1: rawptr) -> int: serial.puts("[so] call returned\n") return 0 -def GETPID() -> uint64: - """getpid():当前进程 pid。/dev/desk 的事件按 pid 过滤,app 需要它。""" - return uint64(process.current()) - -def _is_dev_path(path: const str) -> int: - """路径是否以 "/dev/" 开头(走 devfs 设备节点而不是 FAT32)。逐字符比较,不引 str 库。""" - if path[0] != 47: return 0 # '/' - if path[1] != 100: return 0 # 'd' - if path[2] != 101: return 0 # 'e' - if path[3] != 118: return 0 # 'v' - if path[4] != 47: return 0 # '/' - return 1 - +def GETPID() -> uint64: + """getpid():当前进程 pid。/dev/desk 的事件按 pid 过滤,app 需要它。""" + return uint64(process.current()) + +def _is_dev_path(path: const str) -> int: + """路径是否以 "/dev/" 开头(走 devfs 设备节点而不是 FAT32)。逐字符比较,不引 str 库。""" + if path[0] != 47: return 0 # '/' + if path[1] != 100: return 0 # 'd' + if path[2] != 101: return 0 # 'e' + if path[3] != 118: return 0 # 'v' + if path[4] != 47: return 0 # '/' + return 1 + def OPEN(path_ptr: str, mode: uint32) -> int: p: process.Process* = _current_proc() path: const str = path_ptr @@ -206,56 +413,89 @@ def OPEN(path_ptr: str, mode: uint32) -> int: if _is_dev_path(path): dfd: int = devfs.devfs_open(path, int(mode)) if dfd < 0: - return -1 + return _err(abi.ENOENT) fd: int = _fd_alloc(p) if fd < 0: devfs.devfs_close(dfd) - return -1 + return _err(abi.EMFILE) # 存 devfs_fd+1:devfs fd 0 是合法值,直接用会与"空槽位"混淆 p.fd_table[fd] = rawptr(uint64(dfd + 1)) p.fd_kind[fd] = process.FD_KIND_DEVFS + p.fd_flags[fd] = mode return fd fat32.lock() - fp: rawptr = fat32.open(path, uint8(mode)) + fp: rawptr = fat32.open(path, _open_mode(mode)) if not fp: fat32.unlock() - return -1 + return _err(abi.ENOENT) fd: int = _fd_alloc(p) if fd < 0: fat32.close(fp) fat32.unlock() - return -1 + return _err(abi.EMFILE) p.fd_table[fd] = fp p.fd_kind[fd] = process.FD_KIND_FAT32 + p.fd_flags[fd] = mode fat32.unlock() return fd def CLOSE(fd: int) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: - return int(fat32_types.FRESULT.FR_INVALID_OBJECT) + return _err(abi.EBADF) fp: rawptr = p.fd_table[fd] if not fp: - return int(fat32_types.FRESULT.FR_INVALID_OBJECT) + return _err(abi.EBADF) + # Pipes carry their own reference counts: every inherited/dup'd end was + # counted, so always decrement (fork's shared flag must not skip this). + if p.fd_kind[fd] == process.FD_KIND_PIPE: + res_pipe: int = _pipe_close(fp) + _fd_free(p, fd) + return res_pipe + # fork() copies the fd table; shared entries must not close the + # underlying object (the parent still owns it). + if p.fd_shared[fd] != 0: + _fd_free(p, fd) + return 0 + # dup()/dup2() aliases: only the last close releases the object. + if _fd_other_ref(p, fd) != 0: + _fd_free(p, fd) + return 0 if p.fd_kind[fd] == process.FD_KIND_DEVFS: res_dev: int = devfs.devfs_close(int(uint64(fp)) - 1) _fd_free(p, fd) - return res_dev + if res_dev < 0: return _err(abi.EIO) + return 0 + if p.fd_kind[fd] == process.FD_KIND_DIR: + dd: int = int(uint64(fp)) - 1 + if dd >= 0 and dd < process.PROC_MAX_DIRS: + fat32.lock() + fat32.closedir(&p.dir_pool[dd]) + fat32.unlock() + _dir_free(p, dd) + _fd_free(p, fd) + return 0 fat32.lock() res: int = int(fat32.close(fp)) fat32.unlock() _fd_free(p, fd) - return res + return _fr_errno(res) def READ(fd: int, buf_ptr: rawptr, count: uint32) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: - return -1 + return _err(abi.EBADF) fp: rawptr = p.fd_table[fd] if not fp: - return -1 + return _err(abi.EBADF) + if p.fd_kind[fd] == process.FD_KIND_DIR: + return _err(abi.EISDIR) + if p.fd_kind[fd] == process.FD_KIND_PIPE: + return _pipe_read(fp, buf_ptr, count) if p.fd_kind[fd] == process.FD_KIND_DEVFS: - return devfs.devfs_read(int(uint64(fp)) - 1, buf_ptr, count) + r: int = devfs.devfs_read(int(uint64(fp)) - 1, buf_ptr, count) + if r < 0: return _err(abi.EIO) + return r fat32.lock() br: uint32 = fat32.read(fp, buf_ptr, count) fat32.unlock() @@ -264,12 +504,18 @@ def READ(fd: int, buf_ptr: rawptr, count: uint32) -> int: def WRITE(fd: int, buf_ptr: rawptr, count: uint32) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: - return -1 + return _err(abi.EBADF) fp: rawptr = p.fd_table[fd] if not fp: - return -1 + return _err(abi.EBADF) + if p.fd_kind[fd] == process.FD_KIND_DIR: + return _err(abi.EISDIR) + if p.fd_kind[fd] == process.FD_KIND_PIPE: + return _pipe_write(fp, buf_ptr, count) if p.fd_kind[fd] == process.FD_KIND_DEVFS: - return devfs.devfs_write(int(uint64(fp)) - 1, buf_ptr, count) + r: int = devfs.devfs_write(int(uint64(fp)) - 1, buf_ptr, count) + if r < 0: return _err(abi.EIO) + return r fat32.lock() bw: uint32 = fat32.write(fp, buf_ptr, count) fat32.unlock() @@ -279,106 +525,302 @@ def IOCTL(fd: int, request: uint32, arg: rawptr) -> int: """设备控制(Linux ioctl 的子集):当前只有 devfs 设备支持,用于 /dev/fb0 查几何。""" p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: - return -1 + return _err(abi.EBADF) if not p.fd_used[fd]: - return -1 + return _err(abi.EBADF) if p.fd_kind[fd] != process.FD_KIND_DEVFS: - return -1 - return devfs.devfs_ioctl(int(uint64(p.fd_table[fd])) - 1, request, arg) + return _err(abi.ENOTTY) + r: int = devfs.devfs_ioctl(int(uint64(p.fd_table[fd])) - 1, request, arg) + if r < 0: return _err(abi.EINVAL) + return r -def SEEK(fd: int, offset: uint32, whence: uint32) -> int: - """lseek(fd, offset, whence):0=SET 1=CUR 2=END(END 时 offset 为"距末尾的距离")。 +def SEEK(fd: int, offset: uint64, whence: uint32) -> int: + """lseek(fd, offset, whence) -> new offset, or -errno. - 私有 syscall TELL/SIZE 已删除:用户态用 lseek(fd,0,SEEK_CUR/SEEK_END) 表达即可。 + offset is 64-bit on the ABI side; FAT32 caps files at 4 GiB so anything + beyond that range is rejected with -EINVAL. Whence: 0=SET 1=CUR 2=END. """ p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: - return int(fat32_types.FRESULT.FR_INVALID_OBJECT) + return _err(abi.EBADF) fp: rawptr = p.fd_table[fd] if not fp: - return int(fat32_types.FRESULT.FR_INVALID_OBJECT) + return _err(abi.EBADF) + if p.fd_kind[fd] == process.FD_KIND_DIR: + return _err(abi.EINVAL) + if p.fd_kind[fd] == process.FD_KIND_PIPE: + return _err(abi.ESPIPE) + if p.fd_kind[fd] == process.FD_KIND_DEVFS: + return _err(abi.ESPIPE) fat32.lock() - r: int = -1 - if whence == uint32(0): - r = int(fat32.seek(fp, offset)) - elif whence == uint32(1): - r = int(fat32.seek(fp, fat32.tell(fp) + offset)) - elif whence == uint32(2): - sz: uint32 = fat32.size(fp) - if offset <= sz: - r = int(fat32.seek(fp, sz - offset)) + base: int = 0 + if whence == uint32(abi.SEEK_CUR): + base = int(fat32.tell(fp)) + elif whence == uint32(abi.SEEK_END): + base = int(fat32.size(fp)) + elif whence != uint32(abi.SEEK_SET): + fat32.unlock() + return _err(abi.EINVAL) + tgt: int = base + int(offset) + if tgt < 0 or tgt > 0x7FFFFFFF: + fat32.unlock() + return _err(abi.EINVAL) + r: int = int(fat32.seek(fp, uint32(tgt))) fat32.unlock() - return r + if r != 0: return _fr_errno(r) + return tgt def MKDIR(path_ptr: str) -> int: path: const str = path_ptr fat32.lock() r: int = int(fat32.mkdir(path)) fat32.unlock() - return r + return _fr_errno(r) def REMOVE(path_ptr: str) -> int: path: const str = path_ptr fat32.lock() r: int = int(fat32.remove(path)) fat32.unlock() - return r + return _fr_errno(r) -def OPENdir(path_ptr: str) -> int: +# ---------------- P1: file system ABI ---------------- + +def _open_dir(path: const str) -> int: p: process.Process* = _current_proc() - path: const str = path_ptr dd: int = _dir_alloc(p) - if dd < 0: - return -1 + if dd < 0: return _err(abi.EMFILE) dp: fat32_types.dirobj* = &p.dir_pool[dd] fat32.lock() res: int = int(fat32.opendir(path, dp)) fat32.unlock() if res != 0: _dir_free(p, dd) - return -1 + return _fr_errno(res) p.dir_table[dd] = rawptr(uint64(dp)) - return dd + fd: int = _fd_alloc(p) + if fd < 0: + fat32.lock() + fat32.closedir(dp) + fat32.unlock() + _dir_free(p, dd) + return _err(abi.EMFILE) + p.fd_table[fd] = rawptr(uint64(dd + 1)) + p.fd_kind[fd] = process.FD_KIND_DIR + return fd -def READdir(dd: int, info_ptr: rawptr) -> int: - p: process.Process* = _current_proc() - if dd < 0 or dd >= process.PROC_MAX_DIRS: - return int(fat32_types.FRESULT.FR_INVALID_OBJECT) - dp: rawptr = p.dir_table[dd] - if not dp: - return int(fat32_types.FRESULT.FR_INVALID_OBJECT) - info: fat32_types.fileinfo* = info_ptr - fat32.lock() - r: int = int(fat32.readdir(dp, info)) - fat32.unlock() - return r +def OPENAT(dirfd: int, path_ptr: str, flags: uint32, mode: uint32) -> int: + path: const str = path_ptr + buf: [256]char + r: int = _resolve_path(dirfd, path, &buf[0]) + if r != 0: return r + if (flags & uint32(abi.O_DIRECTORY)) != 0: + return _open_dir(&buf[0]) + return OPEN(&buf[0], flags) -def CLOSEdir(dd: int) -> int: +def GETDENTS64(fd: int, buf_ptr: rawptr, count: uint64) -> int: p: process.Process* = _current_proc() - if dd < 0 or dd >= process.PROC_MAX_DIRS: - return int(fat32_types.FRESULT.FR_INVALID_OBJECT) - dp: rawptr = p.dir_table[dd] - if not dp: - return int(fat32_types.FRESULT.FR_INVALID_OBJECT) + if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) + if not p.fd_used[fd]: return _err(abi.EBADF) + if p.fd_kind[fd] != process.FD_KIND_DIR: return _err(abi.ENOTDIR) + dd: int = int(uint64(p.fd_table[fd])) - 1 + if dd < 0 or dd >= process.PROC_MAX_DIRS: return _err(abi.EBADF) + dp: fat32_types.dirobj* = &p.dir_pool[dd] + info: fat32_types.fileinfo fat32.lock() - fat32.closedir(dp) + r: int = int(fat32.readdir(dp, &info)) fat32.unlock() - _dir_free(p, dd) + if r != 0: + if r == int(fat32_types.FRESULT.FR_NO_FILE): return 0 + return _fr_errno(r) + name: str = &info.fname[0] + n: int = int(string.strlen(name)) + reclen: int = (19 + n + 1 + 7) / 8 * 8 + if uint64(reclen) > count: return _err(abi.EINVAL) + d: abi.linux_dirent64* = buf_ptr + d.d_ino = uint64(1) + d.d_off = uint64(0) + d.d_reclen = uint16(reclen) + if (int(info.attr) & 0x10) != 0: + d.d_type = uint8(abi.DT_DIR) + else: + d.d_type = uint8(abi.DT_REG) + dst: char* = char*(uint64(buf_ptr) + 19) + i: int = 0 + while i < n: + dst[i] = name[i] + i += 1 + dst[n] = 0 + i = n + 1 + while i < reclen - 19: + dst[i] = 0 + i += 1 + return reclen + +def FSTAT(fd: int, st_ptr: rawptr) -> int: + p: process.Process* = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) + if not p.fd_used[fd]: return _err(abi.EBADF) + if st_ptr is None: return _err(abi.EFAULT) + st: abi.linux_stat* = st_ptr + if p.fd_kind[fd] == process.FD_KIND_DEVFS: + _fill_stat(st, uint64(0), uint32(abi.MODE_CHAR), uint64(fd + 1)) + elif p.fd_kind[fd] == process.FD_KIND_DIR: + _fill_stat(st, uint64(0), uint32(abi.MODE_DIR), uint64(fd + 1)) + elif p.fd_kind[fd] == process.FD_KIND_PIPE: + _fill_stat(st, uint64(0), uint32(abi.MODE_FIFO), uint64(fd + 1)) + else: + fp: rawptr = p.fd_table[fd] + size: uint64 = uint64(fat32.size(fp)) + _fill_stat(st, size, uint32(abi.MODE_FILE), uint64(fd + 1)) return 0 +def NEWFSTATAT(dirfd: int, path_ptr: str, st_ptr: rawptr, flags: uint32) -> int: + if st_ptr is None: return _err(abi.EFAULT) + path: const str = path_ptr + if path is None: return _err(abi.EFAULT) + if path[0] == 0: + if (flags & uint32(abi.AT_EMPTY_PATH)) != 0: + return FSTAT(dirfd, st_ptr) + return _err(abi.ENOENT) + st: abi.linux_stat* = st_ptr + buf: [256]char + r: int = _resolve_path(dirfd, path, &buf[0]) + if r != 0: return r + if _is_dev_path(&buf[0]): + _fill_stat(st, uint64(0), uint32(abi.MODE_CHAR), uint64(1)) + return 0 + if buf[0] == 47 and buf[1] == 0: + _fill_stat(st, uint64(0), uint32(abi.MODE_DIR), uint64(1)) + return 0 + info: fat32_types.fileinfo + fat32.lock() + res: int = int(fat32.stat(&buf[0], &info)) + fat32.unlock() + if res != 0: return _fr_errno(res) + if (int(info.attr) & 0x10) != 0: + _fill_stat(st, uint64(0), uint32(abi.MODE_DIR), uint64(1)) + else: + _fill_stat(st, uint64(info.file_size), uint32(abi.MODE_FILE), uint64(1)) + return 0 + +def DUP(fd: int) -> int: + p: process.Process* = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) + if not p.fd_used[fd]: return _err(abi.EBADF) + nfd: int = _fd_alloc(p) + if nfd < 0: return _err(abi.EMFILE) + p.fd_table[nfd] = p.fd_table[fd] + p.fd_kind[nfd] = p.fd_kind[fd] + p.fd_flags[nfd] = p.fd_flags[fd] + p.fd_shared[nfd] = p.fd_shared[fd] + if p.fd_kind[nfd] == process.FD_KIND_PIPE: + _pipe_add_ref(p.fd_table[nfd]) + return nfd + +def DUP2(oldfd: int, newfd: int) -> int: + p: process.Process* = _current_proc() + if oldfd < 0 or oldfd >= process.PROC_MAX_FDS: return _err(abi.EBADF) + if not p.fd_used[oldfd]: return _err(abi.EBADF) + if newfd < 0 or newfd >= process.PROC_MAX_FDS: return _err(abi.EBADF) + if oldfd == newfd: return newfd + if p.fd_used[newfd]: + r: int = CLOSE(newfd) + if r != 0: return r + p.fd_used[newfd] = 1 + p.fd_table[newfd] = p.fd_table[oldfd] + p.fd_kind[newfd] = p.fd_kind[oldfd] + p.fd_flags[newfd] = p.fd_flags[oldfd] + p.fd_shared[newfd] = p.fd_shared[oldfd] + if p.fd_kind[newfd] == process.FD_KIND_PIPE: + _pipe_add_ref(p.fd_table[newfd]) + return newfd + +def DUP3(oldfd: int, newfd: int, flags: uint32) -> int: + return DUP2(oldfd, newfd) + +def FCNTL(fd: int, cmd: uint32, arg: uint64) -> int: + p: process.Process* = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) + if not p.fd_used[fd]: return _err(abi.EBADF) + c: int = int(cmd) + if c == abi.F_DUPFD or c == abi.F_DUPFD_CLOEXEC: + return DUP(fd) + if c == abi.F_GETFD: + return 0 + if c == abi.F_SETFD: + return 0 + if c == abi.F_GETFL: + return int(p.fd_flags[fd]) + if c == abi.F_SETFL: + p.fd_flags[fd] = uint32(arg) + return 0 + return _err(abi.EINVAL) + +def PREAD64(fd: int, buf_ptr: rawptr, count: uint32, offset: uint64) -> int: + p: process.Process* = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) + if not p.fd_used[fd]: return _err(abi.EBADF) + if p.fd_kind[fd] != process.FD_KIND_FAT32: return _err(abi.ESPIPE) + cur: int = SEEK(fd, uint64(0), uint32(abi.SEEK_CUR)) + if cur < 0: return cur + r: int = SEEK(fd, offset, uint32(abi.SEEK_SET)) + if r < 0: return r + n: int = READ(fd, buf_ptr, count) + SEEK(fd, uint64(cur), uint32(abi.SEEK_SET)) + return n + +def PWRITE64(fd: int, buf_ptr: rawptr, count: uint32, offset: uint64) -> int: + p: process.Process* = _current_proc() + if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) + if not p.fd_used[fd]: return _err(abi.EBADF) + if p.fd_kind[fd] != process.FD_KIND_FAT32: return _err(abi.ESPIPE) + cur: int = SEEK(fd, uint64(0), uint32(abi.SEEK_CUR)) + if cur < 0: return cur + r: int = SEEK(fd, offset, uint32(abi.SEEK_SET)) + if r < 0: return r + n: int = WRITE(fd, buf_ptr, count) + SEEK(fd, uint64(cur), uint32(abi.SEEK_SET)) + return n + +def RENAME(old_ptr: str, new_ptr: str) -> int: + oldp: const str = old_ptr + newp: const str = new_ptr + fat32.lock() + r: int = int(fat32.rename(oldp, newp)) + fat32.unlock() + return _fr_errno(r) + +def RMDIR(path_ptr: str) -> int: + path: const str = path_ptr + fat32.lock() + r: int = int(fat32.rmdir(path)) + fat32.unlock() + return _fr_errno(r) + +def UNLINKAT(dirfd: int, path_ptr: str, flags: uint32) -> int: + path: const str = path_ptr + buf: [256]char + r: int = _resolve_path(dirfd, path, &buf[0]) + if r != 0: return r + if (flags & uint32(abi.AT_REMOVEDIR)) != 0: + return RMDIR(&buf[0]) + return REMOVE(&buf[0]) + def STAT(path_ptr: str, info_ptr: rawptr) -> int: path: const str = path_ptr info: fat32_types.fileinfo* = info_ptr fat32.lock() r: int = int(fat32.stat(path, info)) fat32.unlock() - return r + return _fr_errno(r) -def SPAWN(path_ptr: str, blocking: int) -> int: +# Private launcher kept for Terminal's `run` command (a new child, caller stays alive). +def SPAWN_PROC(path_ptr: str, blocking: int) -> int: path: const str = path_ptr pid: int = elf.spawn_elf(path) if pid < 0: - return pid + return _err(abi.ENOENT) if blocking: p: process.Process* = process.ProcessManager.get_process(pid) if p: @@ -386,65 +828,1138 @@ def SPAWN(path_ptr: str, blocking: int) -> int: sched.Scheduler.try_reschedule() return pid -define USER_HEAP_BASE = 0x20000000 +def WAIT4(pid: int, status_ptr: rawptr, options: uint32, rusage: rawptr) -> int: + cur: int = process.current() + while True: + found_child: int = 0 + i: int = 0 + while i < process.MAX_PROCESSES: + p: process.Process* = process.ProcessManager.get_process(i) + if p is not None: + matches: int = 0 + if pid <= 0: + matches = 1 # any child (pid 0: same group, approximated) + elif p.pid == pid: + matches = 1 + if matches != 0 and p.pid != cur and p.parent_pid == cur: + found_child = 1 + if p.state == process.PROC_DONE: + if status_ptr is not None: + st: int* = int*(status_ptr) + st[0] = (p.exit_code & 0xFF) << 8 + p.parent_pid = -1 + return p.pid + i += 1 + if found_child == 0: + return _err(abi.ECHILD) + if (options & uint32(1)) != 0: # WNOHANG + return 0 + sched.Scheduler.yield_now() + +# mmap bump allocator and brk base must not overlap (both live below the stack). +define USER_HEAP_BASE = 0x20000000 +define USER_BRK_BASE = 0x40000000 +define USER_VA_LIMIT = 0x7FFFFF000000 -# ---- mmap 的 prot / flags(值与 Linux 同名常量一致)---- -define PROT_NONE = 0x0 -define PROT_READ = 0x1 -define PROT_WRITE = 0x2 -define PROT_EXEC = 0x4 -define MAP_SHARED = 0x01 -define MAP_PRIVATE = 0x02 -define MAP_FIXED = 0x10 -define MAP_ANONYMOUS = 0x20 # fd = -1(Linux 匿名映射约定) define MMAP_FD_ANON: uint64 = 0xFFFFFFFFFFFFFFFF -def MMAP(addr: uint64, length: uint64, prot: uint64, flags: uint64, fd: uint64, offset: uint64) -> uint64: - """mmap(addr, length, prot, flags, fd, offset):与 Linux 同名同参。 +# ---- VMA bookkeeping ------------------------------------------------------- - · fd == MMAP_FD_ANON(-1) 且带 MAP_ANONYMOUS:匿名映射 —— 分配物理页、清零后映射到 - 本进程新 VA(对齐 Linux 的"匿名映射必为 0"语义); - · fd 是带 mmap_info 的 devfs 设备 fd(当前仅 /dev/fb0):把设备物理区间 - [phys+offset, phys+offset+length) 按页映射进来(用户可读写)。 - addr 与 MAP_FIXED 暂未实现:一律由内核挑 VA,返回真实起始地址。失败返回 0。 +def _vma_find(p: process.Process*, addr: uint64) -> int: + for i in range(process.MAX_VMAS): + if p.vma_kind[i] == 0: continue + b: uint64 = p.vma_base[i] + if addr >= b and addr < b + p.vma_size[i]: return i + return -1 + +def _vma_overlap(p: process.Process*, start: uint64, size: uint64) -> int: + if size == 0: return 0 + end: uint64 = start + size + for i in range(process.MAX_VMAS): + if p.vma_kind[i] == 0: continue + b: uint64 = p.vma_base[i] + e: uint64 = b + p.vma_size[i] + if start < e and b < end: return 1 + return 0 + +def _vma_add(p: process.Process*, base: uint64, size: uint64, prot: uint32, kind: int, flags: uint32) -> int: + for i in range(process.MAX_VMAS): + if p.vma_kind[i] == 0: + p.vma_base[i] = base + p.vma_size[i] = size + p.vma_prot[i] = prot + p.vma_kind[i] = uint8(kind) + p.vma_flags[i] = flags + return 0 + return _err(abi.ENOMEM) + +def _vma_del(p: process.Process*, i: int): + p.vma_base[i] = 0 + p.vma_size[i] = 0 + p.vma_prot[i] = 0 + p.vma_kind[i] = 0 + p.vma_flags[i] = 0 + +def _unmap_pages(p: process.Process*, start: uint64, end: uint64, free_pages: int): + va: uint64 = start + while va < end: + if free_pages != 0: + phys: uint64 = paging.GetPagePhys(va) + if phys != 0: + mm.free_page(rawptr(phys)) + paging.UnMapPage(va) + va = va + uint64(4096) + +# Remove [addr, addr+length) from the VMA table, splitting partially covered +# areas, and release the backing frames of anon/file mappings. +def _vma_unmap_range(p: process.Process*, addr: uint64, length: uint64) -> int: + if length == 0: return 0 + end: uint64 = addr + length + i: int = 0 + while i < process.MAX_VMAS: + if p.vma_kind[i] != 0: + b: uint64 = p.vma_base[i] + e: uint64 = b + p.vma_size[i] + if b < end and addr < e: + s: uint64 = addr + if b > s: s = b + t: uint64 = end + if e < t: t = e + free_pages: int = 1 + if p.vma_kind[i] == uint8(process.VMA_KIND_DEV): free_pages = 0 + _unmap_pages(p, s, t, free_pages) + if s <= b and t >= e: + _vma_del(p, i) + elif s <= b: + p.vma_base[i] = t + p.vma_size[i] = e - t + elif t >= e: + p.vma_size[i] = s - b + else: + r: int = _vma_add(p, t, e - t, uint32(p.vma_prot[i]), + int(p.vma_kind[i]), uint32(p.vma_flags[i])) + if r != 0: return r + p.vma_size[i] = s - b + i += 1 + return 0 + +def MMAP(addr: uint64, length: uint64, prot: uint64, flags: uint64, fd: uint64, offset: uint64) -> uint64: + """mmap(addr, length, prot, flags, fd, offset) -> base, or MAP_FAILED (-1). + + · MAP_ANONYMOUS: fresh zeroed pages; + · FAT32 fd: file contents copied into fresh pages (MAP_PRIVATE semantics); + · devfs fd: the device physical range is mapped directly (no frame owns it). """ p: process.Process* = _current_proc() - if length == 0: return 0 - aligned: uint64 = (length + 4095) & ~uint64(4095) - if p.heap_start == 0: - p.heap_start = USER_HEAP_BASE - va: uint64 = p.heap_start + p.heap_size - if fd == MMAP_FD_ANON: - if (flags & MAP_ANONYMOUS) == 0: return 0 - for off in range(0, aligned, 4096): - phys_page: rawptr = mm.malloc(4096) - if phys_page is None: return 0 - string.memset(phys_page, 0, 4096) - paging.MapPage(va + off, uint64(phys_page), - paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) + if length == 0: return abi.MAP_FAILED + aligned: uint64 = (length + uint64(0xFFF)) & ~uint64(0xFFF) + if aligned == 0: return abi.MAP_FAILED + fixed: int = 0 + if (flags & uint64(abi.MAP_FIXED)) != 0: fixed = 1 + base: uint64 = 0 + if fixed != 0: + if (addr & uint64(0xFFF)) != 0: return abi.MAP_FAILED + base = addr + if base < uint64(0x10000) or base + aligned > uint64(USER_VA_LIMIT): + return abi.MAP_FAILED + if p.brk_end > p.brk_start and base < p.brk_end and p.brk_start < base + aligned: + return abi.MAP_FAILED + else: + if p.heap_start == 0: p.heap_start = USER_HEAP_BASE + base = p.heap_start + p.heap_size + while True: + brk_hit: int = 0 + if p.brk_end > p.brk_start and base < p.brk_end and p.brk_start < base + aligned: + brk_hit = 1 + if _vma_overlap(p, base, aligned) == 0 and brk_hit == 0: break + p.heap_size = p.heap_size + aligned + base = p.heap_start + p.heap_size + if base + aligned > uint64(USER_VA_LIMIT): return abi.MAP_FAILED + if base + aligned > uint64(USER_VA_LIMIT): return abi.MAP_FAILED + # classify the mapping source + kind: int = process.VMA_KIND_ANON + uncached: int = 0 + dev_phys: uint64 = 0 + dev_size: uint64 = 0 + fp: rawptr = None + if (flags & uint64(abi.MAP_ANONYMOUS)) != 0: + kind = process.VMA_KIND_ANON + elif fd == MMAP_FD_ANON: + return abi.MAP_FAILED else: fdi: int = int(fd) - if fdi < 0 or fdi >= process.PROC_MAX_FDS: return 0 - if not p.fd_used[fdi]: return 0 - if p.fd_kind[fdi] != process.FD_KIND_DEVFS: return 0 - dev_phys: uint64 = 0 - dev_size: uint64 = 0 - if devfs.devfs_get_mmap_info(int(uint64(p.fd_table[fdi])) - 1, &dev_phys, &dev_size) != 0: - return 0 - if offset + aligned > dev_size: return 0 - for off in range(0, aligned, 4096): - paging.MapPage(va + off, dev_phys + offset + off, - paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) - p.heap_size = p.heap_size + aligned - return va + if fdi < 0 or fdi >= process.PROC_MAX_FDS: return abi.MAP_FAILED + if not p.fd_used[fdi]: return abi.MAP_FAILED + if p.fd_kind[fdi] == process.FD_KIND_DEVFS: + kind = process.VMA_KIND_DEV + if (offset & uint64(0xFFF)) != 0: return abi.MAP_FAILED + if devfs.devfs_get_mmap_info(int(uint64(p.fd_table[fdi])) - 1, &dev_phys, &dev_size) != 0: + return abi.MAP_FAILED + if devfs.devfs_mmap_uncached(int(uint64(p.fd_table[fdi])) - 1) != 0: + uncached = 1 + if offset + aligned > dev_size: return abi.MAP_FAILED + elif p.fd_kind[fdi] == process.FD_KIND_FAT32: + kind = process.VMA_KIND_FILE + if (offset & uint64(0xFFF)) != 0: return abi.MAP_FAILED + if offset > uint64(0xFFFFFFFF): return abi.MAP_FAILED + fp = p.fd_table[fdi] + else: + return abi.MAP_FAILED + if fixed != 0: + ur: int = _vma_unmap_range(p, base, aligned) + if ur != 0: return abi.MAP_FAILED + pte: uint64 = paging.PTE_PRESENT | paging.PTE_USER + if (prot & uint64(abi.PROT_WRITE)) != 0: + pte = pte | paging.PTE_WRITABLE + if uncached != 0: + # MMIO (framebuffer). Use PAT write-combining when the CPU exposes it; + # otherwise leave the mapping write-back (fast sequential blits, and the + # compositor does not rely on UC for visibility). UC (PTE_PCD) made + # dragging noticeably slower on VMware. + if paging.has_pat_wc() != 0: + pte = pte | paging.PTE_PAT + # 这一段会连续调用 MapPage,而 MapPage 依赖**全局 paging.pml4**。若中途被 + # 定时器抢占并切换进程,paging.pml4 会被 switch_pid 改成别的进程,后续页就 + # 映射进错误的地址空间(本进程留下未映射空洞;用户写空洞时若命中 identity + # 大页就会直接改物理内存 → BAD blk / 画面花屏)。整个映射循环必须原子。 + asm.cli() + va: uint64 = base + if kind == process.VMA_KIND_FILE: + fat32.lock() + oldpos: uint32 = fat32.tell(fp) + fat32.seek(fp, uint32(offset)) + fat32.unlock() + done: uint64 = 0 + while va < base + aligned: + fphys: rawptr = mm.alloc_page() + if fphys is None: + fat32.lock() + fat32.seek(fp, oldpos) + fat32.unlock() + _unmap_pages(p, base, va, 1) + asm.sti() + return abi.MAP_FAILED + string.memset(fphys, 0, 4096) + chunk: uint32 = 4096 + if length - done < uint64(4096): chunk = uint32(length - done) + if chunk > 0: + fat32.lock() + got: int = fat32.read(fp, fphys, chunk) + fat32.unlock() + if got > 0: + done = done + uint64(got) + paging.MapPage(va, uint64(fphys), pte) + va = va + uint64(4096) + fat32.lock() + fat32.seek(fp, oldpos) + fat32.unlock() + else: + while va < base + aligned: + if kind == process.VMA_KIND_DEV: + paging.MapPage(va, dev_phys + offset + (va - base), pte) + else: + aphys: rawptr = mm.alloc_page() + if aphys is None: + _mf: [128]char + viperlib.snprintf(&_mf, 128, "[mmap] FAIL pid=%d va=0x%lx len=0x%lx\n", + p.pid, va, aligned) + serial.puts(&_mf) + _unmap_pages(p, base, va, 1) + asm.sti() + return abi.MAP_FAILED + string.memset(aphys, 0, 4096) + paging.MapPage(va, uint64(aphys), pte) + va = va + uint64(4096) + r: int = _vma_add(p, base, aligned, uint32(prot), kind, uint32(flags)) + if r != 0: + if kind == process.VMA_KIND_DEV: + _unmap_pages(p, base, base + aligned, 0) + else: + _unmap_pages(p, base, base + aligned, 1) + asm.sti() + return abi.MAP_FAILED + if fixed == 0: + p.heap_size = p.heap_size + aligned + asm.sti() + return base def MUNMAP(ptr: uint64, size: uint64) -> int: + if size == 0: return _err(abi.EINVAL) + start: uint64 = ptr & ~uint64(0xFFF) + end: uint64 = (ptr + size + uint64(0xFFF)) & ~uint64(0xFFF) + if end <= start: return _err(abi.EINVAL) + if start < uint64(0x10000) or end > uint64(USER_VA_LIMIT): return _err(abi.EINVAL) + asm.cli() + _mr: int = _vma_unmap_range(_current_proc(), start, end - start) + asm.sti() + return _mr + +def MPROTECT(addr: uint64, length: uint64, prot: uint64) -> int: + p: process.Process* = _current_proc() + if length == 0: return 0 + start: uint64 = addr & ~uint64(0xFFF) + end: uint64 = (addr + length + uint64(0xFFF)) & ~uint64(0xFFF) + if end <= start: return _err(abi.EINVAL) + if start < uint64(0x10000) or end > uint64(USER_VA_LIMIT): return _err(abi.EINVAL) + va: uint64 = start + while va < end: + # Kernel-loaded ELF segments are not in the VMA table; accept any + # present 4KB page so RELRO mprotect() on the main image still works. + if _vma_find(p, va) < 0 and paging.GetPagePhys(va) == 0: + return _err(abi.ENOMEM) + va = va + uint64(4096) + # Snapshot the VMAs that overlap: splitting below adds new entries. + idx: [64]int + n: int = 0 + for i in range(process.MAX_VMAS): + if p.vma_kind[i] == 0: continue + b: uint64 = p.vma_base[i] + e: uint64 = b + p.vma_size[i] + if b < end and start < e: + idx[n] = i + n += 1 + j: int = 0 + while j < n: + i: int = idx[j] + b: uint64 = p.vma_base[i] + e: uint64 = b + p.vma_size[i] + s: uint64 = start + if b > s: s = b + t: uint64 = end + if e < t: t = e + old_prot: uint32 = uint32(p.vma_prot[i]) + kind: int = int(p.vma_kind[i]) + vflags: uint32 = uint32(p.vma_flags[i]) + if s <= b and t >= e: + p.vma_prot[i] = uint32(prot) + elif s <= b: + r: int = _vma_add(p, t, e - t, old_prot, kind, vflags) + if r != 0: return r + p.vma_size[i] = t - b + p.vma_prot[i] = uint32(prot) + elif t >= e: + r = _vma_add(p, s, e - s, uint32(prot), kind, vflags) + if r != 0: return r + p.vma_size[i] = s - b + else: + r = _vma_add(p, s, t - s, uint32(prot), kind, vflags) + if r != 0: return r + r = _vma_add(p, t, e - t, old_prot, kind, vflags) + if r != 0: return r + p.vma_size[i] = s - b + j += 1 + pte: uint64 = paging.PTE_PRESENT | paging.PTE_USER + if (prot & uint64(abi.PROT_WRITE)) != 0: + pte = pte | paging.PTE_WRITABLE + va = start + while va < end: + if paging.SetPageFlags(va, pte) == 0: return _err(abi.ENOMEM) + va = va + uint64(4096) return 0 +def BRK(addr: uint64) -> uint64: + p: process.Process* = _current_proc() + if p.brk_start == 0: + p.brk_start = USER_BRK_BASE + p.brk_end = USER_BRK_BASE + if addr == 0: return p.brk_end + if addr < p.brk_start or addr > uint64(USER_VA_LIMIT): return p.brk_end + old_map: uint64 = (p.brk_end + uint64(0xFFF)) & ~uint64(0xFFF) + new_map: uint64 = (addr + uint64(0xFFF)) & ~uint64(0xFFF) + if new_map > old_map: + asm.cli() + va: uint64 = old_map + while va < new_map: + if _vma_overlap(p, va, uint64(4096)) != 0: + _unmap_pages(p, old_map, va, 1) + asm.sti() + return p.brk_end + bphys: rawptr = mm.alloc_page() + if bphys is None: + _unmap_pages(p, old_map, va, 1) + asm.sti() + return p.brk_end + string.memset(bphys, 0, 4096) + paging.MapPage(va, uint64(bphys), + paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) + va = va + uint64(4096) + asm.sti() + elif new_map < old_map: + _unmap_pages(p, new_map, old_map, 1) + p.brk_end = addr + return p.brk_end + +def MREMAP(old_addr: uint64, old_size: uint64, new_size: uint64, flags: uint64, new_addr: uint64) -> uint64: + p: process.Process* = _current_proc() + if old_size == 0 or new_size == 0: return abi.MAP_FAILED + if (old_addr & uint64(0xFFF)) != 0: return abi.MAP_FAILED + old_len: uint64 = (old_size + uint64(0xFFF)) & ~uint64(0xFFF) + new_len: uint64 = (new_size + uint64(0xFFF)) & ~uint64(0xFFF) + if old_addr < uint64(0x10000) or old_addr + old_len > uint64(USER_VA_LIMIT): + return abi.MAP_FAILED + if _vma_find(p, old_addr) < 0: return abi.MAP_FAILED + if new_len <= old_len: + if new_len < old_len: + _vma_unmap_range(p, old_addr + new_len, old_len - new_len) + return old_addr + if (flags & uint64(abi.MREMAP_MAYMOVE)) == 0: + return abi.MAP_FAILED + mflags: uint64 = uint64(abi.MAP_PRIVATE | abi.MAP_ANONYMOUS) + hint: uint64 = 0 + if (flags & uint64(abi.MREMAP_FIXED)) != 0: + if new_addr < old_addr + old_len and old_addr < new_addr + new_len: + return abi.MAP_FAILED + mflags = mflags | uint64(abi.MAP_FIXED) + hint = new_addr + nv: uint64 = MMAP(hint, new_len, uint64(abi.PROT_READ | abi.PROT_WRITE), + mflags, MMAP_FD_ANON, 0) + if nv == abi.MAP_FAILED: return abi.MAP_FAILED + string.memcpy(rawptr(nv), rawptr(old_addr), old_len) + _vma_unmap_range(p, old_addr, old_len) + return nv + +def MADVISE(addr: uint64, length: uint64, advice: uint32) -> int: + # No paging/swapping policy yet; accept the hint. + return 0 + +# ============================================================================ +# P4/P5/P6: TLS, futex, time, signals, iovec, pipes, reboot +# ============================================================================ + +# ---- arch_prctl ---- +def ARCH_PRCTL(code: int, addr: uint64) -> int: + if code == abi.ARCH_SET_FS: + sched.set_fs_base(addr) + return 0 + if code == abi.ARCH_GET_FS: + if addr == 0: return _err(abi.EFAULT) + dst: uint64* = uint64*(addr) + dst[0] = sched.get_fs_base() + return 0 + return _err(abi.ENOSYS) + +# ---- futex: polling implementation (no sleep queue yet) ---- +def FUTEX(uaddr: uint64, op: uint32, val: uint32, timeout: rawptr) -> int: + if uaddr == 0: return _err(abi.EFAULT) + cmd: int = int(op) & 0x7F + u: uint32* = uint32*(uaddr) + if cmd == 0 or cmd == 9: + if u[0] != val: return _err(abi.EAGAIN) + while u[0] == val: + sched.Scheduler.yield_now() + return 0 + if cmd == 1 or cmd == 10: + return 0 + return _err(abi.ENOSYS) + +# ---- time ---- +def _rtc_epoch() -> uint64: + y: int = int(rtc.rtc_get_year()) + mo: int = int(rtc.rtc_get_month()) + d: int = int(rtc.rtc_get_day()) + h: int = int(rtc.rtc_get_hours()) + mi: int = int(rtc.rtc_get_minutes()) + s: int = int(rtc.rtc_get_seconds()) + yy: int = y + if mo <= 2: yy = yy - 1 + era: int = yy / 400 + yoe: int = yy - era * 400 + mp: int = (mo + 9) % 12 + doy: int = (153 * mp + 2) / 5 + d - 1 + doe: int = yoe * 365 + yoe / 4 - yoe / 100 + doy + days: int = era * 146097 + doe - 719468 + return uint64(days * 86400 + h * 3600 + mi * 60 + s) + +def CLOCK_GETTIME(clockid: int, ts_ptr: rawptr) -> int: + if ts_ptr is None: return _err(abi.EFAULT) + ts: abi.linux_timespec* = ts_ptr + if clockid == 0: + ts.tv_sec = _rtc_epoch() + ts.tv_nsec = uint64(0) + else: + t: uint64 = timer.timer_get_ticks() + ts.tv_sec = t / uint64(1000) + ts.tv_nsec = (t % uint64(1000)) * uint64(1000000) + return 0 + +def CLOCK_GETRES(clockid: int, ts_ptr: rawptr) -> int: + if ts_ptr is None: return _err(abi.EFAULT) + ts: abi.linux_timespec* = ts_ptr + ts.tv_sec = uint64(0) + ts.tv_nsec = uint64(1000000) + return 0 + +def GETTIMEOFDAY(tv_ptr: rawptr, tz_ptr: rawptr) -> int: + if tv_ptr is not None: + tv: uint64* = uint64*(tv_ptr) + tv[0] = _rtc_epoch() + tv[1] = uint64(0) + if tz_ptr is not None: + string.memset(tz_ptr, 0, 8) + return 0 + +def NANOSLEEP(req_ptr: rawptr, rem_ptr: rawptr) -> int: + if req_ptr is None: return _err(abi.EFAULT) + ts: abi.linux_timespec* = req_ptr + ms: uint64 = ts.tv_sec * uint64(1000) + ts.tv_nsec / uint64(1000000) + if ms > uint64(0x7FFFFFFF): ms = uint64(0x7FFFFFFF) + if ms > 0: + timer.timer_msleep(uint32(ms)) + return 0 + +# ---- signals: registration + delivery on syscall return ---- +define SIG_DFL = 0 +define SIG_IGN = 1 +define SA_NODEFER = 0x40000000 + +def RT_SIGACTION(sig: int, act_ptr: rawptr, old_ptr: rawptr, sigsetsize: uint32) -> int: + if sig < 1 or sig > 64: return _err(abi.EINVAL) + p: process.Process* = _current_proc() + if old_ptr is not None: + o: uint64* = uint64*(old_ptr) + o[0] = p.sig_handler[sig] + o[1] = p.sig_actflags[sig] + o[2] = p.sig_restorer[sig] + o[3] = uint64(0) + if act_ptr is not None: + a: uint64* = uint64*(act_ptr) + p.sig_handler[sig] = a[0] + p.sig_actflags[sig] = a[1] + p.sig_restorer[sig] = a[2] + return 0 + +def RT_SIGPROCMASK(how: int, set_ptr: rawptr, old_ptr: rawptr, sigsetsize: uint32) -> int: + p: process.Process* = _current_proc() + if old_ptr is not None: + o: uint64* = uint64*(old_ptr) + o[0] = p.sig_mask + if set_ptr is not None: + s: uint64* = uint64*(set_ptr) + m: uint64 = s[0] + if how == 0: + p.sig_mask = p.sig_mask | m + elif how == 1: + p.sig_mask = p.sig_mask & ~m + elif how == 2: + p.sig_mask = m + else: + return _err(abi.EINVAL) + return 0 + +def KILL(pid: int, sig: int) -> int: + if sig < 0 or sig > 64: return _err(abi.EINVAL) + if sig == 0: return 0 + target: process.Process* = None + if pid == 0 or pid == process.current(): + target = _current_proc() + elif pid > 0: + target = process.ProcessManager.get_process(pid) + if target is None: return _err(abi.ESRCH) + target.sig_pending = target.sig_pending | (uint64(1) << uint64(sig - 1)) + return 0 + +def RT_SIGRETURN() -> int: + f: uint64* = uint64*(sched.get_syscall_frame()) + sf: uint64 = f[15] - uint64(8) + w: uint64* = uint64*(sf) + i: int = 0 + while i < 12: + f[i] = w[1 + i] + i += 1 + f[13] = w[13] + f[12] = w[14] + f[15] = w[15] + p: process.Process* = _current_proc() + p.sig_mask = w[18] + return int(w[16]) + +# Deliver one pending signal by rewriting the syscall return frame. +# Returns 1 if a handler was entered (the syscall must return 0 in RAX). +def _deliver_signals(ret: uint64) -> int: + p: process.Process* = _current_proc() + avail: uint64 = p.sig_pending & ~p.sig_mask + # SIGKILL(9) / SIGSTOP(19) cannot be blocked + avail = avail | (p.sig_pending & (uint64(1) << uint64(8))) + avail = avail | (p.sig_pending & (uint64(1) << uint64(18))) + if avail == 0: return 0 + sig: int = 1 + while sig <= 64: + if (avail & (uint64(1) << uint64(sig - 1))) != 0: break + sig += 1 + if sig > 64: return 0 + bit: uint64 = uint64(1) << uint64(sig - 1) + p.sig_pending = p.sig_pending & ~bit + h: uint64 = p.sig_handler[sig] + if h == uint64(SIG_IGN): + return _deliver_signals(ret) + if h == uint64(SIG_DFL): + if sig == 1 or sig == 2 or sig == 3 or sig == 4 or sig == 5 or sig == 6 or sig == 7 or sig == 8 or sig == 9 or sig == 11 or sig == 13 or sig == 14 or sig == 15: + p.exit_code = 128 + sig + sched.Scheduler.exit_process(p.pid) + return _deliver_signals(ret) + rest: uint64 = p.sig_restorer[sig] + if rest == 0: + return _deliver_signals(ret) + f: uint64* = uint64*(sched.get_syscall_frame()) + user_rsp: uint64 = f[15] + user_rip: uint64 = f[13] + user_flags: uint64 = f[12] + # The handler is entered with RSP == sf and [sf] == restorer, exactly like a + # normal call: rsp % 16 == 8 at function entry. rt_sigreturn then runs with + # rsp == sf + 8 and recovers sf as (user_rsp - 8). + sf: uint64 = ((user_rsp - uint64(320)) & ~uint64(0xF)) - uint64(8) + w: uint64* = uint64*(sf) + w[0] = rest + i: int = 0 + while i < 12: + w[1 + i] = f[i] + i += 1 + w[13] = user_rip + w[14] = user_flags + w[15] = user_rsp + w[16] = ret + w[17] = uint64(sig) + w[18] = p.sig_mask + si: uint64 = sf + uint64(152) + string.memset(rawptr(si), 0, 128) + si32: uint32* = uint32*(si) + si32[0] = uint32(sig) # si_signo + si32[1] = uint32(0) # si_errno + si32[2] = uint32(0) # si_code + # handler(sig, &siginfo, &ucontext) + f[8] = uint64(sig) + f[9] = si + f[10] = sf + uint64(8) + f[13] = h + # Enter the handler with [sf] = restorer (normal call-frame shape). + f[15] = sf + if (p.sig_actflags[sig] & uint64(SA_NODEFER)) == 0: + p.sig_mask = p.sig_mask | bit + return 1 + +# ---- iovec ---- +define IOV_MAX = 1024 + +def READV(fd: int, iov_ptr: rawptr, iovcnt: int) -> int: + if iov_ptr is None or iovcnt < 0 or iovcnt > IOV_MAX: return _err(abi.EINVAL) + iov: abi.iovec* = iov_ptr + total: int = 0 + i: int = 0 + while i < iovcnt: + want: uint32 = 0xFFFFFFFF + if iov[i].len < uint64(0xFFFFFFFF): want = uint32(iov[i].len) + n: int = READ(fd, iov[i].base, want) + if n < 0: + if total > 0: return total + return n + total = total + n + if uint64(n) < iov[i].len: break + i += 1 + return total + +def WRITEV(fd: int, iov_ptr: rawptr, iovcnt: int) -> int: + if iov_ptr is None or iovcnt < 0 or iovcnt > IOV_MAX: return _err(abi.EINVAL) + iov: abi.iovec* = iov_ptr + total: int = 0 + i: int = 0 + while i < iovcnt: + want: uint32 = 0xFFFFFFFF + if iov[i].len < uint64(0xFFFFFFFF): want = uint32(iov[i].len) + n: int = WRITE(fd, iov[i].base, want) + if n < 0: + if total > 0: return total + return n + total = total + n + if uint64(n) < iov[i].len: break + i += 1 + return total + +def POLL(fds: rawptr, nfds: uint64, timeout: int) -> int: + return 0 + +# ---- pipes ---- +define MAX_PIPES = 8 +define PIPE_CAP = 4096 + +_pipe_buf: [MAX_PIPES]rawptr +_pipe_head: [MAX_PIPES]uint32 +_pipe_tail: [MAX_PIPES]uint32 +_pipe_cnt: [MAX_PIPES]uint32 +_pipe_rd: [MAX_PIPES]int +_pipe_wr: [MAX_PIPES]int +_pipe_used: [MAX_PIPES]uint8 + +def _pipe_alloc() -> int: + for i in range(MAX_PIPES): + if _pipe_used[i] == 0: + b: rawptr = mm.malloc(uint64(PIPE_CAP)) + if b is None: return -1 + _pipe_buf[i] = b + _pipe_head[i] = 0 + _pipe_tail[i] = 0 + _pipe_cnt[i] = 0 + _pipe_rd[i] = 0 + _pipe_wr[i] = 0 + _pipe_used[i] = 1 + return i + return -1 + +def PIPE2(fds_ptr: rawptr, flags: uint32) -> int: + if fds_ptr is None: return _err(abi.EFAULT) + p: process.Process* = _current_proc() + id: int = _pipe_alloc() + if id < 0: return _err(abi.EMFILE) + rfd: int = _fd_alloc(p) + if rfd < 0: + mm.free(_pipe_buf[id]) + _pipe_used[id] = 0 + return _err(abi.EMFILE) + wfd: int = _fd_alloc(p) + if wfd < 0: + _fd_free(p, rfd) + mm.free(_pipe_buf[id]) + _pipe_used[id] = 0 + return _err(abi.EMFILE) + p.fd_table[rfd] = rawptr(uint64(id * 2 + 1)) + p.fd_kind[rfd] = process.FD_KIND_PIPE + p.fd_flags[rfd] = flags + p.fd_table[wfd] = rawptr(uint64(id * 2 + 2)) + p.fd_kind[wfd] = process.FD_KIND_PIPE + p.fd_flags[wfd] = flags + _pipe_rd[id] = 1 + _pipe_wr[id] = 1 + fds: int* = int*(fds_ptr) + fds[0] = rfd + fds[1] = wfd + return 0 + +def PIPE(fds_ptr: rawptr) -> int: + return PIPE2(fds_ptr, uint32(0)) + +def _pipe_read(fp: rawptr, buf_ptr: rawptr, count: uint32) -> int: + id: int = (int(uint64(fp)) - 1) / 2 + if id < 0 or id >= MAX_PIPES or _pipe_used[id] == 0: return _err(abi.EBADF) + while _pipe_cnt[id] == 0: + if _pipe_wr[id] == 0: return 0 + sched.Scheduler.yield_now() + n: uint32 = count + if _pipe_cnt[id] < n: n = _pipe_cnt[id] + dst: uint8* = uint8*(buf_ptr) + i: uint32 = 0 + while i < n: + src: uint8* = uint8*(uint64(_pipe_buf[id]) + uint64(_pipe_head[id])) + dst[i] = src[0] + _pipe_head[id] = (_pipe_head[id] + uint32(1)) % uint32(PIPE_CAP) + i += 1 + _pipe_cnt[id] = _pipe_cnt[id] - n + return int(n) + +def _pipe_write(fp: rawptr, buf_ptr: rawptr, count: uint32) -> int: + id: int = (int(uint64(fp)) - 1) / 2 + if id < 0 or id >= MAX_PIPES or _pipe_used[id] == 0: return _err(abi.EBADF) + if _pipe_rd[id] == 0: return _err(abi.EPIPE) + while _pipe_cnt[id] >= uint32(PIPE_CAP): + if _pipe_rd[id] == 0: return _err(abi.EPIPE) + sched.Scheduler.yield_now() + n: uint32 = count + room: uint32 = uint32(PIPE_CAP) - _pipe_cnt[id] + if room < n: n = room + src: uint8* = uint8*(buf_ptr) + i: uint32 = 0 + while i < n: + dst: uint8* = uint8*(uint64(_pipe_buf[id]) + uint64(_pipe_tail[id])) + dst[0] = src[i] + _pipe_tail[id] = (_pipe_tail[id] + uint32(1)) % uint32(PIPE_CAP) + i += 1 + _pipe_cnt[id] = _pipe_cnt[id] + n + return int(n) + +# Count one more reference to a pipe end (dup/dup2/fork inherit). +def _pipe_add_ref(fp: rawptr): + v: int = int(uint64(fp)) - 1 + id: int = v / 2 + end: int = v % 2 + if id < 0 or id >= MAX_PIPES: return + if end == 0: _pipe_rd[id] = _pipe_rd[id] + 1 + else: _pipe_wr[id] = _pipe_wr[id] + 1 + +def _pipe_close(fp: rawptr) -> int: + v: int = int(uint64(fp)) - 1 + id: int = v / 2 + end: int = v % 2 + if id < 0 or id >= MAX_PIPES: return _err(abi.EBADF) + if end == 0: + if _pipe_rd[id] > 0: _pipe_rd[id] = _pipe_rd[id] - 1 + else: + if _pipe_wr[id] > 0: _pipe_wr[id] = _pipe_wr[id] - 1 + if _pipe_rd[id] == 0 and _pipe_wr[id] == 0: + if _pipe_buf[id] is not None: mm.free(_pipe_buf[id]) + _pipe_buf[id] = None + _pipe_used[id] = 0 + return 0 + +# ---- Linux reboot(169) ---- +define LINUX_REBOOT_MAGIC1 = 0xFEE1DEAD +define LINUX_REBOOT_MAGIC2 = 672274793 +define LINUX_REBOOT_CMD_RESTART = 0x01234567 +define LINUX_REBOOT_CMD_POWER_OFF = 0x4321FEDC + +def REBOOT_LINUX(magic1: uint32, magic2: uint32, cmd: uint32, arg: rawptr) -> int: + if magic1 != uint32(LINUX_REBOOT_MAGIC1) or magic2 != uint32(LINUX_REBOOT_MAGIC2): + return _err(abi.EINVAL) + if cmd == uint32(LINUX_REBOOT_CMD_RESTART): + power_reboot() + return 0 + if cmd == uint32(LINUX_REBOOT_CMD_POWER_OFF): + power_shutdown() + return 0 + return _err(abi.EINVAL) + +# ============================================================================ +# Linux clone()/fork(): resume the child at the caller's post-syscall RIP +# ============================================================================ + +define CLONE_VM = 0x00000100 +define CLONE_FS = 0x00000200 +define CLONE_FILES = 0x00000400 +define CLONE_SIGHAND = 0x00000800 +define CLONE_THREAD = 0x00010000 +define CLONE_SETTLS = 0x00080000 +define CLONE_PARENT_SETTID = 0x00100000 +define CLONE_CHILD_CLEARTID = 0x00200000 +define CLONE_CHILD_SETTID = 0x01000000 + +class ForkCtx: + pid: int + rip: uint64 + rsp: uint64 + flags: uint64 + fs: uint64 + kern_stack: uint64 + regs: [12]uint64 + +def _clone_context(flags: uint64, stack: uint64, tls: uint64) -> ForkCtx*: + fp: uint64 = sched.get_syscall_frame() + if fp == 0: return None + f: uint64* = uint64*(fp) + ctx: ForkCtx* = ForkCtx*(mm.malloc(uint64(ForkCtx.__sizeof__()))) + if ctx is None: return None + string.memset(rawptr(ctx), 0, uint64(ForkCtx.__sizeof__())) + ctx.rip = f[13] + ctx.flags = f[12] + ctx.rsp = f[15] + if stack != 0: ctx.rsp = stack + ctx.fs = sched.get_fs_base() + if (flags & uint64(CLONE_SETTLS)) != 0 and tls != 0: + ctx.fs = tls + i: int = 0 + while i < 12: + ctx.regs[i] = f[i] + i += 1 + return ctx + +def _fork_child_entry(arg: rawptr) -> int: + ctx: ForkCtx* = arg + asm.cli() + proc.switch_pid(ctx.pid) + ks: uint64 = ctx.kern_stack + gdt.set_tss_rsp0(ks) + cpu.set_kernel_rsp0(ks) + proc.set_resume(ctx.rip, ctx.rsp, ctx.flags, uint64(0), &ctx.regs[0]) + mm.free(rawptr(ctx)) + proc.drop_to_user_mode_ctx(ks) + return 0 + +def _clone_thread(flags: uint64, stack: uint64, ptid: uint64, ctid: uint64, tls: uint64) -> int: + cur: process.Process* = _current_proc() + ctx: ForkCtx* = _clone_context(flags, stack, tls) + if ctx is None: return _err(abi.EAGAIN) + ctx.pid = cur.pid + th: sched.Thread* = sched.Scheduler.create_thread(&_fork_child_entry, rawptr(ctx), cur.pid) + if th is None: + mm.free(rawptr(ctx)) + return _err(abi.EAGAIN) + th.kern_stack = uint64(th.stack) + th.stack_size + ctx.kern_stack = th.kern_stack + if cur.addThread(th.tid) != 0: + th.state = sched.THREAD_DONE + mm.free(rawptr(ctx)) + return _err(abi.EAGAIN) + sched.set_thread_fs_base(th.tid, ctx.fs) + if (flags & uint64(CLONE_PARENT_SETTID)) != 0 and ptid != 0: + pt: int* = int*(ptid) + pt[0] = th.tid + if (flags & uint64(CLONE_CHILD_SETTID)) != 0 and ctid != 0: + ct: int* = int*(ctid) + ct[0] = th.tid + if (flags & uint64(CLONE_CHILD_CLEARTID)) != 0 and ctid != 0: + sched.set_clear_child_tid(th.tid, ctid) + return th.tid + +def _alloc_page4k() -> rawptr: + return mm.alloc_page() + +# Fork/exec inherit the fd table as shared references: neither side may release +# an object the other still uses. Non-pipe objects have no cross-process +# refcount yet, so mark BOTH sides shared (they are leaked on close instead of +# being freed under a live user). Pipes do have counts and stay reclaimable. +def _copy_fds(dst: process.Process*, src: process.Process*): + i: int = 0 + while i < process.PROC_MAX_FDS: + if src.fd_used[i]: + dst.fd_used[i] = 1 + dst.fd_table[i] = src.fd_table[i] + dst.fd_kind[i] = src.fd_kind[i] + dst.fd_flags[i] = src.fd_flags[i] + if src.fd_kind[i] == process.FD_KIND_PIPE: + dst.fd_shared[i] = 0 + _pipe_add_ref(src.fd_table[i]) + else: + dst.fd_shared[i] = 1 + src.fd_shared[i] = 1 + i += 1 + +# Linux execve (placed after the pipe/fork helpers so it can inherit fds). +def EXECVE(path_ptr: str, argv: rawptr, envp: rawptr) -> int: + cur: process.Process* = _current_proc() + old_parent: int = cur.parent_pid + path: const str = path_ptr + pid: int = elf.spawn_elf(path, argv, envp) + if pid < 0: + return _err(abi.ENOENT) + np: process.Process* = process.ProcessManager.get_process(pid) + if np is not None: + np.parent_pid = old_parent + string.strcpy(&np.cwd[0], &cur.cwd[0]) + np.sig_mask = cur.sig_mask + i: int = 0 + while i < 65: + np.sig_handler[i] = 0 + np.sig_actflags[i] = 0 + np.sig_restorer[i] = 0 + i += 1 + np.sig_pending = 0 + _copy_fds(np, cur) + cur.exit_code = 0 + sched.Scheduler.exit_process(cur.pid) + return 0 + +def _fork_process(stack: uint64) -> int: + cur: process.Process* = _current_proc() + ctx: ForkCtx* = _clone_context(uint64(0), stack, uint64(0)) + if ctx is None: return _err(abi.EAGAIN) + np: process.Process* = process.ProcessManager.create_process(&cur.name[0], None) + if np is None: + mm.free(rawptr(ctx)) + return _err(abi.EAGAIN) + new_pml4: uint64 = paging.create_process_page_table() + if new_pml4 == 0: + mm.free(rawptr(ctx)) + np.exit() + return _err(abi.ENOMEM) + if paging.CopyUserSpace(cur.pml4_root, new_pml4, rawptr(uint64(&_alloc_page4k))) != 0: + mm.free(rawptr(ctx)) + np.exit() + return _err(abi.ENOMEM) + np.pml4_root = new_pml4 + np.elf_base = cur.elf_base + np.heap_start = cur.heap_start + np.heap_size = cur.heap_size + np.brk_start = cur.brk_start + np.brk_end = cur.brk_end + np.entry = cur.entry + np.parent_pid = cur.pid + string.strcpy(&np.cwd[0], &cur.cwd[0]) + i: int = 0 + while i < process.MAX_VMAS: + np.vma_base[i] = cur.vma_base[i] + np.vma_size[i] = cur.vma_size[i] + np.vma_prot[i] = cur.vma_prot[i] + np.vma_kind[i] = cur.vma_kind[i] + np.vma_flags[i] = cur.vma_flags[i] + i += 1 + i = 0 + while i < 65: + np.sig_handler[i] = cur.sig_handler[i] + np.sig_actflags[i] = cur.sig_actflags[i] + np.sig_restorer[i] = cur.sig_restorer[i] + i += 1 + np.sig_mask = cur.sig_mask + np.sig_pending = 0 + # shared fd table (the child must not close the parent's objects) + _copy_fds(np, cur) + # create_process allocated a private 64KB stack; the child uses the copy. + if np.user_stack != 0: + mm.free(rawptr(np.user_stack - uint64(process.USER_STACK_SIZE))) + np.user_stack = 0 + ctx.pid = np.pid + th: sched.Thread* = sched.Scheduler.create_thread(&_fork_child_entry, rawptr(ctx), np.pid) + if th is None: + mm.free(rawptr(ctx)) + np.exit() + return _err(abi.EAGAIN) + th.kern_stack = np.kern_stack + ctx.kern_stack = np.kern_stack + np.addThread(th.tid) + sched.set_thread_fs_base(th.tid, ctx.fs) + return np.pid + +def CLONE(flags: uint64, stack: uint64, ptid: uint64, ctid: uint64, tls: uint64) -> int: + if (flags & uint64(CLONE_VM)) != 0: + return _clone_thread(flags, stack, ptid, ctid, tls) + return _fork_process(stack) + +def FORK() -> int: + return _fork_process(uint64(0)) + +# ============================================================================ +# P0: identity / environment / random +# ============================================================================ + +def GETUID() -> uint64: + return uint64(0) + +def GETEUID() -> uint64: + return uint64(0) + +def GETGID() -> uint64: + return uint64(0) + +def GETEGID() -> uint64: + return uint64(0) + +def GETPPID() -> uint64: + return uint64(_current_proc().parent_pid) + +def GETTID() -> uint64: + return uint64(sched.thread_current()) + +def SET_TID_ADDRESS(tid_ptr: rawptr) -> uint64: + # Single-address-space threads: the scheduler tid is the Linux tid. + tid: int = sched.thread_current() + if tid_ptr is not None: + sched.set_clear_child_tid(tid, uint64(tid_ptr)) + return uint64(tid) + +def SET_ROBUST_LIST(head: rawptr, len: uint64) -> int: + # Recorded nowhere yet; futex-based thread exit lands with P4. + return 0 + +def RSEQ(rseq_ptr: rawptr, len: uint32, flags: uint32) -> int: + # glibc disables rseq when this returns -ENOSYS. + return _err(abi.ENOSYS) + +def UNAME(buf_ptr: rawptr) -> int: + if buf_ptr is None: return _err(abi.EFAULT) + u: abi.linux_utsname* = buf_ptr + string.memset(rawptr(u), 0, uint64(abi.SIZEOF_UTSNAME)) + # Report "Linux" so scripts and build systems do not special-case us; + # the ViperOS identity stays in the version string. + string.strcpy(&u.sysname[0], "Linux") + string.strcpy(&u.nodename[0], "viperos") + string.strcpy(&u.release[0], "6.1.0-viperos") + string.strcpy(&u.version[0], "#1 SMP ViperOS") + string.strcpy(&u.machine[0], "x86_64") + string.strcpy(&u.domainname[0], "(none)") + return 0 + +def GETCWD(buf_ptr: rawptr, size: uint64) -> int: + if buf_ptr is None: return _err(abi.EFAULT) + p: process.Process* = _current_proc() + n: int = int(string.strlen(&p.cwd[0])) + if size < uint64(n + 1): return _err(abi.ERANGE) + dst: char* = char*(buf_ptr) + string.strcpy(dst, &p.cwd[0]) + return n + 1 + +def CHDIR(path_ptr: str) -> int: + path: const str = path_ptr + buf: [256]char + r: int = _resolve_path(abi.AT_FDCWD, path, &buf[0]) + if r != 0: return r + p: process.Process* = _current_proc() + if buf[0] == 47 and buf[1] == 0: + string.strcpy(&p.cwd[0], "/") + return 0 + info: fat32_types.fileinfo + fat32.lock() + res: int = int(fat32.stat(&buf[0], &info)) + fat32.unlock() + if res != 0: return _fr_errno(res) + if (int(info.attr) & 0x10) == 0: + return _err(abi.ENOTDIR) + string.strcpy(&p.cwd[0], &buf[0]) + return 0 + +def FACCESSAT(dirfd: int, path_ptr: str, mode: uint32, flags: uint32) -> int: + path: const str = path_ptr + buf: [256]char + r: int = _resolve_path(dirfd, path, &buf[0]) + if r != 0: return r + if buf[0] == 47 and buf[1] == 0: return 0 + info: fat32_types.fileinfo + fat32.lock() + res: int = int(fat32.stat(&buf[0], &info)) + fat32.unlock() + if res != 0: return _fr_errno(res) + return 0 + +def ACCESS(path_ptr: str, mode: uint32) -> int: + return FACCESSAT(abi.AT_FDCWD, path_ptr, mode, uint32(0)) + +def READLINKAT(dirfd: int, path_ptr: str, buf_ptr: rawptr, bufsiz: uint64) -> int: + path: const str = path_ptr + tmp: [256]char + r: int = _resolve_path(dirfd, path, &tmp[0]) + if r != 0: return r + info: fat32_types.fileinfo + fat32.lock() + res: int = int(fat32.stat(&tmp[0], &info)) + fat32.unlock() + if res != 0: return _fr_errno(res) + # FAT32 has no symbolic links; an existing name is simply not a link. + return _err(abi.EINVAL) + +def READLINK(path_ptr: str, buf_ptr: rawptr, bufsiz: uint64) -> int: + return READLINKAT(abi.AT_FDCWD, path_ptr, buf_ptr, bufsiz) + +def PRLIMIT64(pid: int, resource: uint32, new_ptr: rawptr, old_ptr: rawptr) -> int: + if pid != 0 and pid != process.current(): + return _err(abi.ESRCH) + # Setting limits is accepted but not enforced yet. + if old_ptr is not None: + rl: abi.linux_rlimit* = old_ptr + cur: uint64 = abi.RLIM_INFINITY + mx: uint64 = abi.RLIM_INFINITY + if resource == uint32(abi.RLIMIT_STACK): + cur = uint64(process.USER_STACK_SIZE) + mx = uint64(process.USER_STACK_SIZE) + elif resource == uint32(abi.RLIMIT_NOFILE): + cur = uint64(process.PROC_MAX_FDS) + mx = uint64(process.PROC_MAX_FDS) + rl.cur = cur + rl.max = mx + return 0 + +def GETRANDOM(buf_ptr: rawptr, buflen: uint64, flags: uint32) -> int: + if buf_ptr is None: return _err(abi.EFAULT) + if buflen > uint64(0x7FFFFFFF): return _err(abi.EINVAL) + p: uint8* = uint8*(buf_ptr) + i: uint64 = 0 + while i < buflen: + v: uint64 = _rng_next() + j: int = 0 + while j < 8 and i < buflen: + p[i] = uint8(v & uint64(0xFF)) + v = v >> 8 + i += 1 + j += 1 + return int(buflen) + extern def syscall_entry() -export def syscall_handler(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64, arg4: uint64, arg5: uint64, arg6: uint64) -> uint64: +def syscall_dispatch(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64, arg4: uint64, arg5: uint64, arg6: uint64) -> uint64: n: int = int(num) if n == int(Syscall.EXIT): return uint64(EXIT(int(arg1))) @@ -462,21 +1977,13 @@ export def syscall_handler(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64 elif n == int(Syscall.IOCTL): return uint64(IOCTL(int(arg1), uint32(arg2), rawptr(arg3))) elif n == int(Syscall.SEEK): - return uint64(SEEK(int(arg1), uint32(arg2), uint32(arg3))) + return uint64(SEEK(int(arg1), arg2, uint32(arg3))) elif n == int(Syscall.MKDIR): path_ptr: str = str(arg1) return uint64(MKDIR(path_ptr)) elif n == int(Syscall.REMOVE): path_ptr: str = str(arg1) return uint64(REMOVE(path_ptr)) - elif n == int(Syscall.OPENDIR): - path_ptr: str = str(arg1) - return uint64(OPENdir(path_ptr)) - elif n == int(Syscall.READDIR): - info_ptr: rawptr = rawptr(arg2) - return uint64(READdir(int(arg1), info_ptr)) - elif n == int(Syscall.CLOSEDIR): - return uint64(CLOSEdir(int(arg1))) elif n == int(Syscall.STAT): path_ptr: str = str(arg1) info_ptr: rawptr = rawptr(arg2) @@ -487,34 +1994,50 @@ export def syscall_handler(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64 elif n == int(Syscall.SHUTDOWN): power_shutdown() return uint64(0) - elif n == int(Syscall.LOAD_SO): - path_ptr_so: rawptr = rawptr(arg1) - return LOAD_SO(path_ptr_so) - elif n == int(Syscall.SO_CALL1): - sym_ptr: rawptr = rawptr(arg1) - return uint64(SO_CALL1(sym_ptr, rawptr(arg2))) - elif n == int(Syscall.GETPID): - return GETPID() - elif n == int(Syscall.YIELD): - # sched_yield 是"主动放弃剩余时间片",必须无条件切换;try_reschedule() 只在 - # 时间片到期时才切,作为 syscall 会让等待外设的进程白等(见 Scheduler.yield_now 注释)。 - sched.Scheduler.yield_now() - return 0 + elif n == int(Syscall.LOAD_SO): + path_ptr_so: rawptr = rawptr(arg1) + return LOAD_SO(path_ptr_so) + elif n == int(Syscall.SO_CALL1): + sym_ptr: rawptr = rawptr(arg1) + return uint64(SO_CALL1(sym_ptr, rawptr(arg2))) + elif n == int(Syscall.GETPID): + return GETPID() + elif n == int(Syscall.YIELD): + # sched_yield 是"主动放弃剩余时间片",必须无条件切换;try_reschedule() 只在 + # 时间片到期时才切,作为 syscall 会让等待外设的进程白等(见 Scheduler.yield_now 注释)。 + sched.Scheduler.yield_now() + return 0 elif n == int(Syscall.SPAWN): - path_ptr_sp: rawptr = rawptr(arg1) - return uint64(SPAWN(path_ptr_sp, int(arg2))) + return uint64(EXECVE(str(arg1), rawptr(arg2), rawptr(arg3))) + elif n == int(Syscall.SPAWN_PROC): + path_ptr_sp2: rawptr = rawptr(arg1) + return uint64(SPAWN_PROC(path_ptr_sp2, int(arg2))) + elif n == int(Syscall.WAIT4): + return uint64(WAIT4(int(arg1), rawptr(arg2), uint32(arg3), rawptr(arg4))) elif n == int(Syscall.MMAP): return MMAP(arg1, arg2, arg3, arg4, arg5, arg6) elif n == int(Syscall.MUNMAP): return uint64(MUNMAP(arg1, arg2)) - elif n == int(Syscall.THREAD_CREATE): + elif n == int(Syscall.MPROTECT): + return uint64(MPROTECT(arg1, arg2, arg3)) + elif n == int(Syscall.BRK): + return BRK(arg1) + elif n == int(Syscall.MREMAP): + return MREMAP(arg1, arg2, arg3, arg4, arg5) + elif n == int(Syscall.MADVISE): + return uint64(MADVISE(arg1, arg2, uint32(arg3))) + elif n == int(Syscall.CLONE): + return uint64(CLONE(arg1, arg2, arg3, arg4, arg5)) + elif n == int(Syscall.FORK): + return uint64(FORK()) + elif n == int(Syscall.THREAD_CREATE_OLD): func_ptr: rawptr = rawptr(arg1) arg_ptr: rawptr = rawptr(arg2) cur_pid: int = process.current() # app 传进来的是**用户地址**:必须用 create_user_thread(独立用户栈 + 独立内核栈、 # 走 drop_to_user_mode 进 Ring 3),不能用 create_thread 在 Ring 0 直接 call。 th: sched.Thread* = sched.Scheduler.create_user_thread(cur_pid, func_ptr, arg_ptr) - if th is None: return uint64(0xFFFFFFFFFFFFFFFF) + if th is None: return uint64(_err(abi.EAGAIN)) p: process.Process* = process.ProcessManager.get_process(cur_pid) if p: p.addThread(th.tid) @@ -523,9 +2046,124 @@ export def syscall_handler(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64 # 用户线程显式退出(回收用户栈等资源后让出,永不返回)。 sched.Scheduler.exit_current_thread() return 0 + elif n == int(Syscall.UNAME): + return uint64(UNAME(rawptr(arg1))) + elif n == int(Syscall.GETUID): + return GETUID() + elif n == int(Syscall.GETEUID): + return GETEUID() + elif n == int(Syscall.GETGID): + return GETGID() + elif n == int(Syscall.GETEGID): + return GETEGID() + elif n == int(Syscall.GETPPID): + return GETPPID() + elif n == int(Syscall.GETTID): + return GETTID() + elif n == int(Syscall.GETCWD): + return uint64(GETCWD(rawptr(arg1), arg2)) + elif n == int(Syscall.CHDIR): + return uint64(CHDIR(str(arg1))) + elif n == int(Syscall.ACCESS): + return uint64(ACCESS(str(arg1), uint32(arg2))) + elif n == int(Syscall.FACCESSAT): + return uint64(FACCESSAT(int(arg1), str(arg2), uint32(arg3), uint32(arg4))) + elif n == int(Syscall.READLINK): + return uint64(READLINK(str(arg1), rawptr(arg2), arg3)) + elif n == int(Syscall.READLINKAT): + return uint64(READLINKAT(int(arg1), str(arg2), rawptr(arg3), arg4)) + elif n == int(Syscall.SET_TID_ADDRESS): + return SET_TID_ADDRESS(rawptr(arg1)) + elif n == int(Syscall.SET_ROBUST_LIST): + return uint64(SET_ROBUST_LIST(rawptr(arg1), arg2)) + elif n == int(Syscall.RSEQ): + return uint64(RSEQ(rawptr(arg1), uint32(arg2), uint32(arg3))) + elif n == int(Syscall.PRLIMIT64): + return uint64(PRLIMIT64(int(arg1), uint32(arg2), rawptr(arg3), rawptr(arg4))) + elif n == int(Syscall.GETRANDOM): + return uint64(GETRANDOM(rawptr(arg1), arg2, uint32(arg3))) + elif n == int(Syscall.FSTAT): + return uint64(FSTAT(int(arg1), rawptr(arg2))) + elif n == int(Syscall.NEWFSTATAT): + return uint64(NEWFSTATAT(int(arg1), str(arg2), rawptr(arg3), uint32(arg4))) + elif n == int(Syscall.OPENAT): + return uint64(OPENAT(int(arg1), str(arg2), uint32(arg3), uint32(arg4))) + elif n == int(Syscall.GETDENTS64): + return uint64(GETDENTS64(int(arg1), rawptr(arg2), arg3)) + elif n == int(Syscall.DUP): + return uint64(DUP(int(arg1))) + elif n == int(Syscall.DUP2): + return uint64(DUP2(int(arg1), int(arg2))) + elif n == int(Syscall.DUP3): + return uint64(DUP3(int(arg1), int(arg2), uint32(arg3))) + elif n == int(Syscall.FCNTL): + return uint64(FCNTL(int(arg1), uint32(arg2), arg3)) + elif n == int(Syscall.PREAD64): + return uint64(PREAD64(int(arg1), rawptr(arg2), uint32(arg3), arg4)) + elif n == int(Syscall.PWRITE64): + return uint64(PWRITE64(int(arg1), rawptr(arg2), uint32(arg3), arg4)) + elif n == int(Syscall.RENAME): + return uint64(RENAME(str(arg1), str(arg2))) + elif n == int(Syscall.RMDIR): + return uint64(RMDIR(str(arg1))) + elif n == int(Syscall.UNLINKAT): + return uint64(UNLINKAT(int(arg1), str(arg2), uint32(arg3))) + elif n == int(Syscall.ARCH_PRCTL): + return uint64(ARCH_PRCTL(int(arg1), arg2)) + elif n == int(Syscall.FUTEX): + return uint64(FUTEX(arg1, uint32(arg2), uint32(arg3), rawptr(arg4))) + elif n == int(Syscall.CLOCK_GETTIME): + return uint64(CLOCK_GETTIME(int(arg1), rawptr(arg2))) + elif n == int(Syscall.CLOCK_GETRES): + return uint64(CLOCK_GETRES(int(arg1), rawptr(arg2))) + elif n == int(Syscall.GETTIMEOFDAY): + return uint64(GETTIMEOFDAY(rawptr(arg1), rawptr(arg2))) + elif n == int(Syscall.NANOSLEEP): + return uint64(NANOSLEEP(rawptr(arg1), rawptr(arg2))) + elif n == int(Syscall.RT_SIGACTION): + return uint64(RT_SIGACTION(int(arg1), rawptr(arg2), rawptr(arg3), uint32(arg4))) + elif n == int(Syscall.RT_SIGPROCMASK): + return uint64(RT_SIGPROCMASK(int(arg1), rawptr(arg2), rawptr(arg3), uint32(arg4))) + elif n == int(Syscall.RT_SIGRETURN): + return uint64(RT_SIGRETURN()) + elif n == int(Syscall.KILL): + return uint64(KILL(int(arg1), int(arg2))) + elif n == int(Syscall.READV): + return uint64(READV(int(arg1), rawptr(arg2), int(arg3))) + elif n == int(Syscall.WRITEV): + return uint64(WRITEV(int(arg1), rawptr(arg2), int(arg3))) + elif n == int(Syscall.POLL): + return uint64(POLL(rawptr(arg1), arg2, int(arg3))) + elif n == int(Syscall.PIPE): + return uint64(PIPE(rawptr(arg1))) + elif n == int(Syscall.PIPE2): + return uint64(PIPE2(rawptr(arg1), uint32(arg2))) + elif n == int(Syscall.REBOOT_LINUX): + return uint64(REBOOT_LINUX(uint32(arg1), uint32(arg2), uint32(arg3), rawptr(arg4))) + # Unknown/unimplemented Linux numbers return -ENOSYS (never success). + return uint64(_err(abi.ENOSYS)) + +# Normal C entry: dispatch, then deliver at most one pending signal by +# rewriting the saved user context. Returning 0 makes the signal handler's +# entry RAX = 0; the original syscall result is saved in the frame. +def _syscall_invoke(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64, arg4: uint64, arg5: uint64, arg6: uint64) -> uint64: + sched.set_syscall_frame(_entry_frame) + r: uint64 = syscall_dispatch(num, arg1, arg2, arg3, arg4, arg5, arg6) + if _deliver_signals(r) != 0: + return uint64(0) + return r + +# The compiler-provided syscall_entry calls this symbol. Naked: save the +# entry frame pointer and tail-jump into _syscall_invoke (its ret returns to +# the entry's call site). +@__attribute__(naked) +export def syscall_handler(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64, arg4: uint64, arg5: uint64, arg6: uint64) -> uint64: + __asm__("mov qword ptr [rip + _entry_frame], rbp\n jmp qword ptr [rip + _handler_ptr]") return 0 def init(): + global _handler_ptr + _handler_ptr = rawptr(uint64(&_syscall_invoke)) efer: uint64 = rdmsr(IA32_EFER) efer = efer | 1 wrmsr(IA32_EFER, efer) diff --git a/VKernel/Kernel/main.vp b/VKernel/Kernel/main.vp index f04f127..73102a7 100644 --- a/VKernel/Kernel/main.vp +++ b/VKernel/Kernel/main.vp @@ -111,13 +111,25 @@ class ViperKernel: op=[ASM_DESCR.CLOBBER_MEMORY]) if BootInfo is not None: paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize) + # PAT: entry 4 = write-combining. The framebuffer is mapped with + # PTE_PAT so large compositor blits stay fast on VMware/real HW + # (WB is fast but can be incoherent for scanout; UC is slow). + feats: uint32 = cpu.detect_features_static() + if (feats & uint32(cpu.CPU_FEATURE_PAT)) != 0: + pat_v: uint64 = cpu.msr_read(uint32(0x277)) + pat_v = (pat_v & ~(uint64(7) << uint64(32))) | (uint64(1) << uint64(32)) + cpu.msr_write(uint32(0x277), pat_v) + paging.set_pat_wc() + serial.puts("[main] PAT write-combining enabled\n") + fb_flags: uint64 = paging.PTE_WRITABLE + if paging.has_pat_wc() != 0: + fb_flags = fb_flags | paging.PTE_PAT fb_pages: uint64 = (fb_size + uint64(0xFFF)) >> uint64(12) for fpi in range(fb_pages): fva: uint64 = uint64(fb) + fpi * uint64(0x1000) - # 帧缓冲改用回写(去掉 PTE_PCD):此前按非缓存映射,每次写屏都直通设备, - # 全屏/大块刷新极慢。回写在 QEMU 下显示正确(TCG 不模拟缓存不一致); - # 真机若要严格正确应改为 write-combining(走 PAT)。 - paging.MapPage(fva, fva, paging.PTE_WRITABLE) + # 帧缓冲是 MMIO:优先 PAT WC(快且对扫描输出可见);CPU 不支持 + # PAT 时保持回写,避免用 UC 把整帧提交拖慢。 + paging.MapPage(fva, fva, fb_flags) mm.init(BootInfo.MemmapAddr, BootInfo.MemmapSize, BootInfo.MemmapDescSize, BootInfo.framebuffer_addr, fb_size, BootInfo.kernel_PhysAddr, _kern_end) pt_pool_buf: rawptr = mm.malloc(uint64(513) * uint64(4096)) paging.init_pool(pt_pool_buf, uint64(513) * uint64(4096)) @@ -240,6 +252,12 @@ class ViperKernel: viperlib.snprintf(&font_log, 64, "[main] FontDemo spawned as pid=%d\n", font_pid) serial.puts(&font_log) + img_pid: int = elf.spawn_elf("/APPS/imagesee.elf") + if img_pid >= 0: + img_log: [64]char + viperlib.snprintf(&img_log, 64, "[main] ImageSee spawned as pid=%d\n", img_pid) + serial.puts(&img_log) + sched.Scheduler.enable() # 破坏性 FAT32 测试已删除(write/rename/delete/mkdir/rmdir 会永久改坏 disk.img)。 @@ -263,7 +281,12 @@ class ViperKernel: # /dev/desk:explorer 与各 app 的共享内存通道(S3)。分配失败就让登记跳过 —— # explorer 的 open 会明确失败并打日志,不做静默兜底。 desk_shm: rawptr = mm.malloc(uint64(devfs.DESK_SHM_SIZE)) + sl: [160]char + viperlib.snprintf(&sl, 160, "[main] desk shm=0x%lx size=0x%lx\n", + uint64(desk_shm), uint64(devfs.DESK_SHM_SIZE)) + serial.puts(&sl) devfs.devfs_register_desk(desk_shm, uint64(devfs.DESK_SHM_SIZE)) + mm.check_lists(1) serial.puts("[main] sched started\n") diff --git a/VKernel/Kernel/mm/mm.vp b/VKernel/Kernel/mm/mm.vp index 8398ca8..0f4c359 100644 --- a/VKernel/Kernel/mm/mm.vp +++ b/VKernel/Kernel/mm/mm.vp @@ -56,6 +56,58 @@ class memory_block: # 块头魔数("BLK!") define BLOCK_MAGIC = 0x424C4B21 +# [DIAG] 监视某个空闲块头是否被越界写清零;配合 _last_user 定位"谁写到了这里"。 +static _watch_addr: uint64 = 0 +static _last_user: uint64 = 0 +static _last_size: uint64 = 0 +static _last_kind: int = 0 + +def _watch_check(where: int): + global _watch_addr + if _watch_addr == 0: return + if uint64((memory_block*(_watch_addr)).magic) == uint64(BLOCK_MAGIC): return + _wl: [192]char + viperlib.snprintf(&_wl, 192, "[mm] WATCH0 at %d: watch=0x%lx magic=0x%lx last=0x%lx size=0x%lx kind=%d\n", + where, _watch_addr, uint64(uint32((memory_block*(_watch_addr)).magic)), + _last_user, _last_size, _last_kind) + serial.puts(&_wl) + _watch_addr = 0 + +# ---- 真正的 4KiB 匿名页分配器 ---- +# mmap 的匿名/文件页不再走 malloc(4096)(那会占 8KiB:4KiB 头页 + 4KiB 数据), +# 而是直接向 buddy 要 order-0 块,整页交给用户。代价是页首的 memory_block 头会 +# 被用户数据覆盖 ⇒ free 时必须按"裸页"重建头再 merge;同时用位图区分"裸页"和 +# 普通块,避免 merge 把已分配裸页的脏头误判成坏块。 +define MAX_RAW_PAGES = 262144 # 1GiB / 4KiB +define RAW_BITS_BYTES = 32768 # MAX_RAW_PAGES / 8 +static _raw_heap_start: uint64 = 0 +static _raw_page_count: uint64 = 0 +static _raw_bits: [RAW_BITS_BYTES]uint8 + +def _raw_index(addr: uint64) -> uint64: + if addr < _raw_heap_start: return uint64(0xFFFFFFFFFFFFFFFF) + off: uint64 = addr - _raw_heap_start + idx: uint64 = off >> 12 + if idx >= _raw_page_count: return uint64(0xFFFFFFFFFFFFFFFF) + return idx + +def _raw_is_used(addr: uint64) -> int: + idx: uint64 = _raw_index(addr) + if idx == uint64(0xFFFFFFFFFFFFFFFF): return 0 + return (int(_raw_bits[idx >> 3]) >> int(idx & 7)) & 1 + +def _raw_set(addr: uint64, used: int): + idx: uint64 = _raw_index(addr) + if idx == uint64(0xFFFFFFFFFFFFFFFF): return + byte_i: uint64 = idx >> 3 + bit_i: int = int(idx & 7) + b: int = int(_raw_bits[byte_i]) + if used != 0: + b = b | (1 << bit_i) + else: + b = b & (255 - (1 << bit_i)) + _raw_bits[byte_i] = uint8(b & 0xFF) + # 伙伴系统结构 class __buddy_system: free_lists: [MAX_ORDER + 1]memory_block* @@ -91,11 +143,17 @@ class slab_page: def align_4k(addr: uint64) -> uint64: return (addr + 0xFFF) & ~0xFFF -# 计算阶数 +# 计算阶数:必须**向上取整**。旧实现用逐个右移(floor): +# size=0x4498+4096=0x5498? 或 _bb 的 1141760+4096 → 实际拿到 1MB 块, +# 但用户区从 block+4KB 起,写满 1.09MB 就**越过块尾 92KB**,踩坏后继块头 / +# 别人的数据(现场:[mm] BAD blk blk=0x21780000 sz=0 magic=0、随内存大小 +# 出现的随机崩溃、以及 Terminal 的 Text 缓冲被踩后键盘无响应)。 +# 向上取整后 1.14MB 请求拿 2MB 块,32MB 共享区拿 64MB 块(多一页头)。 static def get_order(size: uint64) -> int: order: int = 0 - while size > MIN_BLOCK_SIZE: - size >>= 1 + block: uint64 = uint64(MIN_BLOCK_SIZE) + while block < size and order < MAX_ORDER: + block = block << 1 order += 1 return order @@ -214,6 +272,10 @@ class _BuddySystemObject: # 检查伙伴块是否存在且空闲 if buddy >= memory_block*(self.buddy.end_addr): break # 伙伴块超出内存范围 + # 裸页(mmap 匿名/文件页)的块头已被用户数据覆盖:位图标记为已分配, + # 绝不能读它的 state/magic(会把用户数据当块头,误报 BAD / 误合并)。 + if _raw_is_used(uint64(buddy)) != 0: + break if int(buddy.state) != BLOCK_FREE or int(buddy.order) != order: break # 伙伴块不空闲或阶数不匹配 # 加固:摘链前先校验伙伴块头(magic / size-order 一致 / prev-next 在堆内且对齐)。 @@ -260,8 +322,31 @@ class _BuddySystemObject: # 保持空闲链原样 —— 否则紧接着的 `block.next.prev = None` 就会踩坏值。 _arc: int = self._check_block(block, BLOCK_FREE) if _arc != 0: - self._report_bad_block(1, _arc, block, found_order) - return None + # 现场:1GiB 内存布局下 order-16 空闲块头被整块清零(sz/st/ord/magic/ + # nxt/prv 全 0),块体本身未分配。若整头全 0,可按链表阶数无损重建头, + # 否则一次坏块会让之后所有分配永久失败(用户态 malloc 返回 NULL → + # 写 0 触发 [0E])。重建只针对"全零头",其他损坏仍按原逻辑拒绝。 + if _arc == 1: + _zeroed: int = 0 + if block.size == 0 and block.state == 0 and block.order == 0 and uint64(block.next) == 0 and uint64(block.prev) == 0: + _zeroed = 1 + if _zeroed == 1: + block.size = get_block_size(found_order) + block.state = BLOCK_FREE + block.order = uint8(found_order) + block.magic = uint32(BLOCK_MAGIC) + block.next = None + block.prev = None + _rbl: [128]char + viperlib.snprintf(&_rbl, 128, "[mm] repaired zeroed free block 0x%lx order=%d size=0x%lx\n", + uint64(block), found_order, block.size) + serial.puts(&_rbl) + else: + self._report_bad_block(1, _arc, block, found_order) + return None + else: + self._report_bad_block(1, _arc, block, found_order) + return None # 从空闲链表中移除块 if block.next: block.next.prev = None self.buddy.free_lists[found_order] = block.next @@ -502,6 +587,8 @@ SlabAllocator: _SlabAllocator # 内存分配函数 export def malloc(size: uint64) -> rawptr: + global _last_user, _last_size, _last_kind + _watch_check(1) if size == 0: return None if size <= SLAB_THRESHOLD: sc: int = _slab_size_class(size) @@ -512,6 +599,9 @@ export def malloc(size: uint64) -> rawptr: _mb: [96]char viperlib.snprintf(&_mb, 96, "[mm] malloc(%lu) slab sc=%d -> NULL\n", size, sc) serial.puts(&_mb) + _last_user = uint64(result) + _last_size = size + _last_kind = 1 return result hdr: uint64 = memory_block.__sizeof__() if size >= 4096: @@ -522,6 +612,9 @@ export def malloc(size: uint64) -> rawptr: block: memory_block* = BuddySystemObject.allocate_block(total) if not block: return None user_ptr: uint64 = (uint64(block) + hdr + 4095) & ~uint64(4095) + _last_user = user_ptr + _last_size = size + _last_kind = 3 real_hdr: uint64 = user_ptr - 8 __asm__(f"""mov rax, {asminp(block, ASM_DESCR.REG_ANY)} mov [{asminp(rawptr(real_hdr), ASM_DESCR.REG_ANY)}], rax""", @@ -533,6 +626,9 @@ export def malloc(size: uint64) -> rawptr: if order2 > MAX_ORDER: return None block2: memory_block* = BuddySystemObject.allocate_block(total2) if not block2: return None + _last_user = uint64(block2) + hdr + _last_size = size + _last_kind = 2 return rawptr(uint64(block2) + hdr) @@ -549,6 +645,7 @@ export def malloc_direct(size: uint64) -> rawptr: export def free(ptr: rawptr): + _watch_check(2) if ptr is None: return sp: slab_page* = _slab_find_page(ptr) if sp: @@ -575,6 +672,67 @@ export def free(ptr: rawptr): block2: memory_block* = rawptr(uint64(ptr) - memory_block.__sizeof__()) BuddySystemObject.free_block(block2) +# 真正的 4KiB 页分配:整页归调用者,页首不再保留块头。 +# ---- 4KiB 页池:mmap/brk/fork 的页不再逐页进出 buddy ---- +# 旧方案 alloc_page() 直接向 buddy 要 order-0 块:用户写满整页会覆盖页首块头, +# 每次 free 都得重建头再 merge;一旦 free-list 有重复/陈旧表头,就会出现 +# "页已经分给字体缓冲,同时还挂在空闲链上" —— 字体数据直接把块头写成 ASCII +# (BAD blk ... magic=0x574d6f74)。这里改成独立页池: +# · 每 2MiB 向 buddy 申请一个 order-9 块(首 4KiB 留给块头,不发给用户) +# · 剩余 511 页串成页内 free-list,alloc/free 只改页首 8 字节 +# · 页永远不逐页归还 buddy,因此页数据永远不会碰到 buddy 块头/空闲链 +# 代价:每 2MiB 浪费 4KiB(0.2%),且不再把空闲页合并回 buddy。 +define PAGE_POOL_CHUNK_SIZE = 0x200000 +define PAGE_POOL_RESERVE = 0x1000 +define PAGE_POOL_MAX = 134217728 # 128MiB +static _page_free_head: rawptr = None + +def _pg_next(p: rawptr) -> rawptr: + return rawptr(*(uint64*(p))) + +def _pg_set_next(p: rawptr, n: rawptr): + *(uint64*(p)) = uint64(n) + +# 启动时一次性预留一大块给页池,之后再不向 buddy 要块。这样页池的块永远不会 +# 出现在 buddy 空闲链上,页数据也不可能踩 buddy 块头;页分配/释放只操作页内 +# free-list(页首 8 字节)。预留块首 4KiB 保留给 memory_block 头。 +def init_page_pool(): + global _page_free_head + if _page_free_head is not None: return + b: memory_block* = BuddySystemObject.allocate_block(uint64(PAGE_POOL_MAX)) + if not b: + b = BuddySystemObject.allocate_block(uint64(67108864)) # 64MiB + if not b: + b = BuddySystemObject.allocate_block(uint64(33554432)) # 32MiB + if not b: + b = BuddySystemObject.allocate_block(uint64(16777216)) # 16MiB + if not b: return + base: uint64 = uint64(b) + end: uint64 = base + b.size + first: uint64 = base + uint64(PAGE_POOL_RESERVE) + p: uint64 = first + while p < end: + nxt: uint64 = 0 + if p + uint64(4096) < end: nxt = p + uint64(4096) + _pg_set_next(rawptr(p), rawptr(nxt)) + p = p + uint64(4096) + _page_free_head = rawptr(first) + +export def alloc_page() -> rawptr: + global _page_free_head + if _page_free_head is None: return None + p: rawptr = _page_free_head + _page_free_head = _pg_next(p) + return p + +# 页池释放:只把页压回页内 free-list,不碰 buddy。 +export def free_page(ptr: rawptr): + global _page_free_head + if ptr is None: return + a: uint64 = uint64(ptr) & ~uint64(0xFFF) + _pg_set_next(rawptr(a), _page_free_head) + _page_free_head = rawptr(a) + export def calloc(nmemb: uint64, size: uint64) -> rawptr: total_size: uint64 = nmemb * size ptr: rawptr = malloc(total_size) @@ -620,7 +778,7 @@ def free_memory_regions(): # 初始化内存管理系统 def init(MemmapAddr: uint64, MemmapSize: uint64, MemmapDescSize: uint64, FbAddr: uint64 = 0, FbSize: uint64 = 0, KernelAddr: uint64 = 0, KernelEnd: uint64 = 0): - global BuddySystemObject + global BuddySystemObject, _raw_heap_start, _raw_page_count start_addr: rawptr = None size: uint64 = 0 if MemmapAddr != 0: @@ -703,7 +861,35 @@ def init(MemmapAddr: uint64, MemmapSize: uint64, MemmapDescSize: uint64, FbAddr: if size < min_blk2: size = 33554432 BuddySystemObject.__init__(start_addr, size) + _raw_heap_start = uint64(start_addr) + _raw_page_count = size >> 12 + if _raw_page_count > uint64(MAX_RAW_PAGES): _raw_page_count = uint64(MAX_RAW_PAGES) + init_page_pool() SlabAllocator.__init__() + _hl: [128]char + viperlib.snprintf(&_hl, 128, "[mm] heap start=0x%lx end=0x%lx size=0x%lx\n", + uint64(start_addr), uint64(start_addr) + size, size) + serial.puts(&_hl) + check_lists(0) + +# 空闲链头一致性检查:tag 只用于日志定位调用阶段。 +export def check_lists(tag: int): + i: int = 0 + bad: int = 0 + while i <= MAX_ORDER: + b: memory_block* = BuddySystemObject.buddy.free_lists[i] + if b is not None: + if uint64(b.magic) != uint64(BLOCK_MAGIC): + _cl: [128]char + viperlib.snprintf(&_cl, 128, "[mm] check%d order=%d bad head=0x%lx magic=0x%lx\n", + tag, i, uint64(b), uint64(uint32(b.magic))) + serial.puts(&_cl) + bad += 1 + if bad >= 3: return + i += 1 + _ok: [64]char + viperlib.snprintf(&_ok, 64, "[mm] check%d free-list heads ok\n", tag) + serial.puts(&_ok) # 虚拟内存映射结构 diff --git a/VKernel/Kernel/paging/paging.vp b/VKernel/Kernel/paging/paging.vp index 907c43e..89812bb 100644 --- a/VKernel/Kernel/paging/paging.vp +++ b/VKernel/Kernel/paging/paging.vp @@ -1,5 +1,6 @@ import drivers.serial.uart.serial as serial import viperlib +import string define PTE_PRESENT = (1 << 0) @@ -11,6 +12,17 @@ define PTE_ACCESSED = (1 << 5) define PTE_DIRTY = (1 << 6) define PTE_PS = (1 << 7) define PTE_GLOBAL = (1 << 8) +# 4KB PTE bit 7 = PAT select (for large pages the same bit is PS). +define PTE_PAT = (1 << 7) + +# PAT entry 4 is reprogrammed to write-combining (WC) for the framebuffer. +# IA32_PAT is configured in main.vp after CPUID tells us PAT is available. +_pat_wc: int = 0 +def set_pat_wc(): + global _pat_wc + _pat_wc = 1 +def has_pat_wc() -> int: + return _pat_wc class _p: entries: [512]uint64 @@ -138,17 +150,45 @@ def _split_1gb_to_pd(pdp_pa: uint64, pdp_idx: uint64) -> int: if not (e & PTE_PRESENT): return 1 if not (e & PTE_PS): return 1 base: uint64 = e & ~uint64(0x3FFFFFFF) - inherit: uint64 = e & (PTE_PCD | PTE_PWT | PTE_USER | uint64(0x8000000000000000)) + # 只继承缓存/执行属性,**绝不继承 PTE_USER**:这些 2MB 条目是 identity 映射, + # 用户态只能通过 MapPage 为具体 4KB 页显式加 USER。若把固件 1GB 条目的 U=1 + # 带进 2MB 叶条目,应用一旦越界写到尚未映射的 VA(例如 heap 0x21780000), + # 就会命中 identity 叶页、直接改物理内存里的 buddy 块头(BAD blk 源头)。 + inherit: uint64 = e & (PTE_PCD | PTE_PWT | uint64(0x8000000000000000)) new_pd: _p* = AllocPage() if not new_pd: return 0 for si in range(512): new_pd.entries[si] = (base + uint64(si) * uint64(0x200000)) | PTE_PRESENT | PTE_WRITABLE | PTE_PS | inherit - # 新 PDPTE 是**非叶条目**:只带 PRESENT|WRITABLE|USER,**绝不带 bit63(NX)** —— - # NX 生效位置是叶条目,已由上面 512 个 2MB 条目继承;非叶条目带 bit63 会让后续所有 - # `entry & ~0xFFF` 派生出的表指针变成非规范地址(正是刚修的 #GP 根因)。 - pdp_t.entries[pdp_idx] = (uint64(new_pd) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (inherit & PTE_USER) + # 新 PDPTE 是**非叶条目**:只带 PRESENT|WRITABLE;USER 由 MapPage 在真正建立 + # 用户页时逐级补上。非叶条目也不能带 bit63(NX)(见下)。 + pdp_t.entries[pdp_idx] = (uint64(new_pd) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE return 1 +# 把 1GiB 范围内"物理地址 == 虚拟地址"的 identity 叶条目的 USER 位清掉。 +# 参数 gb_base 必须是 1GiB 对齐的虚拟基址(pdp_idx << 30)。 +def _sanitize_identity_pd(pd: _p*, gb_base: uint64): + i: int = 0 + while i < 512: + e: uint64 = pd.entries[i] + if (e & PTE_PRESENT) == 0: + i += 1 + continue + va2m: uint64 = gb_base + uint64(i) * uint64(0x200000) + if (e & PTE_PS) != 0: + if (e & ~uint64(0x1FFFFF)) == va2m and (e & PTE_USER) != 0: + pd.entries[i] = e & ~PTE_USER + else: + pt_s: _p* = _p*(e & ~uint64(0xFFF)) + j: int = 0 + while j < 512: + ee: uint64 = pt_s.entries[j] + if (ee & PTE_PRESENT) != 0 and (ee & PTE_USER) != 0: + va4k: uint64 = va2m + uint64(j) * uint64(0x1000) + if (ee & ~uint64(0xFFF)) == va4k: + pt_s.entries[j] = ee & ~PTE_USER + j += 1 + i += 1 + def MapPage(VirtAddr: uint64, PhysAddr: uint64, flags: uint64): global pml4, pdp, pd, pt if VirtAddr == 0 or PhysAddr == 0: return @@ -274,6 +314,68 @@ def EnablePaging(): global pml4, pdp, pd, pt __asm__(f"mov cr3, {asminp(uint64(pml4), ASM_DESCR.REG_ANY)}") +# Physical address backing a 4KB user page, or 0 if unmapped / a huge page. +# munmap() needs this before UnMapPage() clears the PTE, so it can hand the +# frame back to the buddy allocator. +# (Local helper: _table_phys is defined later in this module and Viper does +# not allow same-module forward references.) +def _entry_table(e: uint64) -> uint64: + if (e & PTE_PRESENT) == 0: return 0 + if (e & uint64(0x7FF0000000000000)) != 0: return 0 + return e & uint64(0x000FFFFFFFFFF000) + +def GetPagePhys(VirtAddr: uint64) -> uint64: + if VirtAddr == 0: return 0 + p4: _p* = pml4 + if not p4: return 0 + va: uint64 = VirtAddr & ~uint64(0xFFF) + i4: uint64 = (va >> 39) & 0x1FF + pdp_pa: uint64 = _entry_table(p4.entries[i4]) + if pdp_pa == 0: return 0 + p3: _p* = _p*(pdp_pa) + i3: uint64 = (va >> 30) & 0x1FF + if p3.entries[i3] & PTE_PS: return 0 + pd_pa: uint64 = _entry_table(p3.entries[i3]) + if pd_pa == 0: return 0 + p2: _p* = _p*(pd_pa) + i2: uint64 = (va >> 21) & 0x1FF + if p2.entries[i2] & PTE_PS: return 0 + pt_pa: uint64 = _entry_table(p2.entries[i2]) + if pt_pa == 0: return 0 + p1: _p* = _p*(pt_pa) + i1: uint64 = (va >> 12) & 0x1FF + e: uint64 = p1.entries[i1] + if not (e & PTE_PRESENT): return 0 + return e & uint64(0x000FFFFFFFFFF000) + +# Rewrite the protection bits of an existing 4KB mapping, keeping the frame. +# Returns 1 on success, 0 if the page is not a present 4KB page. +def SetPageFlags(VirtAddr: uint64, flags: uint64) -> int: + if VirtAddr == 0: return 0 + p4: _p* = pml4 + if not p4: return 0 + va: uint64 = VirtAddr & ~uint64(0xFFF) + i4: uint64 = (va >> 39) & 0x1FF + pdp_pa: uint64 = _entry_table(p4.entries[i4]) + if pdp_pa == 0: return 0 + p3: _p* = _p*(pdp_pa) + i3: uint64 = (va >> 30) & 0x1FF + if p3.entries[i3] & PTE_PS: return 0 + pd_pa: uint64 = _entry_table(p3.entries[i3]) + if pd_pa == 0: return 0 + p2: _p* = _p*(pd_pa) + i2: uint64 = (va >> 21) & 0x1FF + if p2.entries[i2] & PTE_PS: return 0 + pt_pa: uint64 = _entry_table(p2.entries[i2]) + if pt_pa == 0: return 0 + p1: _p* = _p*(pt_pa) + i1: uint64 = (va >> 12) & 0x1FF + e: uint64 = p1.entries[i1] + if not (e & PTE_PRESENT): return 0 + p1.entries[i1] = (e & uint64(0x000FFFFFFFFFF000)) | flags | PTE_PRESENT + __asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]") + return 1 + # Extract the physical address of a non-leaf page-table entry. # Bits 12..51 hold the address; bits 52..62 are reserved and must be zero. # Bit 63 (NX) is ignored for non-leaf entries. Returns 0 for unusable entries. @@ -505,7 +607,11 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64: if not new_pdp_page: return uint64(new_pml4_page) for i in range(512): new_pdp_page.entries[i] = kernel_pdp.entries[i] - new_pml4_page.entries[pml4_idx] = (uint64(new_pdp_page) & ~uint64(0xFFF)) | (pml4.entries[pml4_idx] & uint64(0xFFF)) + # 固件 identity 大页可能带 U=1:克隆到进程地址空间后必须降为 supervisor。 + # 用户页的 USER 位由后续 MapPage 在具体 4KB PTE 上显式添加。 + if (new_pdp_page.entries[i] & PTE_PRESENT) != 0 and (new_pdp_page.entries[i] & PTE_PS) != 0: + new_pdp_page.entries[i] = new_pdp_page.entries[i] & ~PTE_USER + new_pml4_page.entries[pml4_idx] = (uint64(new_pdp_page) & ~uint64(0xFFF)) | (pml4.entries[pml4_idx] & uint64(0xFFF) & ~PTE_USER) pdp_idx: uint64 = (isolate_vaddr >> 30) & 0x1FF if not (kernel_pdp.entries[pdp_idx] & PTE_PRESENT): return uint64(new_pml4_page) # 1GB 大页(PDPTE.PS=1):源端没有下层 PD。先把它拆成常规 PD(语义不变:512 个 2MB @@ -515,11 +621,19 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64: if kernel_pdp.entries[pdp_idx] & PTE_PS: if _split_1gb_to_pd(uint64(kernel_pdp), pdp_idx) == 0: return uint64(new_pml4_page) kernel_pd: _p* = _p*(kernel_pdp.entries[pdp_idx] & ~uint64(0xFFF)) + # 固件 identity 映射可能把 4KB/2MB 叶条目的 U 置 1。只要 U=1,用户态一旦 + # 写到尚未显式映射的 VA(应用越界写),就会直接落到**同数值物理页**上—— + # buddy 块头、页表、别人的数据全会被踩(现场:1GiB 下 0x21780000 的 + # order-16 空闲块头被清零)。这里把该 1GiB 内"物理地址 == 虚拟地址"的 + # identity 叶条目降为 supervisor;真正的用户页(phys != va)保持原样。 + _sanitize_identity_pd(kernel_pd, isolate_vaddr & ~uint64(0x3FFFFFFF)) new_pd_page: _p* = AllocPage() if not new_pd_page: return uint64(new_pml4_page) for i in range(512): new_pd_page.entries[i] = kernel_pd.entries[i] - new_pdp_page.entries[pdp_idx] = (uint64(new_pd_page) & ~uint64(0xFFF)) | (kernel_pdp.entries[pdp_idx] & uint64(0xFFF)) + if (new_pd_page.entries[i] & PTE_PRESENT) != 0 and (new_pd_page.entries[i] & PTE_PS) != 0: + new_pd_page.entries[i] = new_pd_page.entries[i] & ~PTE_USER + new_pdp_page.entries[pdp_idx] = (uint64(new_pd_page) & ~uint64(0xFFF)) | (kernel_pdp.entries[pdp_idx] & uint64(0xFFF) & ~PTE_USER) pd_idx: uint64 = (isolate_vaddr >> 21) & 0x1FF if not (kernel_pd.entries[pd_idx] & PTE_PRESENT): return uint64(new_pml4_page) if kernel_pd.entries[pd_idx] & PTE_PS: return uint64(new_pml4_page) @@ -527,10 +641,88 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64: new_pt_page: _p* = AllocPage() if not new_pt_page: return uint64(new_pml4_page) for i in range(512): - new_pt_page.entries[i] = kernel_pt.entries[i] - new_pd_page.entries[pd_idx] = (uint64(new_pt_page) & ~uint64(0xFFF)) | (kernel_pd.entries[pd_idx] & uint64(0xFFF)) + new_pt_page.entries[i] = kernel_pt.entries[i] & ~PTE_USER + new_pd_page.entries[pd_idx] = (uint64(new_pt_page) & ~uint64(0xFFF)) | (kernel_pd.entries[pd_idx] & uint64(0xFFF) & ~PTE_USER) return uint64(new_pml4_page) +# Deep-copy every USER mapping from src into dst (dst must already contain the +# kernel mappings, e.g. from create_process_page_table()). Data frames are +# allocated through alloc_fn(4096); page-table pages come from the paging pool. +# +# A table is only duplicated when its subtree really contains user pages; +# otherwise the parent's table is shared. Without that check a single user +# page at 0x400000 drags the whole kernel identity map (one PT page per 2MB +# region) into the new address space and exhausts the page-table pool. +def _alloc_user_frame(alloc_fn: rawptr) -> rawptr: + newp: rawptr = None + __asm__(f"""mov r11, {asminp(alloc_fn, ASM_DESCR.REG_ANY)} + push rbp + mov rbp, rsp + and rsp, -16 + mov rdi, 4096 + call r11 + mov rsp, rbp + pop rbp + mov {asmout(newp, ASM_DESCR.OUTPUT_REG)}, rax""", + op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX, + ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX, + ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDI, + ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, + ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) + return newp + +# level: 1 = PT, 2 = PD, 3 = PDPT (4 = PML4 only used as the entry point). +def _table_has_user(t: _p*, level: int) -> int: + i: int = 0 + while i < 512: + e: uint64 = t.entries[i] + if (e & PTE_PRESENT) != 0: + if level == 1: + if (e & PTE_USER) != 0: return 1 + elif (e & PTE_PS) != 0: + if (e & PTE_USER) != 0: return 1 + elif (e & PTE_USER) != 0: + if _table_has_user(_p*(e & ~uint64(0xFFF)), level - 1) != 0: return 1 + i += 1 + return 0 + +def _copy_user_table(src: _p*, dst: _p*, level: int, alloc_fn: rawptr) -> int: + i: int = 0 + while i < 512: + e: uint64 = src.entries[i] + if (e & PTE_PRESENT) == 0: + dst.entries[i] = e + elif level == 1: + if (e & PTE_USER) == 0: + dst.entries[i] = e + else: + src_phys: uint64 = e & uint64(0x000FFFFFFFFFF000) + newp: rawptr = _alloc_user_frame(alloc_fn) + if newp is None: return -1 + string.memcpy(newp, rawptr(src_phys), 4096) + dst.entries[i] = (uint64(newp) & ~uint64(0xFFF)) | (e & uint64(0xFFF)) + elif (e & PTE_PS) != 0: + dst.entries[i] = e + elif (e & PTE_USER) == 0: + dst.entries[i] = e + else: + csrc: _p* = _p*(e & ~uint64(0xFFF)) + if _table_has_user(csrc, level - 1) == 0: + dst.entries[i] = e + else: + np: _p* = AllocPage() + if not np: return -1 + dst.entries[i] = (uint64(np) & ~uint64(0xFFF)) | (e & uint64(0xFFF)) + if _copy_user_table(csrc, np, level - 1, alloc_fn) != 0: return -1 + i += 1 + return 0 + +def CopyUserSpace(src_pa: uint64, dst_pa: uint64, alloc_fn: rawptr) -> int: + if src_pa == 0 or dst_pa == 0: return -1 + s4: _p* = _p*(src_pa) + d4: _p* = _p*(dst_pa) + return _copy_user_table(s4, d4, 4, alloc_fn) + def save_pt_range(vaddr_start: uint64, vaddr_end: uint64, save_buf: uint64*, save_count: uint32*) -> int: global pml4 if not pml4: return -1 diff --git a/VKernel/Kernel/sched/process.vp b/VKernel/Kernel/sched/process.vp index ca5143d..03bdf7e 100644 --- a/VKernel/Kernel/sched/process.vp +++ b/VKernel/Kernel/sched/process.vp @@ -12,15 +12,21 @@ define PROC_READY = 0 define PROC_RUNNING = 1 define PROC_BLOCKED = 2 define PROC_DONE = 3 -define MAX_PROCESSES = 8 +define MAX_PROCESSES = 32 define MAX_PROC_THREADS = 8 define PROC_MAX_FDS = 16 # fd 种类(process.fd_kind[])。定义放这里、由 intr/syscall.vp 以 process.FD_KIND_* 引用, # 这样 process.vp 不必 import devfs —— devfs → keyboard/mouse → intr.idt → sched → process # 会形成环,不能让 process 反向依赖 devfs。 define FD_KIND_FAT32 = 0 -define FD_KIND_DEVFS = 1 +define FD_KIND_DEVFS = 1 +define FD_KIND_DIR = 2 +define FD_KIND_PIPE = 3 define PROC_MAX_DIRS = 8 +define MAX_VMAS = 64 +define VMA_KIND_ANON = 1 +define VMA_KIND_DEV = 2 +define VMA_KIND_FILE = 3 define KERN_STACK_SIZE = 8192 define USER_STACK_SIZE = 65536 # 用户栈虚拟区:主线程栈顶 = USER_STACK_VTOP(见 execrunner/elf.vp 的映射), @@ -42,17 +48,39 @@ class Process: heap_start: uint64 heap_size: uint64 name: [32]char + cwd: [256]char fd_table: [PROC_MAX_FDS]rawptr fd_used: [PROC_MAX_FDS]uint8 # 0 = FAT32 文件(fd_table[i] 里是 fileobj*);1 = devfs 设备(fd_table[i] 里是 # devfs_fd+1,+1 只为避开"devfs fd 0"与空指针的歧义)。READ/WRITE/CLOSE/IOCTL 与 # Process.exit 都必须按它分派 —— 把 devfs fd 当 fileobj* 解引用会直接跑飞。 fd_kind: [PROC_MAX_FDS]uint8 + fd_flags: [PROC_MAX_FDS]uint32 + fd_shared: [PROC_MAX_FDS]uint8 dir_table: [PROC_MAX_DIRS]rawptr dir_used: [PROC_MAX_DIRS]uint8 dir_pool: [PROC_MAX_DIRS]fat32_types.dirobj kern_stack: uint64 user_stack: uint64 + # Virtual memory areas backing mmap()/mprotect()/munmap()/mremap(). + vma_base: [MAX_VMAS]uint64 + vma_size: [MAX_VMAS]uint64 + vma_prot: [MAX_VMAS]uint32 + vma_kind: [MAX_VMAS]uint8 + vma_flags: [MAX_VMAS]uint32 + # brk() region (separate from the mmap bump allocator). + brk_start: uint64 + brk_end: uint64 + # Linux process metadata. + entry_rsp: uint64 + parent_pid: int + exit_code: int + # Signals (per process; delivery happens on syscall return). + sig_handler: [65]uint64 + sig_actflags: [65]uint64 + sig_restorer: [65]uint64 + sig_mask: uint64 + sig_pending: uint64 def addThread(self, tid: int) -> int: if self.thread_count >= MAX_PROC_THREADS: return -1 @@ -69,11 +97,12 @@ class Process: # 当指针解引用而跑飞。devfs 的 fd 表是独立的另一张;正规退出路径本来就不 # 回收 fd(见 intr/syscall.vp 的 EXIT 不调 Process.exit),这里只清槽位, # app 自己调 CLOSE 时才走 devfs_close。 - if fp and self.fd_kind[i] == FD_KIND_FAT32: + if fp and self.fd_kind[i] == FD_KIND_FAT32 and self.fd_shared[i] == 0: fat32.close(fp) self.fd_used[i] = 0 self.fd_table[i] = None self.fd_kind[i] = 0 + self.fd_shared[i] = 0 for i in range(PROC_MAX_DIRS): if self.dir_used[i]: dp: rawptr = self.dir_table[i] @@ -125,6 +154,7 @@ class ProcessManager: p.name[i] = ch i += 1 p.name[i] = 0 + string.strcpy(&p.cwd[0], "/") j: int for j in range(MAX_PROC_THREADS): p.threads[j] = -1 @@ -132,9 +162,28 @@ class ProcessManager: p.fd_used[j] = 0 p.fd_table[j] = None p.fd_kind[j] = 0 + p.fd_flags[j] = 0 + p.fd_shared[j] = 0 for j in range(PROC_MAX_DIRS): p.dir_used[j] = 0 p.dir_table[j] = None + for j in range(MAX_VMAS): + p.vma_base[j] = 0 + p.vma_size[j] = 0 + p.vma_prot[j] = 0 + p.vma_kind[j] = 0 + p.vma_flags[j] = 0 + p.brk_start = 0 + p.brk_end = 0 + p.entry_rsp = 0 + p.parent_pid = 0 + p.exit_code = 0 + p.sig_mask = 0 + p.sig_pending = 0 + for j in range(65): + p.sig_handler[j] = 0 + p.sig_actflags[j] = 0 + p.sig_restorer[j] = 0 __asm__(f"""mov {asmout(p.pml4_root, ASM_DESCR.OUTPUT_REG)}, cr3""", op=[ASM_DESCR.CLOBBER_MEMORY]) @@ -171,7 +220,10 @@ class ProcessManager: @staticmethod def create_process(name: str, entry: rawptr) -> Process*: m: ProcessManager* = _proc_mgr_ptr - if m.count >= MAX_PROCESSES: return None + if m.count >= MAX_PROCESSES: + # 启动时已有 1(内核)+7(app) 个槽;旧上限 8 导致 run/runb 永远失败。 + serial.puts("[process] create_process: pid table full\n") + return None pid: int = m.count m.count += 1 p: Process* = &m.processes[pid] @@ -189,17 +241,38 @@ class ProcessManager: p.name[i] = ch i += 1 p.name[i] = 0 + string.strcpy(&p.cwd[0], "/") for j in range(MAX_PROC_THREADS): p.threads[j] = -1 for j in range(PROC_MAX_FDS): p.fd_used[j] = 0 p.fd_table[j] = None p.fd_kind[j] = 0 + p.fd_flags[j] = 0 + p.fd_shared[j] = 0 for j in range(PROC_MAX_DIRS): p.dir_used[j] = 0 p.dir_table[j] = None + for j in range(MAX_VMAS): + p.vma_base[j] = 0 + p.vma_size[j] = 0 + p.vma_prot[j] = 0 + p.vma_kind[j] = 0 + p.vma_flags[j] = 0 + p.brk_start = 0 + p.brk_end = 0 + p.entry_rsp = 0 + p.parent_pid = 0 + p.exit_code = 0 + p.sig_mask = 0 + p.sig_pending = 0 + for j in range(65): + p.sig_handler[j] = 0 + p.sig_actflags[j] = 0 + p.sig_restorer[j] = 0 ks: rawptr = mm.malloc(KERN_STACK_SIZE) if ks is None: + serial.puts("[process] kernel stack alloc failed\n") m.count = m.count - 1 return None ks_size: uint64 = KERN_STACK_SIZE @@ -302,6 +375,60 @@ def drop_to_user_mode(entry: rawptr, user_rsp: uint64, kern_rsp: uint64, arg: ra ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_RBP]) +# Resume context used by fork/clone children: they start user mode at the +# parent's post-syscall RIP with a full register set and RAX=0. +export _resume_ctx: [20]uint64 + +def set_resume(rip: uint64, rsp: uint64, flags: uint64, rax: uint64, regs: uint64*): + _resume_ctx[0] = rip + _resume_ctx[1] = rsp + _resume_ctx[2] = flags + _resume_ctx[3] = rax + i: int = 0 + while i < 12: + _resume_ctx[4 + i] = regs[i] + i += 1 + +def drop_to_user_mode_ctx(kern_rsp: uint64): + __asm__(f"""cli + mov rsp, {asminp(kern_rsp, ASM_DESCR.REG_ANY)} + mov ecx, 0xC0000101 + rdmsr + shl rdx, 32 + or rax, rdx + test rax, rax + jz 1f + swapgs + 1: + lea rbx, [rip + _resume_ctx] + mov r15, [rbx+32] + mov r14, [rbx+40] + mov r13, [rbx+48] + mov r12, [rbx+56] + mov r10, [rbx+64] + mov r9, [rbx+72] + mov r8, [rbx+80] + mov rbp, [rbx+88] + mov rdi, [rbx+96] + mov rsi, [rbx+104] + mov rdx, [rbx+112] + mov rcx, [rbx+0] + mov r11, [rbx+16] + mov rax, [rbx+24] + push 0x23 + push qword ptr [rbx+8] + push r11 + push 0x2B + push rcx + mov rbx, [rbx+120] + iretq""", + op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX, + ASM_DESCR.CLOBBER_RBX, ASM_DESCR.CLOBBER_RCX, + ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, + ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_R8, + ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10, + ASM_DESCR.CLOBBER_RBP]) + # ============================================================ # 用户态线程(THREAD_CREATE)上下文 diff --git a/VKernel/Kernel/sched/sched.vp b/VKernel/Kernel/sched/sched.vp index 53f4a90..0873216 100644 --- a/VKernel/Kernel/sched/sched.vp +++ b/VKernel/Kernel/sched/sched.vp @@ -10,7 +10,7 @@ define THREAD_READY = 0 define THREAD_RUNNING = 1 define THREAD_BLOCKED = 2 define THREAD_DONE = 3 -define MAX_THREADS = 16 +define MAX_THREADS = 64 define THREAD_STACK_SIZE = 65536 define SCHED_QUANTUM = 10 @@ -21,6 +21,34 @@ export _isr_scratch_tbl: [MAX_THREADS * 6]uint64 export _switch_old_tid: int = 0 export _switch_new_tid: int = 0 +# Per-thread IA32_FS_BASE (glibc TLS). Kept outside Thread: Thread has a +# hard-coded 600-byte layout checked by _thread_entry. +define IA32_FS_BASE = 0xC0000100 +_fs_base_tbl: [MAX_THREADS]uint64 +_syscall_frame_tbl: [MAX_THREADS]uint64 +_clear_child_tid: [MAX_THREADS]uint64 + +def _msr_read(msr: uint32) -> uint64: + lo: uint32 + hi: uint32 + __asm__(f"""mov ecx, {asminp(msr, ASM_DESCR.REG_ANY)} + rdmsr + mov {asmout(lo, ASM_DESCR.OUTPUT_REG)}, eax + mov {asmout(hi, ASM_DESCR.OUTPUT_REG)}, edx""", + out=[asmout(lo, ASM_DESCR.OUTPUT_REG), asmout(hi, ASM_DESCR.OUTPUT_REG)], + op=[ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RDX]) + return uint64(lo) | (uint64(hi) << 32) + +def _msr_write(msr: uint32, val: uint64): + lo: uint32 = uint32(val & uint64(0xFFFFFFFF)) + hi: uint32 = uint32(val >> 32) + __asm__(f"""mov ecx, {asminp(msr, ASM_DESCR.REG_ANY)} + mov eax, {asminp(lo, ASM_DESCR.REG_ANY)} + mov edx, {asminp(hi, ASM_DESCR.REG_ANY)} + wrmsr""", + op=[ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RDX]) + + class Thread: rsp: uint64 @@ -103,12 +131,12 @@ class Scheduler: __asm__(f"""mov [{asminp(main_rsp_ptr, ASM_DESCR.REG_ANY)}], rsp""", op=[ASM_DESCR.CLOBBER_MEMORY]) # fail-fast:_thread_entry 的 asm 以硬编码偏移访问线程表(sizeof(Thread)=600、 - # current_tid=+9600=16*600)。增删 Thread 字段会让它们失配 ⇒ 线程入口会按错误的 + # current_tid=+38400=64*600)。增删 Thread 字段会让它们失配 ⇒ 线程入口会按错误的 # 偏移取 func/arg 并 call 任意地址。这里直接核对,不符立即停机暴露。 thr0_addr: uint64 = uint64(&s.threads[0]) curt_addr: uint64 = uint64(&s.current_tid) - if Thread.__sizeof__() != 600 or (curt_addr - thr0_addr) != 9600: - serial.puts("[sched] FATAL: Thread 布局与 _thread_entry 硬编码偏移(600/9600)不一致\n") + if Thread.__sizeof__() != 600 or (curt_addr - thr0_addr) != 38400: + serial.puts("[sched] FATAL: Thread 布局与 _thread_entry 硬编码偏移(600/38400)不一致\n") while True: asm.sti() asm.hlt() @@ -117,7 +145,8 @@ class Scheduler: def _yield(): global _switch_old_rsp, _switch_new_rsp, _yield_cnt, _yield_lock global _switch_old_tid, _switch_new_tid - global _switch_old_fpu, _switch_new_fpu + global _switch_old_fpu, _switch_new_fpu + global _fs_base_tbl _if_saved: int = 0 __asm__(f"""pushfq pop rax @@ -176,6 +205,9 @@ class Scheduler: cpu.set_kernel_rsp0(next_ks) _switch_old_tid = old_tid _switch_new_tid = next_tid + # Per-thread TLS: save the outgoing FS base, load the incoming one. + _fs_base_tbl[old_tid] = _msr_read(uint32(IA32_FS_BASE)) + _msr_write(uint32(IA32_FS_BASE), _fs_base_tbl[next_tid]) _yield_lock = 0 _do_switch() if _if_saved: @@ -306,8 +338,14 @@ class Scheduler: 现场 [0E] rip=0x0 err=0x15(用户态取指缺页)cr2=0x0 rsp=0x7fffffde0000,tid 为该 app 线程, 紧跟在 [syscall] exit called 之后。 """ + global _clear_child_tid s: Scheduler* = _sched_ptr tid: int = s.current_tid + ct: uint64 = _clear_child_tid[tid] + if ct != 0: + cw: uint32* = uint32*(ct) + cw[0] = uint32(0) + _clear_child_tid[tid] = 0 if tid > 0 and tid < MAX_THREADS: s.threads[tid].state = THREAD_DONE proc.free_user_thread(proc.find_user_slot_by_tid(tid)) @@ -318,6 +356,33 @@ class Scheduler: asm.hlt() Scheduler._yield() + @staticmethod + def exit_process(pid: int): + """Linux exit_group: retire every thread of `pid`, release the process, never return.""" + global _clear_child_tid + s: Scheduler* = _sched_ptr + i: int = 0 + while i < s.thread_count: + if s.threads[i].pid == pid: + ct: uint64 = _clear_child_tid[i] + if ct != 0: + cw: uint32* = uint32*(ct) + cw[0] = uint32(0) + _clear_child_tid[i] = 0 + s.threads[i].state = THREAD_DONE + slot: int = proc.find_user_slot_by_tid(i) + if slot >= 0: + proc.free_user_thread(slot) + i += 1 + p: proc.Process* = proc.ProcessManager.get_process(pid) + if p is not None: + p.exit() + Scheduler._yield() + while True: + asm.sti() + asm.hlt() + Scheduler._yield() + _sched_storage: Scheduler = Scheduler() export _sched_ptr: Scheduler* = &_sched_storage @@ -326,6 +391,34 @@ export _switch_new_rsp: rawptr export _switch_old_fpu: rawptr export _switch_new_fpu: rawptr +def set_fs_base(base: uint64): + global _fs_base_tbl + s: Scheduler* = _sched_ptr + _fs_base_tbl[s.current_tid] = base + _msr_write(uint32(IA32_FS_BASE), base) + +def set_thread_fs_base(tid: int, base: uint64): + global _fs_base_tbl + if tid >= 0 and tid < MAX_THREADS: + _fs_base_tbl[tid] = base + +def get_fs_base() -> uint64: + return _msr_read(uint32(IA32_FS_BASE)) + +def set_syscall_frame(f: uint64): + global _syscall_frame_tbl + s: Scheduler* = _sched_ptr + _syscall_frame_tbl[s.current_tid] = f + +def get_syscall_frame() -> uint64: + s: Scheduler* = _sched_ptr + return _syscall_frame_tbl[s.current_tid] + +def set_clear_child_tid(tid: int, addr: uint64): + global _clear_child_tid + if tid >= 0 and tid < MAX_THREADS: + _clear_child_tid[tid] = addr + @__attribute__(naked) def _do_switch(): __asm__(f"""push rax @@ -394,20 +487,20 @@ def _do_switch(): op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX]) # Thread 布局:rsp0/stack8/stack_size16/func24/arg32/state40/tid44/pid48/kern_stack56/fpu_area64/fpu_ptr592 -# ⇒ sizeof(Thread) = 600,Scheduler.threads[16] 占 9600 字节 ⇒ current_tid 在 +9600。 -# 增删 Thread 字段时必须同步下面 asm 里的 9600 / 600(以及 +24/+32/+40 三个字段偏移)。 +# ⇒ sizeof(Thread) = 600,Scheduler.threads[64] 占 38400 字节 ⇒ current_tid 在 +38400。 +# 增删 Thread 字段时必须同步下面 asm 里的 38400 / 600(以及 +24/+32/+40 三个字段偏移)。 @__attribute__(naked) def _thread_entry(): __asm__(f"""sub rsp, 8 mov rax, [rip + _sched_ptr] - mov ecx, dword ptr [rax + 9600] + mov ecx, dword ptr [rax + 38400] movsxd rcx, ecx imul rcx, rcx, 600 mov rdi, [rax + rcx + 32] mov rax, [rax + rcx + 24] call rax mov rax, [rip + _sched_ptr] - mov ecx, dword ptr [rax + 9600] + mov ecx, dword ptr [rax + 38400] movsxd rcx, ecx imul rcx, rcx, 600 mov dword ptr [rax + rcx + 40], 3 diff --git a/VKernel/project.vpj b/VKernel/project.vpj index 46519f9..47cc60c 100644 --- a/VKernel/project.vpj +++ b/VKernel/project.vpj @@ -23,7 +23,9 @@ "output": "kernel.elf" }, "includes": [ - "../../../includes" + "../..", + "../../../includes", + "../" ], "target": { "triple": "x86_64-none-elf", diff --git a/abi/__init__.vp b/abi/__init__.vp new file mode 100644 index 0000000..3f7e353 --- /dev/null +++ b/abi/__init__.vp @@ -0,0 +1,2 @@ +# Package marker for the shared Linux ABI module. +define ABI_VERSION = 1 diff --git a/abi/linux.vp b/abi/linux.vp new file mode 100644 index 0000000..ee3d0f5 --- /dev/null +++ b/abi/linux.vp @@ -0,0 +1,206 @@ +# Linux x86-64 ABI constants and structures shared by the kernel and vpsdk. +# +# The byte layouts must match Linux exactly: glibc reads these structs +# directly and never goes through any ViperOS wrapper. +# +# This module is imported by both sides: +# VKernel/Kernel/intr/syscall.vp (kernel project gains "../.." include) +# vpsdk/syscall.vp / vpsdk/fs.vp (apps already include the repo root) + +# ---------------------------------------------------------------- errno ---- +define EPERM = 1 +define ENOENT = 2 +define ESRCH = 3 +define EINTR = 4 +define EIO = 5 +define ENXIO = 6 +define E2BIG = 7 +define ENOEXEC = 8 +define EBADF = 9 +define ECHILD = 10 +define EAGAIN = 11 +define ENOMEM = 12 +define EACCES = 13 +define EFAULT = 14 +define ENOTBLK = 15 +define EBUSY = 16 +define EEXIST = 17 +define EXDEV = 18 +define ENODEV = 19 +define ENOTDIR = 20 +define EISDIR = 21 +define EINVAL = 22 +define ENFILE = 23 +define EMFILE = 24 +define ENOTTY = 25 +define ETXTBSY = 26 +define EFBIG = 27 +define ENOSPC = 28 +define ESPIPE = 29 +define EROFS = 30 +define EMLINK = 31 +define EPIPE = 32 +define EDOM = 33 +define ERANGE = 34 +define EDEADLK = 35 +define ENAMETOOLONG = 36 +define ENOLCK = 37 +define ENOSYS = 38 +define ENOTEMPTY = 39 +define ELOOP = 40 +define EOVERFLOW = 75 +define ENOTSUP = 95 +define EOPNOTSUPP = 95 +define ETIMEDOUT = 110 + +# ------------------------------------------------------------ open flags ---- +# Values are the Linux x86-64 ones (octal in the man page). +define O_RDONLY = 0 +define O_WRONLY = 1 +define O_RDWR = 2 +define O_ACCMODE = 3 +define O_CREAT = 64 +define O_EXCL = 128 +define O_NOCTTY = 256 +define O_TRUNC = 512 +define O_APPEND = 1024 +define O_NONBLOCK = 2048 +define O_DSYNC = 4096 +define O_DIRECT = 16384 +define O_DIRECTORY = 65536 +define O_NOFOLLOW = 131072 +define O_CLOEXEC = 524288 + +# --------------------------------------------------------------- fcntl ---- +define F_DUPFD = 0 +define F_GETFD = 1 +define F_SETFD = 2 +define F_GETFL = 3 +define F_SETFL = 4 +define F_DUPFD_CLOEXEC = 1030 +define FD_CLOEXEC = 1 + +# ----------------------------------------------------------- at / dirent ---- +define AT_FDCWD = -100 +define AT_SYMLINK_NOFOLLOW = 256 +define AT_REMOVEDIR = 512 +define AT_EACCESS = 512 +define AT_EMPTY_PATH = 4096 + +define DT_UNKNOWN = 0 +define DT_FIFO = 1 +define DT_CHR = 2 +define DT_DIR = 4 +define DT_BLK = 6 +define DT_REG = 8 +define DT_LNK = 10 +define DT_SOCK = 12 + +# ------------------------------------------------------------ mmap/mprot ---- +define PROT_NONE = 0 +define PROT_READ = 1 +define PROT_WRITE = 2 +define PROT_EXEC = 4 +define MAP_SHARED = 1 +define MAP_PRIVATE = 2 +define MAP_FIXED = 16 +define MAP_ANONYMOUS = 32 +define MAP_GROWSDOWN = 256 +define MAP_NORESERVE = 16384 +define MAP_POPULATE = 32768 +define MAP_FAILED: uint64 = 0xFFFFFFFFFFFFFFFF + +# ---------------------------------------------------------------- mremap ---- +define MREMAP_MAYMOVE = 1 +define MREMAP_FIXED = 2 + +# ------------------------------------------------------------- arch_prctl ---- +define ARCH_SET_GS = 0x1001 +define ARCH_SET_FS = 0x1002 +define ARCH_GET_FS = 0x1003 +define ARCH_GET_GS = 0x1004 + +# ---------------------------------------------------------------- rlimit ---- +define RLIMIT_CPU = 0 +define RLIMIT_FSIZE = 1 +define RLIMIT_DATA = 2 +define RLIMIT_STACK = 3 +define RLIMIT_CORE = 4 +define RLIMIT_NOFILE = 7 +define RLIM_INFINITY: uint64 = 0xFFFFFFFFFFFFFFFF + +# ------------------------------------------------------------- file modes ---- +define S_IFMT = 61440 +define S_IFDIR = 16384 +define S_IFCHR = 8192 +define S_IFIFO = 4096 +define S_IFREG = 32768 +define MODE_FILE = 33188 # 0100644 +define MODE_DIR = 16877 # 040755 +define MODE_CHAR = 8630 # 020666 +define MODE_FIFO = 4534 # 010666 + +define SEEK_SET = 0 +define SEEK_CUR = 1 +define SEEK_END = 2 + +define PATH_MAX = 256 +define SIZEOF_LINUX_STAT = 144 +define SIZEOF_UTSNAME = 390 +define SIZEOF_DIRENT64 = 19 + +# ---------------------------------------------------------------- structs ---- +# struct stat, x86-64 Linux layout (144 bytes). +@__attribute__(packed) +class linux_stat: + st_dev: uint64 + st_ino: uint64 + st_nlink: uint64 + st_mode: uint32 + st_uid: uint32 + st_gid: uint32 + pad0: uint32 + st_rdev: uint64 + st_size: uint64 + st_blksize: uint64 + st_blocks: uint64 + st_atime_sec: uint64 + st_atime_nsec: uint64 + st_mtime_sec: uint64 + st_mtime_nsec: uint64 + st_ctime_sec: uint64 + st_ctime_nsec: uint64 + res0: uint64 + res1: uint64 + res2: uint64 + +# struct linux_dirent64 header; the NUL-terminated name follows immediately. +@__attribute__(packed) +class linux_dirent64: + d_ino: uint64 + d_off: uint64 + d_reclen: uint16 + d_type: uint8 + +@__attribute__(packed) +class linux_timespec: + tv_sec: uint64 + tv_nsec: uint64 + +@__attribute__(packed) +class linux_utsname: + sysname: [65]char + nodename: [65]char + release: [65]char + version: [65]char + machine: [65]char + domainname: [65]char + +@__attribute__(packed) +class linux_rlimit: + cur: uint64 + max: uint64 + +class iovec: + base: rawptr + len: uint64 diff --git a/build.py b/build.py index 636d206..f3777b6 100644 --- a/build.py +++ b/build.py @@ -7,7 +7,7 @@ import subprocess # ============================================================================ # viperc 编译器路径 —— 唯一来源,改这里即可 # ============================================================================ -VIPERC = r'..\..\ViperC\__vpcbuild__\output\viperc.exe' +VIPERC = r'..\..\ViperC\__vpcbuild6__\output\viperc.exe' # 解析参数 @@ -17,6 +17,18 @@ noapp = '--noapp' in sys.argv bootloader = '--bootloader' in sys.argv # --onlyrun:跳过全部编译/打包,直接用现有 disk.img 启动 QEMU only_run = '--onlyrun' in sys.argv +# --headless:QEMU 不开窗口(-display none),串口仍写入 serial.log。 +# 自动化调试/无人值守验证时使用;需要看画面时不要带这个参数。 +headless = '--headless' in sys.argv +# --runsecs=N:启动 N 秒后自动结束 QEMU(0=一直运行到客户机退出)。 +# 与 --headless 搭配可用于无人值守的冒烟测试。 +run_secs = 0 +for _arg in sys.argv: + if _arg.startswith('--runsecs='): + try: + run_secs = int(_arg[len('--runsecs='):]) + except ValueError: + run_secs = 0 # --onlyapp:只编译库与应用(保留兼容;现在这已是默认行为) only_app = '--onlyapp' in sys.argv # --kernel:显式重编内核。不带 --app 时**默认就编内核**(见下方 app_filter 解析后的修正); @@ -45,6 +57,17 @@ if kernel_only and nokernel: sys.exit(1) +def SyncSyscallTable(): + """同步 syscall 表:Scripts/syscall_table.py 是唯一权威来源。 + + VKernel 与 vpsdk 各自持有一份 enum(vpsdk 不能依赖内核),构建前统一 + 重新生成,避免两份表手工漂移。生成器失败时保留现有文件继续构建。 + """ + rc = os.system(f'"{sys.executable}" Scripts/syscall_table.py --write') + if rc != 0: + print("[build] 警告: syscall 表同步失败,继续使用现有文件") + + def BuildKernel(): """编译内核:链接器一次产出带符号 kernel.elf,再由 objcopy 二次处理成裸镜像 kernel.bin""" cmd = rf'{VIPERC} --project ./VKernel/project.vpj --clear-cache --rebuild-includes' @@ -74,6 +97,7 @@ APP_LIST = [ ("scene3d", "Apps/Scene3D", "scene3d.elf", "scene3d.elf"), ("explorer", "Apps/Explorer", "explorer.elf", "explorer.elf"), ("fontdemo", "Apps/FontDemo", "fontdemo.elf", "fontdemo.elf"), + ("imagesee", "Apps/ImageSee", "imagesee.elf", "imagesee.elf"), ] # 动态库表:(工程目录, 产物文件名) LIB_LIST = [ @@ -97,6 +121,26 @@ def CopyFonts(): shutil.copy(src, "./disk/fonts/font.ttf") +def CopyAssets(): + """Copy image demo assets (Assets/images) to /IMAGES on the disk.""" + src_dir = "Assets/images" + if not os.path.isdir(src_dir): + print("[build] 警告: 缺少 Assets/images,ImageSee 将没有演示图片") + return + os.makedirs("./disk/images", exist_ok=True) + for name in os.listdir(src_dir): + low = name.lower() + if low.endswith(".png") or low.endswith(".jpg") or low.endswith(".jpeg"): + shutil.copy(os.path.join(src_dir, name), os.path.join("./disk/images", name)) + print(f"[build] asset {name} -> disk/images/{name}") + # Desktop wallpaper used by Explorer (separate from the ImageSee gallery). + bg = "Assets/background.jpg" + if os.path.exists(bg): + os.makedirs("./disk/sys/images/screen", exist_ok=True) + shutil.copy(bg, "./disk/sys/images/screen/background.jpg") + print("[build] asset background.jpg -> disk/sys/images/screen/background.jpg") + + def BuildLibs(): """编译 Libs/ 下的动态库(应用运行期经 LOAD_SO 加载)。""" os.makedirs("./disk/libs", exist_ok=True) @@ -222,11 +266,25 @@ def RunQemu(): '-serial', f'file:{serial_log}', # '-no-reboot' ] + if headless: + qemu_cmd += ['-display', 'none'] print(f"[build] QEMU cmd: {' '.join(qemu_cmd)}") proc = subprocess.Popen(qemu_cmd) - proc.wait() + if run_secs > 0: + try: + proc.wait(timeout=run_secs) + except subprocess.TimeoutExpired: + print(f"[build] --runsecs={run_secs}: 自动结束 QEMU") + proc.terminate() + try: + proc.wait(timeout=10) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait() + else: + proc.wait() print(f"[build] serial_log path: {serial_log}") print(f"[build] file exists: {os.path.exists(serial_log)}") @@ -242,6 +300,7 @@ def RunQemu(): if only_run: print("[build] --onlyrun: 跳过编译/打包,直接用现有 disk.img 启动 QEMU") else: + SyncSyscallTable() if kernel_only: print("[build] --kernel-only: 只编译内核") BuildKernel() @@ -251,8 +310,10 @@ else: else: print("[build] 跳过内核编译(--app 或 --nokernel)") BuildApps() + CopyAssets() BuildBootloader() CopyFonts() BuildDisk() -RunQemu() \ No newline at end of file +if "--norun" not in sys.argv: + RunQemu() \ No newline at end of file diff --git a/docs/imagesee.md b/docs/imagesee.md new file mode 100644 index 0000000..0dc9b35 --- /dev/null +++ b/docs/imagesee.md @@ -0,0 +1,114 @@ +# ImageSee 图像解码库 + +`vpsdk/imagesee` 是一个纯用户态的 PNG / JPEG 解码库,配套示例应用 +`Apps/ImageSee` 在窗口中并排显示解码结果。 + +## 1. 目录与模块 + +```text +vpsdk/imagesee/ +├── imagesee.vp 公共 API:load / decode_memory / checksum +├── image.vp Image 类型与像素打包(0xAARRGGBB) +├── inflate.vp zlib / DEFLATE(stored / fixed / dynamic Huffman) +├── png.vp PNG:全部颜色类型、1/2/4/8/16 位、Adam7 +└── jpeg.vp JPEG:baseline/extended sequential、4:4:4/4:2:2/4:2:0 + +Apps/ImageSee/ 示例应用(窗口 + 两块 FBCanvas) +Assets/images/ 示例图片(由 Scripts/gen_imagesee_assets.py 生成) +``` + +## 2. 公共 API + +```python +import vpsdk.imagesee.imagesee as imagesee + +img = imagesee.load("/IMAGES/SAMPLE.PNG") # 失败返回 None +if img is not None: + w = img.width + h = img.height + c = img.pixels[y * w + x] # 0xAARRGGBB + r = (c >> 16) & 0xFF + g = (c >> 8) & 0xFF + b = c & 0xFF + +img2 = imagesee.decode_memory(buf, size) # 已有字节流时使用 +sum32 = imagesee.checksum(img) # FNV-1a,用于回归比对 +name = imagesee.format_name(img) # "png" / "jpeg" +``` + +像素格式与 ViperOS 窗口 / `FBCanvas` 一致:`0xAARRGGBB`,alpha 字节目前由 +合成层忽略,解码器一律输出 255(PNG 的透明像素保留 alpha=0 于像素字中)。 + +## 3. PNG 支持 + +- 签名、IHDR、PLTE、tRNS、IDAT(可多块)、IEND +- 颜色类型:0(灰度)、2(RGB)、3(调色板)、4(灰度+alpha)、6(RGBA) +- 位深:1/2/4/8/16(按 PNG 规则缩小到 8 位输出) +- 过滤:None / Sub / Up / Average / Paeth +- Adam7 隔行扫描 +- 解码后输出 RGBA;调色板 tRNS、灰度和 RGB 的透明色都会转成 alpha=0 + +## 4. JPEG 支持 + +- Baseline / extended sequential Huffman(SOF0 / SOF1) +- 灰度与 YCbCr,色度子采样 4:4:4 / 4:2:2 / 4:2:0(以及任意 H/V 因子) +- DQT(8/16 位)、DHT、DRI + RSTn 重启标记 +- 组件按 ID 识别(1=Y、2=Cb、3=Cr),也兼容 Adobe APP14 的 RGB 变换 +- 8×8 浮点 IDCT,YCbCr → RGB 后输出 + +**限制**:渐进式 JPEG(SOF2)目前明确返回 `None`(不做“解一半”的错误输出); +CMYK/YCCK 返回 `None`;不支持 EXIF 方向、缩略图、算术编码。 + +## 5. 示例应用 + +`/APPS/IMAGESEE.ELF` 启动后: + +1. 依次解码并打印 `/IMAGES/SAMPLE.PNG`、`PHOTO.JPG`、`GRAY.JPG`、 + `PAL.PNG`、`INTER.PNG` 的尺寸、FNV 校验和与 3 个采样像素; +2. 创建窗口,左画 PNG、右画 JPEG(等比缩放居中); +3. 失败时对应面板显示品红/深蓝棋盘,串口打印 `FAILED stage=N`。 + +`build.py` 会: + +- 编译 `Apps/ImageSee` 到 `disk/apps/imagesee.elf` +- 把 `Assets/images/*.png|jpg` 复制到 `disk/images/` +- 内核 `main.vp` 在 Explorer/FontDemo 之后自动 spawn ImageSee(pid=7) + +重建示例图片: + +```powershell +python Scripts/gen_imagesee_assets.py # 需要 Pillow,仅生成资源时使用 +``` + +## 6. 已验证 + +2026-10-28,QEMU 512MB,无头启动: + +| 文件 | 类型 | 结果 | +|---|---|---| +| sample.png | RGBA,非隔行 | 解码像素/校验和与 PIL 完全一致 | +| pal.png | 调色板 + tRNS | 完全一致 | +| inter.png | RGBA,Adam7,alpha 渐变 | 完全一致 | +| photo.jpg | baseline 4:2:0 | 尺寸/采样点与 PIL 在 ±11 内(IDCT/上采样差异) | +| gray.jpg | 灰度 baseline | 采样点完全一致 | + +## 7. Explorer 桌面壁纸 + +Explorer 启动时调用 `imagesee.load("/sys/images/screen/background.jpg")`: + +- 成功:按“cover”等比裁切铺满 framebuffer,任务栏/窗口叠加在壁纸上 +- 失败(文件不存在/解码失败):重试一次;仍失败则保持原来的深色纯色背景 +- 构建:`build.py` 把 `Assets/background.jpg` 复制到 + `disk/sys/images/screen/background.jpg` +- 重新生成壁纸:`python Scripts/gen_imagesee_assets.py`(960×540 baseline JPEG) + +## 8. 内存 + +解码使用 `stdlib.malloc`(mmap 后端,`free` 为空操作)。一次 `load` 会保留: + +- 压缩文件字节 +- DEFLATE 解压后的扫描线缓冲(PNG) +- 最终 `w*h*4` 像素 +- JPEG 的量化系数与分量平面 + +因此适合中小图片(示例 320×220)。超大图片(>4096²)不建议在当前堆上尝试。 diff --git a/docs/syscall.md b/docs/syscall.md new file mode 100644 index 0000000..85c783f --- /dev/null +++ b/docs/syscall.md @@ -0,0 +1,382 @@ +# ViperOS syscall 与 Linux x86-64 ABI 对齐计划 + +状态:**P0–P6 完成**(含明确受限项;内核/应用构建通过,`fork`、`wait4`、信号投递已在 QEMU 冒烟验证) +最后更新:2026-10-28 + +目标:让 ViperOS 的 syscall 面逐步收敛到 Linux x86-64 ABI,最终能够**直接运行静态/动态链接的 glibc 程序**。 + +非目标(至少本计划内不做): + +- 网络协议栈相关 syscall(socket/connect/…) +- ptrace / seccomp / cgroup / namespace +- 完整 VFS(符号链接、硬链接、inode 语义) +- 与 Linux 内核模块、io_uring、epoll 的兼容 + +--- + +## 1. 现状 + +### 1.1 入口与调用约定 + +| 项目 | 现状 | +|---|---| +| 指令 | `syscall`(MSR `STAR`/`LSTAR`/`FMASK`),另注册了 DPL=3 的 `int 0x80` 遗留门 | +| 调用号 | `rax` | +| 参数 | `rdi`, `rsi`, `rdx`, `r10`, `r8`, `r9`(与 Linux 一致) | +| 返回 | `rax`;目前正负混用 | +| 错误 | 无 `errno`、无 TLS;部分返回 `-1`,部分返回 FAT32 `FRESULT` **正数**,未知调用号返回 `0` | +| 表 | `VKernel/Kernel/intr/syscall.vp` 与 `vpsdk/syscall.vp` 两份手工镜像 | + +### 1.2 已实现的调用 + +权威列表见 `Scripts/syscall_table.py`(`build.py` 构建前自动同步内核与 SDK)。 +截至 P6,Linux 号段已覆盖: + +```text +文件 open/openat/close/read/write/pread/pwrite/lseek/stat/fstat/newfstatat/ + getdents64/dup/dup2/dup3/fcntl/mkdir/unlink/rename/rmdir/getcwd/chdir/ + access/faccessat/readlink/readlinkat +内存 brk/mmap/munmap/mprotect/mremap/madvise +进程 getpid/getppid/gettid/exit/exit_group/wait4/clone/fork/execve +线程 arch_prctl(FS)/set_tid_address/set_robust_list/futex/sched_yield +时间 clock_gettime/clock_getres/gettimeofday/nanosleep +信号 rt_sigaction/rt_sigprocmask/rt_sigreturn/kill +IPC pipe/pipe2/readv/writev/poll(超时桩) +系统 uname/getuid/geteuid/getgid/getegid/prlimit64/getrandom/reboot +``` + +私有段仅剩兼容别名(`LOAD_SO`/`SO_CALL1`/`SPAWN_PROC`/`THREAD_CREATE_OLD`/ +`REBOOT`/`SHUTDOWN`)。 + +### 1.3 已知偏差 + +- `execve(59)` 通过“加载新进程 + 退出调用者”模拟,PID 会改变 +- `fork(57)` / `clone(56)` 会复制/共享地址空间;`CLONE_VM` 无 `CLONE_THREAD` + 的 vfork 场景按同进程线程近似 +- 信号只在 **syscall 返回** 时投递(没有中断返回钩子);`ucontext_t` 为自定义 + 紧凑布局,`siglongjmp` 不可用 +- `futex` 为“查值 + yield”轮询;`poll` 恒返回 0(仅超时语义) +- `exit_group(231)` 终止进程内所有线程;进程退出不回收管道/文件引用计数 +- fork/exec 继承的非管道 fd 标记为两侧共享:close 只清槽位不释放对象(避免 + 交叉释放;代价是泄漏) +- 资源上限:8 进程 / 每进程 8 线程 / 16 fd / 8 目录句柄 / 64 VMA / 8 管道 +- P1 已补:`openat` / `fstat` / `newfstatat` / `getdents64` / `dup` / `fcntl`, + 私有目录调用已删除 +- P2 已补:`brk` / `mprotect` / `munmap` / `mremap` / `madvise`,文件映射 +- P3–P6 已补:启动栈/auxv、`PT_INTERP`/ET_DYN、`execve`、`wait4`、Linux + `clone`/`fork`、TLS、时间、管道、信号投递、`reboot(169)` + +### 1.4 维护风险 + +两份 `enum Syscall` 靠人工同步。P0 起改为**生成**: + +```text +Scripts/syscall_table.py ← 唯一权威表 + │ + ├─ VKernel/Kernel/intr/syscall.vp [BEGIN/END GENERATED] + └─ vpsdk/syscall.vp [BEGIN/END GENERATED] +``` + +`build.py` 在编译前运行生成器;两边不一致会直接覆盖,不再依赖记忆。 + +--- + +## 2. 目标 ABI 契约 + +### 2.1 调用与返回 + +- `rax` = 调用号,参数 `rdi/rsi/rdx/r10/r8/r9` +- 成功:返回非负值(字节数、fd、地址、0…) +- 失败:返回 `-errno`(`-ENOENT`、`-EINVAL`…),**不再返回 `FRESULT` 正数** +- 未实现:`-ENOSYS` +- 指针参数越界/为空:`-EFAULT` +- 用户态 `errno` 由 glibc 从返回值设置,内核不维护 `errno` + +### 2.2 数据结构 + +内核新增 `abi/linux.vp`(内核工程 include 增加仓库根目录),字段布局按 x86-64 Linux; +SDK 侧需要结构体时可直接 `import abi.linux`。两者共用同一份定义,避免布局漂移。 + +| 类型 | 大小 | 用途 | +|---|---:|---| +| `abi.linux_stat` | 144 | `stat/fstat/newfstatat` | +| `abi.linux_dirent64` | 19+name | `getdents64` | +| `abi.linux_timespec` | 16 | 时间 | +| `abi.linux_utsname` | 390 | `uname` | +| `abi.linux_rlimit` | 16 | `prlimit64` | +| `abi.iovec` | 16 | `readv/writev` | + +### 2.3 启动栈(P3) + +glibc 不读 `_start(argc, argv)`,它要求内核按 Linux 方式布置用户栈: + +```text +[argc][argv0..argvN][NULL][envp...][NULL][auxv pairs][AT_NULL] +``` + +必须提供的 auxv:`AT_PHDR`、`AT_PHENT`、`AT_PHNUM`、`AT_ENTRY`、`AT_BASE`、 +`AT_PAGESZ`、`AT_RANDOM`、`AT_UID/EUID/GID/EGID`、`AT_SECURE=0`、 +`AT_EXECFN`、`AT_SYSINFO_EHDR`(可选,无 vDSO 时省略)。 + +动态链接:`PT_INTERP` 指向的解释器按 ET_DYN 基址(`0x70000000`)加载, +主程序 PIE 基址 `0x60000000`,`AT_BASE` 指向解释器,`AT_PHDR/AT_ENTRY` +仍指向主程序。系统镜像中需要实际存在该解释器与 libc。 + +### 2.4 TLS 与线程(P4) + +- `arch_prctl(ARCH_SET_FS)`:设置每线程 `IA32_FS_BASE` +- 线程切换时保存/恢复 `FS_BASE` +- `set_tid_address`、`set_robust_list`、`rseq`(可为 `-ENOSYS`) +- futex 至少覆盖 `FUTEX_WAIT/FUTEX_WAKE`(glibc pthread 必需) + +### 2.5 兼容口径 + +- `uname().sysname` 报 `"Linux"`,`release` 报一个 Linux 风格版本号;真实产品名放在 `version` 字段。这样大量脚本/构建系统不会因 `sysname != Linux` 走特殊分支。 +- 文件权限位使用固定值:普通文件 `0644`、目录 `0755`;FAT32 没有 uid/gid。 + +--- + +## 3. 阶段计划 + +### P0 — ABI 契约与基础设施(本次完成,待编译验证) + +- [x] `abi/linux.vp`:errno、`O_*`、`AT_*`、`DT_*`、`PROT_*`、`MAP_*`、`ARCH_*`、结构体 +- [x] 统一错误模型:Linux 号段调用返回 `-errno`;未知调用 `-ENOSYS` + (私有 0x1000+ 段在 P6 删除前保留旧返回值) +- [x] `open` 采用 Linux `O_*`,内核翻译到 FAT32 `FA_*` +- [x] `lseek` 返回新偏移(而不是状态码),支持 64 位 offset(FAT32 上限内) +- [x] 新调用:`uname` `getuid/geteuid/getgid/getegid` `getppid` `gettid` + `getcwd` `chdir` `access` `faccessat` `readlink` `readlinkat` + `set_tid_address` `set_robust_list` `rseq` `prlimit64` `getrandom` +- [x] `Process.cwd`(默认 `/`) +- [x] syscall 表由 `Scripts/syscall_table.py` 生成,`build.py` 自动同步 +- [x] `vpsdk` 增加对应包装与常量 + +验收:现有应用行为不回退;所有失败路径可观察到负 errno;`Scripts/syscall_table.py --check` 通过。 + +### P1 — 文件系统 ABI(本次完成,待编译验证) + +- [x] `openat(257)`(`AT_FDCWD` + 绝对路径 + cwd 相对路径) +- [x] `fstat(5)`、`newfstatat(262)`、`statx(332)`(返回 `-ENOSYS`,glibc 回退) +- [x] `getdents64(217)` + 目录 fd(`O_DIRECTORY`) +- [x] `dup(32)` / `dup2(33)` / `dup3(292)`、`fcntl(72)`(`F_GETFL/F_SETFL/F_DUPFD`) +- [x] `pread64(17)` / `pwrite64(18)` +- [x] `rename(82)`、`rmdir(84)`、`unlinkat(263)` +- [x] 删除私有 `OPENDIR/READDIR/CLOSEDIR` +- [x] SDK 的 `opendir/readdir/closedir` 改由 `openat + getdents64 + close` 实现, + Terminal 等调用方无需改动 + +验收:`ls` 走 `openat + getdents64`;SDK 的 `open` 也统一经 `openat(AT_FDCWD)`; +`ld.so` 的 `openat/read/fstat/getdents64` 路径可用(文件映射属于 P2)。 + +P1 已知限制: + +- `getdents64` 每次只返回一条记录(glibc 合法,只是多一次 syscall) +- `d_ino` 固定 1、`d_off` 固定 0;`telldir/seekdir/rewinddir` 暂不保证 +- `dirfd != AT_FDCWD` 的相对路径查找返回 `-ENOTSUP`(P3/P6 再补目录 fd 反查) +- `statx` 返回 `-ENOSYS`,glibc 回退到 `newfstatat` +- `rename` 只支持同一 FAT32 卷内;`dup` 共享文件偏移,最后一次 `close` 才释放对象 + +### P2 — 内存管理(本次完成,待编译验证) + +- [x] `brk(12)`:独立 brk 区(`0x40000000` 起),按页增长/收缩 +- [x] `mmap` 真语义:`MAP_FIXED`、`PROT_*`、失败返回 `MAP_FAILED`(-1) +- [x] FAT32 文件映射:把文件内容复制进新页(`MAP_PRIVATE` 语义) +- [x] `munmap(11)` 真正解映射并把物理页归还 buddy +- [x] `mprotect(10)` 修改 PTE 权限(支持 VMA 拆分) +- [x] `mremap(25)`(`MREMAP_MAYMOVE` / `MREMAP_FIXED`)、`madvise(28)` +- [x] 每进程 VMA 表(64 项),记录 base/size/prot/kind/flags +- [x] SDK 的 `mmap` 把 `MAP_FAILED` 归一化为 0,既有 Explorer/devfs 调用方无需修改 + +验收:glibc 的 `malloc` 能使用 brk 主 arena;共享库文件可 mmap 后按段 mprotect; +Explorer 的 framebuffer 映射不回退。 + +P2 已知限制: + +- 文件映射是“读入页”,不支持 `MAP_SHARED` 回写/`msync`(ld.so 用 `MAP_PRIVATE`) +- `PROT_NONE` 仍映射为 present(可读),没有 NX;`PROT_EXEC` 仅记录 +- `munmap` 只归还数据页,页表页仍留在 paging 池 +- `brk` 区不在 VMA 表里(glibc 不会对它 munmap) +- `mremap` 增长必须带 `MREMAP_MAYMOVE`,通过“新建+拷贝+解映射”实现 + +### P3 — 进程、启动栈、解释器(本次完成) + +- [x] 用户栈按 Linux 布局写入 `argc/argv/envp/auxv` + (AT_PHDR/PHENT/PHNUM/PAGESZ/ENTRY/BASE/RANDOM/EXECFN/UID/EUID/GID/EGID 等) +- [x] `execve(59)` 原型 `(path, argv, envp)`;SDK 保留 `spawn` 走私有 `SPAWN_PROC` +- [x] `exit_group(231)` 终止进程内所有线程;`exit(60)` 只退当前线程 +- [x] `wait4(61)`(`WNOHANG`、状态字、子进程回收)、`getppid` +- [x] `clone(56)`:`(flags, stack, ptid, ctid, tls)`;CLONE_VM 建线程, + 不带 CLONE_VM 时复制地址空间 +- [x] `fork(57)`:复制用户页 + 启动在父进程 syscall 返回点(RAX=0) +- [x] `PT_INTERP` + ET_DYN 基址加载;`AT_BASE` 指向解释器 +- [x] 旧的 ViperOS `thread_create(func,arg)` 移到私有 `THREAD_CREATE_OLD` + +验收:静态非 PIE、动态 PIE(安装 `ld.so` 与 libc)都可从正确的启动栈进入; +fork 子进程在父进程同一返回点继续执行,父进程得到子 pid。 + +### P4 — TLS 与线程(本次完成) + +- [x] `arch_prctl(158)`:`SET_FS/GET_FS`(`SET_GS/GET_GS` 返回 `-ENOSYS`) +- [x] 线程切换保存/恢复 `IA32_FS_BASE`(独立表,不改 `Thread` 的 600B 布局) +- [x] `rseq(334)` 返回 `-ENOSYS`;`set_robust_list` 记录 +- [x] `futex(202)`:`WAIT/WAKE` 采用“检查值 + sched_yield”轮询实现 +- [x] `clone(CLONE_VM|CLONE_SETTLS)`:子线程从 post-syscall RIP 开始,FS=tls +- [x] `CLONE_CHILD_CLEARTID`:线程退出时写 0,配合 futex 唤醒 join + +验收:`pthread_create/join` 走 Linux clone 路径;TLS 随线程切换。 + +### P5 — 时间、信号、IPC(本次完成) + +- [x] `clock_gettime(228)` / `clock_getres(229)`(RTC 秒 + 1ms tick) +- [x] `gettimeofday(96)`、`nanosleep(35)` +- [x] `readv(19)` / `writev(20)` +- [x] `pipe(22)` / `pipe2(293)`(8 个管道 × 4KB,轮询读写) +- [x] `poll(7)`(当前固定返回 0 = 超时) +- [x] `rt_sigaction(13)` / `rt_sigprocmask(14)` / `kill(62)`:完整登记 +- [x] 信号投递:syscall 返回前重写用户上下文,进入 handler; + `rt_sigreturn(15)` 恢复原上下文与 syscall 返回值 +- [ ] `ppoll` / `select` / `epoll` + +验收:`signal()`/`sigaction()` 注册的 handler 能在下一次 syscall 返回时执行; +`raise()`/`kill(getpid(), sig)` 可触发。 + +### P6 — 清理私有段(部分完成) + +- [x] `reboot(169)`:校验 Linux magic,支持 RESTART / POWER_OFF +- [x] SDK 的 `process.reboot/shutdown` 改用 `reboot(169)` +- [~] 私有 `LOAD_SO/SO_CALL1` 保留(内核启动仍加载 `slog.so`,用户态链接器未实现) +- [~] 私有 `SPAWN_PROC` / `THREAD_CREATE_OLD` 保留(Terminal 与旧 SDK) +- [~] 私有 `REBOOT/SHUTDOWN` 保留作为兼容别名 +- [ ] 用户态动态链接器 + 0x1000+ 段清空 + +验收:Linux `reboot` 可用;私有段尚未清零(受用户态加载器缺失限制)。 + +--- + +## 4. 调用迁移表 + +`P0` = 本次;`—` 表示尚未实现(内核统一返回 `-ENOSYS`)。 + +| 号 | Linux | ViperOS | 状态 | 备注 | +|---:|---|---|---|---| +| 0 | read | READ | P0 | 字节数 / `-errno` | +| 1 | write | WRITE | P0 | 同上 | +| 2 | open | OPEN | P0 | Linux `O_*`,内核翻译 `FA_*` | +| 3 | close | CLOSE | P0 | 0 / `-EBADF` | +| 4 | stat | STAT | P0 | 保留私有 `fileinfo`(兼容旧 SDK) | +| 5 | fstat | FSTAT | P1 | `struct stat` | +| 7 | poll | POLL | P5 | 固定返回 0(超时) | +| 8 | lseek | SEEK | P0 | 返回新偏移 | +| 9 | mmap | MMAP | P2 | 匿名/文件/设备三种;失败 `MAP_FAILED` | +| 10 | mprotect | MPROTECT | P2 | | +| 11 | munmap | MUNMAP | P2 | | +| 12 | brk | BRK | P2 | | +| 13 | rt_sigaction | RT_SIGACTION | P5 | 登记 + 投递 | +| 14 | rt_sigprocmask | RT_SIGPROCMASK | P5 | 支持 block/unblock/setmask | +| 15 | rt_sigreturn | RT_SIGRETURN | P5 | 恢复原上下文 | +| 16 | ioctl | IOCTL | P0 | devfs 子集 | +| 17 | pread64 | PREAD64 | P1 | | +| 18 | pwrite64 | PWRITE64 | P1 | | +| 19 | readv | READV | P5 | | +| 20 | writev | WRITEV | P5 | | +| 21 | access | ACCESS | P0 | | +| 22 | pipe | PIPE | P5 | 8 × 4KB,轮询 | +| 24 | sched_yield | YIELD | P0 | | +| 25 | mremap | MREMAP | P2 | 增长需 `MREMAP_MAYMOVE` | +| 28 | madvise | MADVISE | P2 | 空操作 | +| 32 | dup | DUP | P1 | 末次 close 才释放对象 | +| 33 | dup2 | DUP2 | P1 | | +| 35 | nanosleep | NANOSLEEP | P5 | 1ms 精度 | +| 39 | getpid | GETPID | P0 | | +| 56 | clone | CLONE | P3 | Linux clone;CLONE_VM 建线程 | +| 57 | fork | FORK | P3 | 复制用户地址空间 | +| 59 | execve | SPAWN | P3 | 新进程 + 退出调用者 | +| 60 | exit | THREAD_EXIT | P0 | | +| 61 | wait4 | WAIT4 | P3 | 支持 WNOHANG | +| 62 | kill | KILL | P5 | 待下次 syscall 投递 | +| 63 | uname | UNAME | P0 | | +| 72 | fcntl | FCNTL | P1 | | +| 79 | getcwd | GETCWD | P0 | | +| 80 | chdir | CHDIR | P0 | | +| 82 | rename | RENAME | P1 | | +| 83 | mkdir | MKDIR | P0 | | +| 84 | rmdir | RMDIR | P1 | | +| 87 | unlink | REMOVE | P0 | | +| 89 | readlink | READLINK | P0 | 当前恒 `-EINVAL` | +| 96 | gettimeofday | GETTIMEOFDAY | P5 | RTC 秒 | +| 102 | getuid | GETUID | P0 | 恒 0 | +| 104 | getgid | GETGID | P0 | 恒 0 | +| 107 | geteuid | GETEUID | P0 | 恒 0 | +| 108 | getegid | GETEGID | P0 | 恒 0 | +| 110 | getppid | GETPPID | P0 | 恒 0 | +| 158 | arch_prctl | ARCH_PRCTL | P4 | SET_FS/GET_FS | +| 169 | reboot | REBOOT_LINUX | P6 | magic 校验;RESTART/POWER_OFF | +| 186 | gettid | GETTID | P0 | 返回线程 tid | +| 202 | futex | FUTEX | P4 | 轮询 WAIT/WAKE | +| 217 | getdents64 | GETDENTS64 | P1 | 每次返回一条记录 | +| 218 | set_tid_address | SET_TID_ADDRESS | P0 | | +| 228 | clock_gettime | CLOCK_GETTIME | P5 | RTC/1ms tick | +| 229 | clock_getres | CLOCK_GETRES | P5 | 1ms | +| 231 | exit_group | EXIT | P3 | 终止进程内所有线程 | +| 257 | openat | OPENAT | P1 | | +| 262 | newfstatat | NEWFSTATAT | P1 | | +| 263 | unlinkat | UNLINKAT | P1 | | +| 267 | readlinkat | READLINKAT | P0 | 当前恒 `-EINVAL` | +| 269 | faccessat | FACCESSAT | P0 | | +| 273 | set_robust_list | SET_ROBUST_LIST | P0 | 仅记录 | +| 292 | dup3 | DUP3 | P1 | flags 忽略 | +| 293 | pipe2 | PIPE2 | P5 | flags 仅记录 | +| 302 | prlimit64 | PRLIMIT64 | P0 | 只读限制 | +| 318 | getrandom | GETRANDOM | P0 | 软件 DRNG | +| 332 | statx | — | P1 | `-ENOSYS`,glibc 回退 | +| 334 | rseq | RSEQ | P0 | `-ENOSYS` | +| 0x1006 | — | LOAD_SO | P6 删除 | | +| 0x1007 | — | SO_CALL1 | P6 删除 | | +| 0x1008 | — | REBOOT | P6 保留别名 | | +| 0x1009 | — | SHUTDOWN | P6 保留别名 | | +| 0x100A | — | SPAWN_PROC | P6 保留 | Terminal `run` 用 | +| 0x100B | — | THREAD_CREATE_OLD | P6 保留 | 旧 `thread_create(func,arg)` | + +--- + +## 5. 测试与验收 + +1. **ABI 单元测试**(用户态小程序) + - 每个调用至少覆盖成功 + 一个错误路径,断言返回值为 `-errno` + - 结构体偏移/大小与 Linux 对照(编译期 `__sizeof__`) +2. **`Scripts/syscall_table.py --check`** + - CI/本地构建前检查两份表是否与权威表一致 +3. **glibc 里程碑** + - M1:静态 `hello`(write/exit/brk/mmap/arch_prctl) + - M2:动态 `hello`(`ld.so`、`openat`、`mmap`、`mprotect`) + - M3:`pthread` + `malloc` 压力测试 + - M4:`ls`/`cat`/`sh` 等 coreutils +4. **回归** + - 现有 7 个应用(Terminal / Explorer / Scene3D / Gargantua / FontDemo / HelloWorld / ImageSee)必须继续运行 + +### 5.1 已完成的冒烟验证(2026-10-28,QEMU) + +- 7 个内置应用全部启动,进入主循环,无异常 +- ImageSee 在同一启动中解码 PNG/JPEG 并在窗口显示(见 `docs/imagesee.md`) +- 临时测试程序调用 `fork()`:父进程得到子 pid,子进程在同一返回点继续运行 + (`getpid/getppid` 正确),`wait4` 返回子 pid 且状态为 `0x700`(exit code 7) +- 信号:`rt_sigaction(SIGUSR1)` + `kill(self)` → handler 在 `kill` 返回时执行, + `kill` 仍返回 0;`SIGUSR2` 被 `rt_sigprocmask` 阻塞时不投递,解除阻塞后的下一次 + syscall 返回时投递 +- 无人值守运行:`python build.py --headless --runsecs=45`(`-display none`, + 45 秒后自动结束 QEMU,串口仍写入 `serial.log`) + +--- + +## 6. 风险与已定决策 + +| 风险 | 处理 | +|---|---| +| 错误码改造影响旧应用 | SDK 已按 `<0` 判断;`FRESULT` 正数路径一并改掉,旧应用需回归测试 | +| 两份表再次漂移 | 生成器 + `build.py` 前缀同步;禁止手改生成段 | +| FAT32 无 inode/权限/符号链接 | `struct stat` 用固定值;`readlink` 一律 `-EINVAL`;`chmod` 后置 | +| `mprotect` 未实现时 RELRO 失效 | P2 前在文档标注为“安全降级”,不假装成功 | +| `getrandom` 无硬件熵 | 软件 xorshift + 启动期混合,仅用于 canary/非密码学场景 | +| `uname` 报 Linux | 提升兼容性;`version` 字段保留 ViperOS 标识 | +| 用户态栈布局改动大 | P3 单独提交,先静态后动态,避免与内存阶段交叉 | diff --git a/vpsdk/fs.vp b/vpsdk/fs.vp index 60836bf..adcc624 100644 --- a/vpsdk/fs.vp +++ b/vpsdk/fs.vp @@ -1,23 +1,31 @@ from . import syscall as syscall +import string -define O_RDONLY = 0x01 -define O_WRONLY = 0x02 -define O_RDWR = 0x03 -define O_CREAT = 0x04 -define O_TRUNC = 0x08 -define O_APPEND = 0x30 +# Linux x86-64 open flags. The kernel translates these to FatFs FA_* modes; +# never pass FatFs values from user space. +define O_RDONLY = 0 +define O_WRONLY = 1 +define O_RDWR = 2 +define O_CREAT = 64 +define O_EXCL = 128 +define O_TRUNC = 512 +define O_APPEND = 1024 +define O_DIRECTORY = 65536 +define O_CLOEXEC = 524288 define SEEK_SET = 0 define SEEK_CUR = 1 define SEEK_END = 2 define DT_UNKNOWN = 0 -define DT_FILE = 1 -define DT_DIR = 2 +define DT_REG = 8 +define DT_DIR = 4 +define DT_FILE = 8 # legacy alias def open(path: const str, flags: uint32) -> int: - return int(syscall._syscall3(uint64(syscall.Syscall.OPEN), uint64(path), uint64(flags), 0)) + """SDK open() goes through openat(AT_FDCWD) so relative paths honor cwd.""" + return syscall.openat(syscall.AT_FDCWD, path, flags, 0) def close(fd: int) -> int: return int(syscall._syscall1(uint64(syscall.Syscall.CLOSE), uint64(fd))) @@ -29,20 +37,19 @@ def write(fd: int, buf: rawptr, count: uint32) -> int: return int(syscall._syscall3(uint64(syscall.Syscall.WRITE), uint64(fd), uint64(buf), uint64(count))) def seek(fd: int, offset: uint32, whence: uint32 = 0) -> int: + """lseek: returns the new offset (>= 0) or -errno.""" return int(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), uint64(offset), uint64(whence))) -def tell(fd: int) -> uint32: - """当前位置 = lseek(fd, 0, SEEK_CUR)(TELL 私有 syscall 已删除)。""" - return uint32(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_CUR))) +def tell(fd: int) -> int: + """当前偏移 = lseek(fd, 0, SEEK_CUR)。""" + return int(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_CUR))) -def fsize(fd: int) -> uint32: - """文件大小:SIZE 私有 syscall 已删除,改用 lseek 往返(不改变最终位置)。 - - 三次 lseek:记住当前位置 → 跳到末尾拿到大小 → 复位。多两次 syscall,换掉一个私有号。 - """ - cur: uint32 = uint32(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_CUR))) - end: uint32 = uint32(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_END))) - syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), uint64(cur), uint64(SEEK_SET)) +def fsize(fd: int) -> int: + """文件大小 = lseek(fd, 0, SEEK_END),随后恢复原位置。""" + cur: int = tell(fd) + end: int = int(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_END))) + if cur >= 0: + syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), uint64(cur), uint64(SEEK_SET)) return end def mkdir(path: const str) -> int: @@ -52,13 +59,46 @@ def remove(path: const str) -> int: return int(syscall._syscall3(uint64(syscall.Syscall.REMOVE), uint64(path), 0, 0)) def opendir(path: const str) -> int: - return int(syscall._syscall3(uint64(syscall.Syscall.OPENDIR), uint64(path), 0, 0)) + """Linux opendir: openat(O_RDONLY|O_DIRECTORY), returns a fd.""" + return syscall.openat(syscall.AT_FDCWD, path, O_RDONLY | O_DIRECTORY, 0) -def readdir(dd: int, info: rawptr) -> int: - return int(syscall._syscall3(uint64(syscall.Syscall.READDIR), uint64(dd), uint64(info), 0)) +def readdir(fd: int, info: rawptr) -> int: + """Fill the legacy fileinfo layout from one getdents64 record. -def closedir(dd: int) -> int: - return int(syscall._syscall1(uint64(syscall.Syscall.CLOSEDIR), uint64(dd))) + Returns 0 with an entry, -ENOENT at end-of-directory, or another -errno. + """ + if info is None: return -22 # -EINVAL + buf: [512]char + n: int = syscall.getdents64(fd, &buf[0], uint64(512)) + if n < 0: return n + if n == 0: return -2 # -ENOENT: end of directory + string.memset(info, 0, 288) # sizeof(fileinfo) + dt: int = int(uint8(buf[18])) + attr: uint8 = 0x20 + if dt == DT_DIR: attr = 0x10 + ap: uint8* = uint8*(uint64(info) + 4) + ap[0] = attr + dst: char* = char*(uint64(info) + 16) + string.strcpy(dst, &buf[19]) + return 0 + +def closedir(fd: int) -> int: + return close(fd) + +def openat(dirfd: int, path: const str, flags: uint32, mode: uint32 = 0) -> int: + return syscall.openat(dirfd, path, flags, mode) + +def rename(old_path: const str, new_path: const str) -> int: + return syscall.rename(old_path, new_path) + +def rmdir(path: const str) -> int: + return syscall.rmdir(path) + +def unlinkat(dirfd: int, path: const str, flags: uint32 = 0) -> int: + return syscall.unlinkat(dirfd, path, flags) + +def fcntl(fd: int, cmd: uint32, arg: uint64 = 0) -> int: + return syscall.fcntl(fd, cmd, arg) def stat(path: const str, info: rawptr) -> int: return int(syscall._syscall3(uint64(syscall.Syscall.STAT), uint64(path), uint64(info), 0)) @@ -78,7 +118,9 @@ class FileIO: self._fd = fd self._flags = flags self._pos = 0 - self._size = fsize(fd) + sz: int = fsize(fd) + if sz < 0: sz = 0 + self._size = uint32(sz) self._closed = 0 def read(self, buf: rawptr, count: uint32) -> int: @@ -100,8 +142,8 @@ class FileIO: def seek(self, offset: uint32) -> int: if self._closed: return -1 res: int = seek(self._fd, offset) - if res == 0: - self._pos = offset + if res >= 0: + self._pos = uint32(res) return res def tell(self) -> uint32: diff --git a/vpsdk/imagesee/image.vp b/vpsdk/imagesee/image.vp new file mode 100644 index 0000000..e98bbea --- /dev/null +++ b/vpsdk/imagesee/image.vp @@ -0,0 +1,57 @@ +# ImageSee shared types. + +import vpsdk.stdlib as stdlib +from stdint import * + +define IMAGE_FMT_UNKNOWN = 0 +define IMAGE_FMT_PNG = 1 +define IMAGE_FMT_JPEG = 2 + +class Image: + width: int + height: int + format: int + # Packed 0xAARRGGBB, matching the window/FBCanvas pixel format. + pixels: uint32* + +def new_image(w: int, h: int, fmt: int) -> Image*: + if w <= 0 or h <= 0: return None + if w > 16384 or h > 16384: return None + img: Image* = Image*(stdlib.malloc(uint64(Image.__sizeof__()))) + if img is None: return None + px: uint32* = uint32*(stdlib.malloc(uint64(w * h * 4))) + if px is None: return None + img.width = w + img.height = h + img.format = fmt + img.pixels = px + return img + +def free_image(img: Image*): + # stdlib.free() is a no-op over the bump mmap heap; keep the API for callers. + if img is None: return + img.pixels = None + img.width = 0 + img.height = 0 + +def pixel_at(img: Image*, x: int, y: int) -> uint32: + if img is None: return uint32(0) + if x < 0 or y < 0 or x >= img.width or y >= img.height: return uint32(0) + return img.pixels[y * img.width + x] + +def red_of(c: uint32) -> int: + return int((c >> 16) & uint32(0xFF)) + +def green_of(c: uint32) -> int: + return int((c >> 8) & uint32(0xFF)) + +def blue_of(c: uint32) -> int: + return int(c & uint32(0xFF)) + +def alpha_of(c: uint32) -> int: + return int((c >> 24) & uint32(0xFF)) + +def make_pixel(r: int, g: int, b: int, a: int = 255) -> uint32: + v: uint32 = (uint32(a & 0xFF) << 24) | (uint32(r & 0xFF) << 16) + v = v | (uint32(g & 0xFF) << 8) | uint32(b & 0xFF) + return v diff --git a/vpsdk/imagesee/imagesee.vp b/vpsdk/imagesee/imagesee.vp new file mode 100644 index 0000000..d41c686 --- /dev/null +++ b/vpsdk/imagesee/imagesee.vp @@ -0,0 +1,67 @@ +# ImageSee: small PNG/JPEG decoder library for ViperOS user space. +# +# img = imagesee.load("/IMAGES/SAMPLE.PNG") +# if img is not None: +# ... img.pixels[y * img.width + x] is 0xAARRGGBB ... +# +# `load` reads the whole file into memory and sniffs the container format; +# `decode_memory` is available when the caller already has the bytes. + +import vpsdk.imagesee.image as image +import vpsdk.imagesee.png as png +import vpsdk.imagesee.jpeg as jpeg +import vpsdk.fs as fs +import vpsdk.stdlib as stdlib +from stdint import * + +define IMAGESEE_MAX_FILE = 64 * 1024 * 1024 + +def decode_memory(data: uint8*, n: uint64) -> image.Image*: + if data is None or n < 8: return None + if int(data[0]) == 0x89 and int(data[1]) == 0x50: + if int(data[2]) == 0x4E and int(data[3]) == 0x47: + return png.decode(data, n) + if int(data[0]) == 0xFF and int(data[1]) == 0xD8: + return jpeg.decode(data, n) + return None + +def load(path: const str) -> image.Image*: + fd: int = fs.open(path, fs.O_RDONLY) + if fd < 0: return None + sz: int = fs.fsize(fd) + if sz <= 0 or uint64(sz) > uint64(IMAGESEE_MAX_FILE): + fs.close(fd) + return None + buf: uint8* = uint8*(stdlib.malloc(uint64(sz))) + if buf is None: + fs.close(fd) + return None + got: uint64 = 0 + while got < uint64(sz): + n: int = fs.read(fd, rawptr(uint64(buf) + got), uint32(uint64(sz) - got)) + if n <= 0: break + got = got + uint64(n) + fs.close(fd) + if got != uint64(sz): return None + return decode_memory(buf, uint64(sz)) + +def last_error() -> int: + """Last JPEG decoder failure stage (0 = not started; 7 = render done).""" + return jpeg.last_error() + +def format_name(img: image.Image*) -> const str: + if img is None: return "none" + if img.format == image.IMAGE_FMT_PNG: return "png" + if img.format == image.IMAGE_FMT_JPEG: return "jpeg" + return "unknown" + +# 32-bit FNV-1a over packed pixels; handy for regression checks. +def checksum(img: image.Image*) -> uint32: + if img is None: return uint32(0) + h: uint32 = uint32(2166136261) + n: int = img.width * img.height + i: int = 0 + while i < n: + h = (h ^ img.pixels[i]) * uint32(16777619) + i = i + 1 + return h diff --git a/vpsdk/imagesee/inflate.vp b/vpsdk/imagesee/inflate.vp new file mode 100644 index 0000000..d478d50 --- /dev/null +++ b/vpsdk/imagesee/inflate.vp @@ -0,0 +1,261 @@ +# DEFLATE / zlib decompression (RFC 1950 + RFC 1951). +# +# Written for PNG's IDAT stream: self-contained, no allocation inside the +# decoder (the caller provides the output buffer), canonical Huffman tables +# built the same way as the Brotli decoder in this SDK. + +define INF_MAXBITS = 15 +define INF_MAXCODES = 288 +define INF_MAXDIST = 32 + +class InfHuff: + count: [INF_MAXBITS + 1]int + symbol: [INF_MAXCODES]int + +# Length codes 257..285 and distance codes 0..29. +_lbase: [29]int = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258] +_lextra: [29]int = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0] +_dbase: [30]int = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577] +_dextra: [30]int = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13] +_clorder: [19]int = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15] + +_inf_src: uint8* = None +_inf_len: uint64 = 0 +_inf_pos: uint64 = 0 +_inf_bitbuf: uint32 = 0 +_inf_bitcnt: int = 0 +_inf_out: uint8* = None +_inf_cap: uint64 = 0 +_inf_got: uint64 = 0 +_inf_err: int = 0 + +_inf_lens: [320]uint8 +_inf_lit: InfHuff +_inf_dist: InfHuff +_inf_clen: InfHuff + +def _inf_need(n: int) -> uint32: + global _inf_bitbuf, _inf_bitcnt, _inf_pos, _inf_err + while _inf_bitcnt < n: + if _inf_pos >= _inf_len: + _inf_err = 1 + return uint32(0) + _inf_bitbuf = _inf_bitbuf | (uint32(_inf_src[_inf_pos]) << _inf_bitcnt) + _inf_pos = _inf_pos + 1 + _inf_bitcnt = _inf_bitcnt + 8 + v: uint32 = _inf_bitbuf & ((uint32(1) << uint32(n)) - uint32(1)) + _inf_bitbuf = _inf_bitbuf >> uint32(n) + _inf_bitcnt = _inf_bitcnt - n + return v + +def _inf_decode(h: InfHuff*) -> int: + global _inf_err + code: int = 0 + first: int = 0 + index: int = 0 + ln: int = 1 + while ln <= INF_MAXBITS: + code = code | int(_inf_need(1)) + cnt: int = h.count[ln] + if code - first < cnt: + return h.symbol[index + (code - first)] + index = index + cnt + first = (first + cnt) << 1 + code = code << 1 + ln = ln + 1 + _inf_err = 1 + return -1 + +def _inf_build(h: InfHuff*, lens: uint8*, n: int) -> int: + i: int = 0 + while i <= INF_MAXBITS: + h.count[i] = 0 + i = i + 1 + i = 0 + while i < n: + ln8: uint8 = lens[i] + if ln8 != 0: + if int(ln8) > INF_MAXBITS: return -1 + h.count[int(ln8)] = h.count[int(ln8)] + 1 + i = i + 1 + offs: [INF_MAXBITS + 2]int + offs[1] = 0 + ln: int = 1 + while ln <= INF_MAXBITS: + offs[ln + 1] = offs[ln] + h.count[ln] + ln = ln + 1 + i = 0 + while i < n: + ln = int(lens[i]) + if ln != 0: + h.symbol[offs[ln]] = i + offs[ln] = offs[ln] + 1 + i = i + 1 + return 0 + +def _inf_put(b: int) -> int: + global _inf_got, _inf_err + if _inf_got >= _inf_cap: + _inf_err = 1 + return -1 + _inf_out[_inf_got] = uint8(b & 0xFF) + _inf_got = _inf_got + 1 + return 0 + +def _inf_stored() -> int: + global _inf_bitbuf, _inf_bitcnt, _inf_pos + # Drop the partial byte and read LEN/NLEN directly from the byte stream. + _inf_bitbuf = uint32(0) + _inf_bitcnt = 0 + if _inf_pos + 4 > _inf_len: return -1 + lenv: int = int(_inf_src[_inf_pos]) | (int(_inf_src[_inf_pos + 1]) << 8) + nlen: int = int(_inf_src[_inf_pos + 2]) | (int(_inf_src[_inf_pos + 3]) << 8) + _inf_pos = _inf_pos + 4 + if (lenv ^ 0xFFFF) != nlen: return -1 + if _inf_pos + uint64(lenv) > _inf_len: return -1 + i: int = 0 + while i < lenv: + if _inf_put(int(_inf_src[_inf_pos])) != 0: return -1 + _inf_pos = _inf_pos + 1 + i = i + 1 + return 0 + +def _inf_fixed() -> int: + i: int = 0 + while i < 144: + _inf_lens[i] = uint8(8) + i = i + 1 + while i < 256: + _inf_lens[i] = uint8(9) + i = i + 1 + while i < 280: + _inf_lens[i] = uint8(7) + i = i + 1 + while i < 288: + _inf_lens[i] = uint8(8) + i = i + 1 + if _inf_build(&_inf_lit, &_inf_lens[0], 288) != 0: return -1 + i = 0 + while i < 32: + _inf_lens[i] = uint8(5) + i = i + 1 + if _inf_build(&_inf_dist, &_inf_lens[0], 32) != 0: return -1 + return 0 + +def _inf_dynamic() -> int: + hlit: int = int(_inf_need(5)) + 257 + hdist: int = int(_inf_need(5)) + 1 + hclen: int = int(_inf_need(4)) + 4 + if hlit > 286 or hdist > 30: return -1 + i: int = 0 + while i < 320: + _inf_lens[i] = uint8(0) + i = i + 1 + i = 0 + while i < hclen: + _inf_lens[_clorder[i]] = uint8(_inf_need(3)) + i = i + 1 + if _inf_build(&_inf_clen, &_inf_lens[0], 19) != 0: return -1 + n: int = hlit + hdist + i = 0 + while i < n: + sym: int = _inf_decode(&_inf_clen) + if _inf_err != 0: return -1 + if sym < 16: + _inf_lens[i] = uint8(sym) + i = i + 1 + elif sym == 16: + if i == 0: return -1 + rep: int = 3 + int(_inf_need(2)) + prev: uint8 = _inf_lens[i - 1] + while rep > 0 and i < n: + _inf_lens[i] = prev + i = i + 1 + rep = rep - 1 + elif sym == 17: + rep: int = 3 + int(_inf_need(3)) + while rep > 0 and i < n: + _inf_lens[i] = uint8(0) + i = i + 1 + rep = rep - 1 + elif sym == 18: + rep: int = 11 + int(_inf_need(7)) + while rep > 0 and i < n: + _inf_lens[i] = uint8(0) + i = i + 1 + rep = rep - 1 + else: + return -1 + if _inf_build(&_inf_lit, &_inf_lens[0], hlit) != 0: return -1 + if _inf_build(&_inf_dist, &_inf_lens[hlit], hdist) != 0: return -1 + return 0 + +def _inf_codes() -> int: + while True: + sym: int = _inf_decode(&_inf_lit) + if _inf_err != 0: return -1 + if sym < 256: + if _inf_put(sym) != 0: return -1 + elif sym == 256: + return 0 + elif sym >= 257 and sym <= 285: + li: int = sym - 257 + length: int = _lbase[li] + int(_inf_need(_lextra[li])) + dsym: int = _inf_decode(&_inf_dist) + if _inf_err != 0: return -1 + if dsym < 0 or dsym >= 30: return -1 + dist: int = _dbase[dsym] + int(_inf_need(_dextra[dsym])) + if dist <= 0 or uint64(dist) > _inf_got: return -1 + i: int = 0 + while i < length: + src_i: uint64 = _inf_got - uint64(dist) + if _inf_put(int(_inf_out[src_i])) != 0: return -1 + i = i + 1 + else: + return -1 + +def _inf_blocks() -> int: + while True: + final: int = int(_inf_need(1)) + btype: int = int(_inf_need(2)) + if _inf_err != 0: return -1 + if btype == 0: + if _inf_stored() != 0: return -1 + elif btype == 1: + if _inf_fixed() != 0: return -1 + if _inf_codes() != 0: return -1 + elif btype == 2: + if _inf_dynamic() != 0: return -1 + if _inf_codes() != 0: return -1 + else: + return -1 + if final != 0: + return 0 + +# Inflate a zlib stream (skips the 2-byte header). Returns bytes written +# into `dst`, or -1 on malformed input / output overflow. +def zlib_decompress(src: uint8*, src_len: uint64, dst: uint8*, dst_cap: uint64) -> int: + global _inf_src, _inf_len, _inf_pos, _inf_bitbuf, _inf_bitcnt + global _inf_out, _inf_cap, _inf_got, _inf_err + if src is None or dst is None or src_len < 6: return -1 + cmf: int = int(src[0]) + flg: int = int(src[1]) + if (cmf & 0x0F) != 8: return -1 + if (cmf * 256 + flg) % 31 != 0: return -1 + if (flg & 0x20) != 0: return -1 # FDICT unsupported + _inf_src = src + _inf_len = src_len + _inf_pos = 2 + _inf_bitbuf = uint32(0) + _inf_bitcnt = 0 + _inf_out = dst + _inf_cap = dst_cap + _inf_got = 0 + _inf_err = 0 + if _inf_blocks() != 0: return -1 + return int(_inf_got) diff --git a/vpsdk/imagesee/jpeg.vp b/vpsdk/imagesee/jpeg.vp new file mode 100644 index 0000000..0ec7469 --- /dev/null +++ b/vpsdk/imagesee/jpeg.vp @@ -0,0 +1,781 @@ +# JPEG decoder: baseline/extended sequential (SOF0/SOF1) and progressive +# (SOF2), Huffman entropy coding, restart markers, 4:4:4 / 4:2:2 / 4:2:0 +# chroma subsampling, 8-bit samples. Output is packed 0xAARRGGBB. +# +# Coefficient arrays are kept in natural (de-zigzagged) order so the IDCT is +# a straight 8x8 pass. Progressive scans refine the same arrays. + +import vpsdk.imagesee.image as im +import vpsdk.stdlib as stdlib +import string +import math +from stdint import * + +define J_MAX_COMPS = 4 + +class JHuff: + count: [17]int + symbol: [256]int + +class JComp: + id: int + hs: int + vs: int + tq: int + td: int + ta: int + bw: int + bh: int + coef: int16* + plane: uint8* + +_j_src: uint8* = None +_j_len: uint64 = 0 +_j_pos: uint64 = 0 +_j_bitbuf: uint32 = 0 +_j_bitcnt: int = 0 +_j_marker: int = -1 +_j_err: int = 0 + +_j_w: int = 0 +_j_h: int = 0 +_j_ncomp: int = 0 +_j_maxh: int = 1 +_j_maxv: int = 1 +_j_mcu_cols: int = 0 +_j_mcu_rows: int = 0 +_j_progressive: int = 0 +_j_rgb_mode: int = 0 +_j_restart: int = 0 + +_j_scan_ns: int = 0 +_j_sc: [4]int +_j_sctd: [4]int +_j_scta: [4]int +_j_ss: int = 0 +_j_se: int = 0 +_j_ah: int = 0 +_j_al: int = 0 +_j_eobrun: int = 0 +_j_dcpred: [4]int + +_j_qt: [4 * 64]uint16 +_j_qt_used: [4]int +_j_dc: [4]JHuff +_j_ac: [4]JHuff +_j_comps: [4]JComp +_j_planes_ready: int = 0 +_j_stage: int = 0 +_j_scan_no: int = 0 +_j_cur_mcu: int = 0 + +_j_cs: [64]float +_j_cw: [8]float +_j_trig_ready: int = 0 +_j_g: [64]float + +_zigzag: [64]int = [ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 +] + +def _j_u8(o: uint64) -> int: + return int(_j_src[o]) + +def last_error() -> int: + return _j_stage + +def _j_u16(o: uint64) -> int: + return (int(_j_src[o]) << 8) | int(_j_src[o + 1]) + +# ---------------------------------------------------------------- bit reader +# Returns the next entropy byte with 0xFF00 unstuffing; on a marker it stores +# the marker id in _j_marker and returns -1. +def _j_byte() -> int: + global _j_pos, _j_marker + if _j_pos >= _j_len: return -1 + b: int = int(_j_src[_j_pos]) + _j_pos = _j_pos + 1 + if b == 0xFF: + while _j_pos < _j_len and int(_j_src[_j_pos]) == 0xFF: + _j_pos = _j_pos + 1 + if _j_pos >= _j_len: return -1 + m: int = int(_j_src[_j_pos]) + _j_pos = _j_pos + 1 + if m == 0x00: return 0xFF + _j_marker = m + return -1 + return b + +def _j_bits(n: int) -> uint32: + global _j_bitbuf, _j_bitcnt, _j_err + if n <= 0: return uint32(0) + while _j_bitcnt < n: + b: int = _j_byte() + if b < 0: + _j_err = 1 + return uint32(0) + _j_bitbuf = (_j_bitbuf << 8) | uint32(b) + _j_bitcnt = _j_bitcnt + 8 + _j_bitcnt = _j_bitcnt - n + v: uint32 = (_j_bitbuf >> uint32(_j_bitcnt)) & ((uint32(1) << uint32(n)) - uint32(1)) + return v + +def _j_get_bit() -> int: + return int(_j_bits(1)) + +def _j_receive(s: int) -> int: + if s == 0: return 0 + v: int = int(_j_bits(s)) + if v < (1 << (s - 1)): + v = v - ((1 << s) - 1) + return v + +def _j_huff_build(h: JHuff*, counts: uint8*, vals: uint8*, n: int) -> int: + total: int = 0 + i: int = 1 + while i <= 16: + c: int = int(counts[i - 1]) + h.count[i] = c + total = total + c + i = i + 1 + if total != n: return -1 + # canonical offsets + offs: [18]int + offs[1] = 0 + i = 1 + while i <= 16: + offs[i + 1] = offs[i] + h.count[i] + i = i + 1 + # `vals` is already ordered by code length (JPEG DHT order). + i = 0 + ln: int = 1 + vi: int = 0 + while ln <= 16: + c = h.count[ln] + k: int = 0 + while k < c: + h.symbol[offs[ln] + k] = int(vals[vi]) + vi = vi + 1 + k = k + 1 + ln = ln + 1 + return 0 + +def _j_hdecode(h: JHuff*) -> int: + global _j_err + code: int = 0 + first: int = 0 + index: int = 0 + ln: int = 1 + while ln <= 16: + code = code | _j_get_bit() + cnt: int = h.count[ln] + if code - first < cnt: + return h.symbol[index + (code - first)] + index = index + cnt + first = (first + cnt) << 1 + code = code << 1 + ln = ln + 1 + _j_err = 1 + return 0 + +# --------------------------------------------------------------- markers +def _j_next_marker() -> int: + global _j_pos, _j_marker + if _j_marker >= 0: + m: int = _j_marker + _j_marker = -1 + return m + while _j_pos < _j_len: + b: int = int(_j_src[_j_pos]) + _j_pos = _j_pos + 1 + if b != 0xFF: continue + while _j_pos < _j_len and int(_j_src[_j_pos]) == 0xFF: + _j_pos = _j_pos + 1 + if _j_pos >= _j_len: return -1 + m = int(_j_src[_j_pos]) + _j_pos = _j_pos + 1 + if m == 0x00: continue + return m + return -1 + +def _j_parse_dqt(p: uint64, pend: uint64) -> int: + while p < pend: + pq: int = int(_j_src[p]) >> 4 + tq: int = int(_j_src[p]) & 15 + p = p + 1 + if tq >= 4: return -1 + i: int = 0 + while i < 64: + if pq != 0: + if p + 1 >= pend: return -1 + v: int = (int(_j_src[p]) << 8) | int(_j_src[p + 1]) + p = p + 2 + else: + if p >= pend: return -1 + v = int(_j_src[p]) + p = p + 1 + _j_qt[tq * 64 + _zigzag[i]] = uint16(v) + i = i + 1 + _j_qt_used[tq] = 1 + return 0 + +def _j_parse_dht(p: uint64, pend: uint64) -> int: + while p < pend: + if p + 17 > pend: return -1 + tc: int = int(_j_src[p]) >> 4 + th: int = int(_j_src[p]) & 15 + p = p + 1 + if tc > 1 or th > 3: return -1 + counts: [16]uint8 + total: int = 0 + i: int = 0 + while i < 16: + counts[i] = _j_src[p + i] + total = total + int(counts[i]) + i = i + 1 + p = p + 16 + if total <= 0 or total > 256 or p + uint64(total) > pend: return -1 + vals: [256]uint8 + i = 0 + while i < total: + vals[i] = _j_src[p + i] + i = i + 1 + p = p + uint64(total) + if tc == 0: + if _j_huff_build(&_j_dc[th], &counts[0], &vals[0], total) != 0: return -1 + else: + if _j_huff_build(&_j_ac[th], &counts[0], &vals[0], total) != 0: return -1 + return 0 + +def _j_parse_sof(p: uint64, pend: uint64, prog: int) -> int: + global _j_w, _j_h, _j_ncomp, _j_progressive, _j_maxh, _j_maxv, _j_mcu_cols, _j_mcu_rows, _j_planes_ready + if p + 6 > pend: return -1 + prec: int = int(_j_src[p]) + h: int = _j_u16(p + 1) + w: int = _j_u16(p + 3) + nf: int = int(_j_src[p + 5]) + p = p + 6 + if prec != 8 or w <= 0 or h <= 0 or nf < 1 or nf > J_MAX_COMPS: return -1 + if w > 16384 or h > 16384: return -1 + if p + uint64(nf * 3) > pend: return -1 + _j_w = w + _j_h = h + _j_ncomp = nf + _j_progressive = prog + _j_maxh = 1 + _j_maxv = 1 + i: int = 0 + while i < nf: + cid: int = int(_j_src[p + i * 3]) + hv: int = int(_j_src[p + i * 3 + 1]) + tq: int = int(_j_src[p + i * 3 + 2]) + hs: int = hv >> 4 + vs: int = hv & 15 + if hs < 1 or hs > 4 or vs < 1 or vs > 4 or tq > 3: return -1 + _j_comps[i].id = cid + _j_comps[i].hs = hs + _j_comps[i].vs = vs + _j_comps[i].tq = tq + _j_comps[i].td = 0 + _j_comps[i].ta = 0 + if hs > _j_maxh: _j_maxh = hs + if vs > _j_maxv: _j_maxv = vs + i = i + 1 + _j_mcu_cols = (w + 8 * _j_maxh - 1) / (8 * _j_maxh) + _j_mcu_rows = (h + 8 * _j_maxv - 1) / (8 * _j_maxv) + _j_planes_ready = 0 + return 0 + +def _j_prepare() -> int: + global _j_planes_ready + if _j_planes_ready != 0: return 0 + i: int = 0 + while i < _j_ncomp: + comp: JComp* = &_j_comps[i] + comp.bw = _j_mcu_cols * comp.hs + comp.bh = _j_mcu_rows * comp.vs + if comp.bw <= 0 or comp.bh <= 0: return -1 + if _j_progressive != 0: + comp.coef = int16*(stdlib.malloc(uint64(comp.bw * comp.bh * 64 * 2))) + if comp.coef is None: return -1 + string.memset(rawptr(comp.coef), 0, uint64(comp.bw * comp.bh * 64 * 2)) + else: + comp.coef = int16*(stdlib.malloc(uint64(comp.bw * comp.bh * 64 * 2))) + if comp.coef is None: return -1 + string.memset(rawptr(comp.coef), 0, uint64(comp.bw * comp.bh * 64 * 2)) + i = i + 1 + _j_planes_ready = 1 + return 0 + +# ------------------------------------------------------------------- blocks +def _j_block_seq(c: int, blk: int): + comp: JComp* = &_j_comps[c] + base: int = blk * 64 + t: int = _j_hdecode(&_j_dc[comp.td]) + s: int = t & 15 + _j_dcpred[c] = _j_dcpred[c] + _j_receive(s) + comp.coef[base] = int16(_j_dcpred[c]) + k: int = 1 + while k < 64: + rs: int = _j_hdecode(&_j_ac[comp.ta]) + if _j_err != 0: break + s = rs & 15 + r: int = rs >> 4 + if s == 0: + if r == 15: + k = k + 16 + continue + break + k = k + r + if k > 63: break + v: int = _j_receive(s) + comp.coef[base + _zigzag[k]] = int16(v) + k = k + 1 + +def _j_block_dc_first(c: int, blk: int): + comp: JComp* = &_j_comps[c] + t: int = _j_hdecode(&_j_dc[comp.td]) + s: int = t & 15 + _j_dcpred[c] = _j_dcpred[c] + _j_receive(s) + comp.coef[blk * 64] = int16(_j_dcpred[c] << _j_al) + +def _j_block_dc_refine(c: int, blk: int): + comp: JComp* = &_j_comps[c] + if _j_get_bit() != 0: + comp.coef[blk * 64] = int16(int(comp.coef[blk * 64]) | (1 << _j_al)) + +def _j_block_ac_first(c: int, blk: int): + global _j_eobrun + if _j_eobrun > 0: + _j_eobrun = _j_eobrun - 1 + return + comp: JComp* = &_j_comps[c] + base: int = blk * 64 + k: int = _j_ss + while k <= _j_se: + rs: int = _j_hdecode(&_j_ac[comp.ta]) + if _j_err != 0: break + s: int = rs & 15 + r: int = rs >> 4 + if s == 0: + if r < 15: + _j_eobrun = (1 << r) + int(_j_bits(r)) - 1 + break + k = k + 16 + else: + k = k + r + if k > _j_se: break + v: int = _j_receive(s) + comp.coef[base + _zigzag[k]] = int16(v << _j_al) + k = k + 1 + +def _j_block_ac_refine(c: int, blk: int): + global _j_eobrun, _j_err + comp: JComp* = &_j_comps[c] + base: int = blk * 64 + p1: int = 1 << _j_al + m1: int = (0 - 1) << _j_al + k: int = _j_ss + if _j_eobrun == 0: + while k <= _j_se: + rs: int = _j_hdecode(&_j_ac[comp.ta]) + if _j_err != 0: break + s: int = rs & 15 + r: int = rs >> 4 + newv: int = 0 + zrl: int = 0 + if s == 0: + if r < 15: + _j_eobrun = (1 << r) + int(_j_bits(r)) - 1 + break + zrl = 1 + else: + if s != 1: + _j_err = 1 + break + if _j_get_bit() != 0: newv = p1 + else: newv = m1 + # Advance over r zero coefficients, refining non-zero ones. + while k <= _j_se: + idx: int = base + _zigzag[k] + cv: int = int(comp.coef[idx]) + if cv != 0: + if _j_get_bit() != 0 and (cv & p1) == 0: + if cv > 0: comp.coef[idx] = int16(cv + p1) + else: comp.coef[idx] = int16(cv + m1) + else: + if r == 0: break + r = r - 1 + k = k + 1 + if newv != 0 and k <= _j_se: + comp.coef[base + _zigzag[k]] = int16(newv) + k = k + 1 + elif zrl != 0 and k <= _j_se: + # ZRL skips 16 zero coefficients; the inner loop leaves us + # parked on the 16th one (r == 0), so step past it. + k = k + 1 + else: + while k <= _j_se: + idx: int = base + _zigzag[k] + cv: int = int(comp.coef[idx]) + if cv != 0: + if _j_get_bit() != 0 and (cv & p1) == 0: + if cv > 0: comp.coef[idx] = int16(cv + p1) + else: comp.coef[idx] = int16(cv + m1) + k = k + 1 + _j_eobrun = _j_eobrun - 1 + +# --------------------------------------------------------------------- scan +def _j_restart_scan() -> int: + global _j_bitbuf, _j_bitcnt, _j_err, _j_eobrun, _j_marker, _j_pos + _j_bitbuf = uint32(0) + _j_bitcnt = 0 + _j_err = 0 + _j_eobrun = 0 + i: int = 0 + while i < J_MAX_COMPS: + _j_dcpred[i] = 0 + i = i + 1 + if _j_marker >= 0: + m: int = _j_marker + _j_marker = -1 + if m >= 0xD0 and m <= 0xD7: return 0 + _j_marker = m + return -1 + while _j_pos < _j_len: + b: int = int(_j_src[_j_pos]) + _j_pos = _j_pos + 1 + if b != 0xFF: continue + while _j_pos < _j_len and int(_j_src[_j_pos]) == 0xFF: + _j_pos = _j_pos + 1 + if _j_pos >= _j_len: return -1 + m = int(_j_src[_j_pos]) + _j_pos = _j_pos + 1 + if m == 0x00: continue + if m >= 0xD0 and m <= 0xD7: return 0 + _j_marker = m + return -1 + return -1 + +def _j_decode_block(c: int, blk: int): + if _j_progressive == 0: + _j_block_seq(c, blk) + else: + if _j_ss == 0: + if _j_ah == 0: _j_block_dc_first(c, blk) + else: _j_block_dc_refine(c, blk) + else: + if _j_ah == 0: _j_block_ac_first(c, blk) + else: _j_block_ac_refine(c, blk) + +def _j_scan() -> int: + global _j_bitbuf, _j_bitcnt, _j_err, _j_eobrun, _j_cur_mcu, _j_stage + _j_bitbuf = uint32(0) + _j_bitcnt = 0 + _j_err = 0 + _j_eobrun = 0 + i: int = 0 + while i < J_MAX_COMPS: + _j_dcpred[i] = 0 + i = i + 1 + if _j_scan_ns < 1: return -1 + if _j_scan_ns > 1: + total: int = _j_mcu_cols * _j_mcu_rows + mcu: int = 0 + while mcu < total: + _j_cur_mcu = mcu + if _j_restart > 0 and mcu > 0 and (mcu % _j_restart) == 0: + if _j_restart_scan() != 0: return -1 + si: int = 0 + while si < _j_scan_ns: + c: int = _j_sc[si] + comp: JComp* = &_j_comps[c] + vy: int = 0 + while vy < comp.vs: + hx: int = 0 + while hx < comp.hs: + bx: int = (mcu % _j_mcu_cols) * comp.hs + hx + by: int = (mcu / _j_mcu_cols) * comp.vs + vy + _j_decode_block(c, by * comp.bw + bx) + if _j_err != 0: + _j_stage = 1000 + mcu + return -1 + hx = hx + 1 + vy = vy + 1 + si = si + 1 + mcu = mcu + 1 + else: + c = _j_sc[0] + comp = &_j_comps[c] + cw: int = (_j_w * comp.hs + _j_maxh - 1) / _j_maxh + ch: int = (_j_h * comp.vs + _j_maxv - 1) / _j_maxv + bwc: int = (cw + 7) / 8 + bhc: int = (ch + 7) / 8 + total = bwc * bhc + k: int = 0 + while k < total: + _j_cur_mcu = k + if _j_restart > 0 and k > 0 and (k % _j_restart) == 0: + if _j_restart_scan() != 0: return -1 + by: int = k / bwc + bx: int = k % bwc + _j_decode_block(c, by * comp.bw + bx) + if _j_err != 0: + _j_stage = 2000 + k + return -1 + k = k + 1 + _j_bitbuf = uint32(0) + _j_bitcnt = 0 + return 0 + +def _j_parse_sos(p: uint64, pend: uint64) -> int: + global _j_scan_ns, _j_ss, _j_se, _j_ah, _j_al + if p + 4 > pend: return -1 + ns: int = int(_j_src[p]) + if ns < 1 or ns > J_MAX_COMPS or p + uint64(3 + ns * 2) > pend: return -1 + p = p + 1 + i: int = 0 + while i < ns: + cs: int = int(_j_src[p]) + td: int = int(_j_src[p + 1]) >> 4 + ta: int = int(_j_src[p + 1]) & 15 + ci: int = -1 + j: int = 0 + while j < _j_ncomp: + if _j_comps[j].id == cs: ci = j + j = j + 1 + if ci < 0: return -1 + _j_sc[i] = ci + _j_sctd[i] = td + _j_scta[i] = ta + _j_comps[ci].td = td + _j_comps[ci].ta = ta + p = p + 2 + i = i + 1 + ss: int = int(_j_src[p]) + se: int = int(_j_src[p + 1]) + ahal: int = int(_j_src[p + 2]) + _j_scan_ns = ns + _j_ss = ss + _j_se = se + _j_ah = ahal >> 4 + _j_al = ahal & 15 + if se > 63 or ss > se: return -1 + return 0 + +# --------------------------------------------------------------------- IDCT +def _j_trig(): + global _j_trig_ready + if _j_trig_ready != 0: return + x: int = 0 + while x < 8: + u: int = 0 + while u < 8: + _j_cs[x * 8 + u] = float(math.cos(float((2 * x + 1) * u) * 3.141592653589793 / 16.0)) + u = u + 1 + x = x + 1 + _j_cw[0] = float(0.7071067811865476) + u = 1 + while u < 8: + _j_cw[u] = float(1.0) + u = u + 1 + _j_trig_ready = 1 + +def _j_idct_block(comp: JComp*, bx: int, by: int): + qbase: int = comp.tq * 64 + cbase: int = (by * comp.bw + bx) * 64 + v: int = 0 + while v < 8: + x: int = 0 + while x < 8: + s: float = float(0.0) + u: int = 0 + while u < 8: + s = s + _j_cw[u] * (float(int(comp.coef[cbase + v * 8 + u])) * float(int(_j_qt[qbase + v * 8 + u]))) * _j_cs[x * 8 + u] + u = u + 1 + _j_g[v * 8 + x] = s + x = x + 1 + v = v + 1 + y: int = 0 + pw: int = comp.bw * 8 + while y < 8: + x = 0 + while x < 8: + s: float = float(0.0) + v = 0 + while v < 8: + s = s + _j_cw[v] * _j_g[v * 8 + x] * _j_cs[y * 8 + v] + v = v + 1 + val: int = int(s * float(0.25) + float(128.5)) + if val < 0: val = 0 + if val > 255: val = 255 + comp.plane[(by * 8 + y) * pw + bx * 8 + x] = uint8(val) + x = x + 1 + y = y + 1 + +def _j_render() -> im.Image*: + global _j_stage + _j_trig() + i: int = 0 + while i < _j_ncomp: + comp: JComp* = &_j_comps[i] + pw: int = comp.bw * 8 + ph: int = comp.bh * 8 + comp.plane = uint8*(stdlib.malloc(uint64(pw * ph))) + if comp.plane is None: + _j_stage = 8 + return None + by: int = 0 + while by < comp.bh: + bx: int = 0 + while bx < comp.bw: + _j_idct_block(comp, bx, by) + bx = bx + 1 + by = by + 1 + i = i + 1 + img: im.Image* = im.new_image(_j_w, _j_h, im.IMAGE_FMT_JPEG) + if img is None: + _j_stage = 9 + return None + ci_y: int = 0 + ci_cb: int = 1 + ci_cr: int = 2 + i = 0 + while i < _j_ncomp: + if _j_comps[i].id == 1: ci_y = i + elif _j_comps[i].id == 2: ci_cb = i + elif _j_comps[i].id == 3: ci_cr = i + i = i + 1 + c0: JComp* = &_j_comps[ci_y] + pw0: int = c0.bw * 8 + y: int = 0 + while y < _j_h: + x: int = 0 + while x < _j_w: + if _j_ncomp == 1: + cy: int = (y * c0.vs) / _j_maxv + cx: int = (x * c0.hs) / _j_maxh + if cy >= c0.bh * 8: cy = c0.bh * 8 - 1 + if cx >= pw0: cx = pw0 - 1 + g: int = int(c0.plane[cy * pw0 + cx]) + img.pixels[y * _j_w + x] = im.make_pixel(g, g, g, 255) + else: + cr: JComp* = &_j_comps[ci_cr] + cb: JComp* = &_j_comps[ci_cb] + yv: int = int(c0.plane[((y * c0.vs) / _j_maxv) * pw0 + ((x * c0.hs) / _j_maxh)]) + crv: int = int(cr.plane[((y * cr.vs) / _j_maxv) * (cr.bw * 8) + ((x * cr.hs) / _j_maxh)]) + cbv: int = int(cb.plane[((y * cb.vs) / _j_maxv) * (cb.bw * 8) + ((x * cb.hs) / _j_maxh)]) + if _j_rgb_mode != 0: + img.pixels[y * _j_w + x] = im.make_pixel(yv, crv, cbv, 255) + else: + r: int = yv + int(float(1.402) * float(crv - 128)) + g: int = yv - int(float(0.344136) * float(cbv - 128)) - int(float(0.714136) * float(crv - 128)) + b: int = yv + int(float(1.772) * float(cbv - 128)) + if r < 0: r = 0 + if r > 255: r = 255 + if g < 0: g = 0 + if g > 255: g = 255 + if b < 0: b = 0 + if b > 255: b = 255 + img.pixels[y * _j_w + x] = im.make_pixel(r, g, b, 255) + x = x + 1 + y = y + 1 + return img + +# --------------------------------------------------------------------- main +def decode(src: uint8*, src_len: uint64) -> im.Image*: + global _j_src, _j_len, _j_pos, _j_marker, _j_err, _j_w, _j_h, _j_ncomp, _j_restart, _j_rgb_mode, _j_planes_ready + global _j_stage, _j_scan_no + _j_stage = 0 + _j_scan_no = 0 + if src is None or src_len < 4: return None + if int(src[0]) != 0xFF or int(src[1]) != 0xD8: return None + _j_stage = 1 + _j_src = src + _j_len = src_len + _j_pos = 2 + _j_marker = -1 + _j_err = 0 + _j_w = 0 + _j_h = 0 + _j_ncomp = 0 + _j_restart = 0 + _j_rgb_mode = 0 + _j_planes_ready = 0 + i: int = 0 + while i < 4: + _j_qt_used[i] = 0 + i = i + 1 + while True: + m: int = _j_next_marker() + if m < 0 or m == 0xD9: break + if m == 0xD8: continue + if m >= 0xD0 and m <= 0xD7: continue + if m == 0x01: continue + if _j_pos + 2 > _j_len: break + seclen: int = _j_u16(_j_pos) + if seclen < 2: break + pend: uint64 = _j_pos + uint64(seclen) + if pend > _j_len: break + p: uint64 = _j_pos + 2 + if m == 0xDB: + if _j_parse_dqt(p, pend) != 0: return None + _j_pos = pend + elif m == 0xC4: + if _j_parse_dht(p, pend) != 0: return None + _j_pos = pend + elif m == 0xC0 or m == 0xC1: + if _j_parse_sof(p, pend, 0) != 0: return None + _j_pos = pend + elif m == 0xC2: + # Progressive JPEG (SOF2) is parsed by the coefficient machinery + # below but successive-approximation refinement is not trusted yet: + # reject it instead of returning a partially-wrong image. + _j_stage = 15 + return None + elif m == 0xDD: + if p + 2 <= pend: _j_restart = _j_u16(p) + _j_pos = pend + elif m == 0xEE: + # Adobe APP14: transform 0 means the components are RGB. + if pend - p >= 12: + tr: int = int(_j_src[p + 11]) + if tr == 0: _j_rgb_mode = 1 + _j_pos = pend + elif m == 0xDA: + if _j_parse_sos(p, pend) != 0: + _j_stage = 12 + return None + _j_stage = 3 + if _j_prepare() != 0: + _j_stage = 13 + return None + _j_stage = 4 + kind: int = 0 + if _j_ss == 0: + if _j_ah != 0: kind = 1 + else: + if _j_ah != 0: kind = 3 + else: kind = 2 + _j_stage = 100 + _j_scan_no * 10 + kind + _j_pos = pend + if _j_scan() != 0: + return None + _j_scan_no = _j_scan_no + 1 + _j_stage = 5 + else: + _j_pos = pend + if _j_w <= 0 or _j_h <= 0 or _j_ncomp < 1: + _j_stage = 10 + return None + if _j_ncomp == 4: + _j_stage = 11 + return None + _j_stage = 6 + img: im.Image* = _j_render() + if img is None: return None + _j_stage = 7 + return img diff --git a/vpsdk/imagesee/png.vp b/vpsdk/imagesee/png.vp new file mode 100644 index 0000000..702aac1 --- /dev/null +++ b/vpsdk/imagesee/png.vp @@ -0,0 +1,303 @@ +# PNG decoder (RFC 2083): all standard color types, bit depths 1/2/4/8/16, +# plus Adam7 interlacing. Output is packed 0xAARRGGBB in `Image.pixels`. + +import vpsdk.imagesee.inflate as inflate +import vpsdk.imagesee.image as im +import vpsdk.stdlib as stdlib +import string +from stdint import * + +define PNG_SIG_0 = 0x89 +define PNG_SIG_1 = 0x50 +define PNG_SIG_2 = 0x4E +define PNG_SIG_3 = 0x47 + +_png_src: uint8* = None +_png_len: uint64 = 0 +_png_idat: uint8* = None +_png_idat_len: uint64 = 0 +_png_w: int = 0 +_png_h: int = 0 +_png_depth: int = 0 +_png_ctype: int = 0 +_png_interlace: int = 0 +_png_channels: int = 0 +_png_raw: uint8* = None +_png_plte: [768]uint8 +_png_plte_n: int = 0 +_png_trns: [256]uint8 +_png_trns_n: int = 0 +_png_err: int = 0 +_png_raw_off: uint64 = 0 + +# Adam7 pass origins / steps. +_ax0: [7]int = [0, 4, 0, 2, 0, 1, 0] +_ay0: [7]int = [0, 0, 4, 0, 2, 0, 1] +_adx: [7]int = [8, 8, 4, 4, 2, 2, 1] +_ady: [7]int = [8, 8, 8, 4, 4, 2, 2] + +def _be32(d: uint8*, o: uint64) -> uint32: + v: uint32 = (uint32(d[o]) << 24) | (uint32(d[o + 1]) << 16) + v = v | (uint32(d[o + 2]) << 8) | uint32(d[o + 3]) + return v + +def _sig_ok(d: uint8*) -> int: + if d[0] != uint8(PNG_SIG_0): return 0 + if d[1] != uint8(PNG_SIG_1): return 0 + if d[2] != uint8(PNG_SIG_2): return 0 + if d[3] != uint8(PNG_SIG_3): return 0 + return 1 + +def _channels_of(ct: int) -> int: + if ct == 0: return 1 + if ct == 2: return 3 + if ct == 3: return 1 + if ct == 4: return 2 + if ct == 6: return 4 + return 0 + +def _depth_ok(ct: int, bd: int) -> int: + if bd != 1 and bd != 2 and bd != 4 and bd != 8 and bd != 16: return 0 + if ct == 0: return 1 + if ct == 3: return 1 if bd <= 8 else 0 + if ct == 2 or ct == 4 or ct == 6: return 1 if bd == 8 or bd == 16 else 0 + return 0 + +def _rowbytes(w: int) -> int: + bits: int = w * _png_channels * _png_depth + return (bits + 7) / 8 + +def _pass_w(w: int, p: int) -> int: + x0: int = _ax0[p] + if w <= x0: return 0 + return (w - x0 + _adx[p] - 1) / _adx[p] + +def _pass_h(h: int, p: int) -> int: + y0: int = _ay0[p] + if h <= y0: return 0 + return (h - y0 + _ady[p] - 1) / _ady[p] + +def _raw_size() -> uint64: + if _png_interlace == 0: + return uint64(_png_h * (1 + _rowbytes(_png_w))) + total: uint64 = 0 + p: int = 0 + while p < 7: + pw: int = _pass_w(_png_w, p) + ph: int = _pass_h(_png_h, p) + if pw > 0 and ph > 0: + total = total + uint64(ph * (1 + _rowbytes(pw))) + p = p + 1 + return total + +def _sample(line: uint8*, idx: int, depth: int) -> int: + if depth == 8: + return int(line[idx]) + if depth == 16: + return (int(line[idx * 2]) << 8) | int(line[idx * 2 + 1]) + bitpos: int = idx * depth + byte: int = int(line[bitpos >> 3]) + shift: int = 8 - depth - (bitpos & 7) + return (byte >> shift) & ((1 << depth) - 1) + +def _paeth(a: int, b: int, c: int) -> int: + p: int = a + b - c + pa: int = p - a + if pa < 0: pa = -pa + pb: int = p - b + if pb < 0: pb = -pb + pc: int = p - c + if pc < 0: pc = -pc + if pa <= pb and pa <= pc: return a + if pb <= pc: return b + return c + +def _unfilter(ft: int, cur: uint8*, prev: uint8*, n: int, bpp: int): + i: int = 0 + while i < n: + a: int = 0 + c: int = 0 + if i >= bpp: + a = int(cur[i - bpp]) + c = int(prev[i - bpp]) + b: int = int(prev[i]) + x: int = int(cur[i]) + if ft == 1: + x = x + a + elif ft == 2: + x = x + b + elif ft == 3: + x = x + ((a + b) >> 1) + elif ft == 4: + x = x + _paeth(a, b, c) + cur[i] = uint8(x & 0xFF) + i = i + 1 + +def _scale(v: int, depth: int) -> int: + if depth == 8: return v + if depth == 16: return v >> 8 + mx: int = (1 << depth) - 1 + return (v * 255 + (mx / 2)) / mx + +def _pixel_argb(line: uint8*, idx: int) -> uint32: + ct: int = _png_ctype + bd: int = _png_depth + if ct == 0: + raw: int = _sample(line, idx, bd) + g: int = _scale(raw, bd) + a: int = 255 + if _png_trns_n >= 2: + tv: int = (int(_png_trns[0]) << 8) | int(_png_trns[1]) + if raw == tv: a = 0 + return im.make_pixel(g, g, g, a) + if ct == 2: + r: int = _sample(line, idx * 3, bd) + g: int = _sample(line, idx * 3 + 1, bd) + b: int = _sample(line, idx * 3 + 2, bd) + a: int = 255 + if _png_trns_n >= 6: + tr: int = (int(_png_trns[0]) << 8) | int(_png_trns[1]) + tg: int = (int(_png_trns[2]) << 8) | int(_png_trns[3]) + tb: int = (int(_png_trns[4]) << 8) | int(_png_trns[5]) + if r == tr and g == tg and b == tb: a = 0 + return im.make_pixel(_scale(r, bd), _scale(g, bd), _scale(b, bd), a) + if ct == 3: + pi: int = _sample(line, idx, bd) + if pi * 3 + 2 >= 768: return im.make_pixel(0, 0, 0, 255) + r: int = int(_png_plte[pi * 3]) + g: int = int(_png_plte[pi * 3 + 1]) + b: int = int(_png_plte[pi * 3 + 2]) + a: int = 255 + if pi < _png_trns_n: a = int(_png_trns[pi]) + return im.make_pixel(r, g, b, a) + if ct == 4: + g: int = _sample(line, idx * 2, bd) + a: int = _sample(line, idx * 2 + 1, bd) + return im.make_pixel(_scale(g, bd), _scale(g, bd), _scale(g, bd), _scale(a, bd)) + if ct == 6: + r: int = _sample(line, idx * 4, bd) + g: int = _sample(line, idx * 4 + 1, bd) + b: int = _sample(line, idx * 4 + 2, bd) + a: int = _sample(line, idx * 4 + 3, bd) + return im.make_pixel(_scale(r, bd), _scale(g, bd), _scale(b, bd), _scale(a, bd)) + return im.make_pixel(0, 0, 0, 255) + +def _decode_pass(out: uint32*, p: int): + global _png_raw_off + pw: int = _pass_w(_png_w, p) + ph: int = _pass_h(_png_h, p) + px0: int = _ax0[p] + py0: int = _ay0[p] + pdx: int = _adx[p] + pdy: int = _ady[p] + if _png_interlace == 0: + # Non-interlaced images are a single full-size pass, not Adam7 pass 0. + pw = _png_w + ph = _png_h + px0 = 0 + py0 = 0 + pdx = 1 + pdy = 1 + if pw <= 0 or ph <= 0: return + rb: int = _rowbytes(pw) + bpp: int = (_png_channels * _png_depth + 7) / 8 + if bpp < 1: bpp = 1 + cur: uint8* = uint8*(stdlib.malloc(uint64(rb))) + prev: uint8* = uint8*(stdlib.malloc(uint64(rb))) + if cur is None or prev is None: return + string.memset(rawptr(cur), 0, uint64(rb)) + string.memset(rawptr(prev), 0, uint64(rb)) + off: uint64 = _png_raw_off + ry: int = 0 + while ry < ph: + ft: int = int(_png_raw[off]) + off = off + 1 + string.memcpy(rawptr(cur), rawptr(uint64(_png_raw) + off), uint64(rb)) + off = off + uint64(rb) + _unfilter(ft, cur, prev, rb, bpp) + y: int = py0 + ry * pdy + rx: int = 0 + while rx < pw: + x: int = px0 + rx * pdx + if x < _png_w and y < _png_h: + out[y * _png_w + x] = _pixel_argb(cur, rx) + rx = rx + 1 + string.memcpy(rawptr(prev), rawptr(cur), uint64(rb)) + ry = ry + 1 + _png_raw_off = off + +def decode(src: uint8*, src_len: uint64) -> im.Image*: + global _png_src, _png_len, _png_w, _png_h, _png_depth, _png_ctype + global _png_interlace, _png_channels, _png_raw, _png_plte_n, _png_trns_n + global _png_idat, _png_idat_len, _png_err, _png_raw_off + if src is None or src_len < 33: return None + if _sig_ok(src) == 0: return None + _png_src = src + _png_len = src_len + _png_w = 0 + _png_h = 0 + _png_plte_n = 0 + _png_trns_n = 0 + _png_err = 0 + _png_raw_off = 0 + _png_idat = uint8*(stdlib.malloc(src_len)) + if _png_idat is None: return None + _png_idat_len = 0 + pos: uint64 = 8 + got_ihdr: int = 0 + while pos + 12 <= src_len: + clen: uint64 = uint64(_be32(src, pos)) + if pos + 12 + clen > src_len: return None + typ0: int = int(src[pos + 4]) + typ1: int = int(src[pos + 5]) + typ2: int = int(src[pos + 6]) + typ3: int = int(src[pos + 7]) + dpos: uint64 = pos + 8 + if typ0 == 73 and typ1 == 72 and typ2 == 68 and typ3 == 82: # IHDR + if clen < 13: return None + _png_w = int(_be32(src, dpos)) + _png_h = int(_be32(src, dpos + 4)) + _png_depth = int(src[dpos + 8]) + _png_ctype = int(src[dpos + 9]) + _png_interlace = int(src[dpos + 12]) + _png_channels = _channels_of(_png_ctype) + if _png_w <= 0 or _png_h <= 0 or _png_w > 16384 or _png_h > 16384: return None + if _png_channels == 0 or _depth_ok(_png_ctype, _png_depth) == 0: return None + if int(src[dpos + 10]) != 0 or int(src[dpos + 11]) != 0: return None + got_ihdr = 1 + elif typ0 == 80 and typ1 == 76 and typ2 == 84 and typ3 == 69: # PLTE + n: uint64 = clen + if n > 768: n = 768 + string.memcpy(rawptr(&_png_plte[0]), rawptr(uint64(src) + dpos), n) + _png_plte_n = int(n / 3) + elif typ0 == 116 and typ1 == 82 and typ2 == 78 and typ3 == 83: # tRNS + n: uint64 = clen + if n > 256: n = 256 + string.memcpy(rawptr(&_png_trns[0]), rawptr(uint64(src) + dpos), n) + _png_trns_n = int(n) + elif typ0 == 73 and typ1 == 68 and typ2 == 65 and typ3 == 84: # IDAT + string.memcpy(rawptr(uint64(_png_idat) + _png_idat_len), + rawptr(uint64(src) + dpos), clen) + _png_idat_len = _png_idat_len + clen + elif typ0 == 73 and typ1 == 69 and typ2 == 78 and typ3 == 68: # IEND + break + pos = pos + 12 + clen + if got_ihdr == 0 or _png_idat_len == 0: return None + + rawsz: uint64 = _raw_size() + if rawsz == 0: return None + _png_raw = uint8*(stdlib.malloc(rawsz)) + if _png_raw is None: return None + got_raw: int = inflate.zlib_decompress(_png_idat, _png_idat_len, _png_raw, rawsz) + if got_raw < 0 or uint64(got_raw) != rawsz: return None + + img: im.Image* = im.new_image(_png_w, _png_h, im.IMAGE_FMT_PNG) + if img is None: return None + if _png_interlace == 0: + _decode_pass(img.pixels, 0) + else: + p: int = 0 + while p < 7: + _decode_pass(img.pixels, p) + p = p + 1 + return img diff --git a/vpsdk/process.vp b/vpsdk/process.vp index 1b7f106..064ccd3 100644 --- a/vpsdk/process.vp +++ b/vpsdk/process.vp @@ -5,12 +5,12 @@ def exit(code: int): syscall._syscall1(uint64(syscall.Syscall.EXIT), uint64(code)) def reboot(): - """Reset the machine.""" - syscall._syscall0(uint64(syscall.Syscall.REBOOT)) + """Reset the machine via Linux reboot(169, LINUX_REBOOT_CMD_RESTART).""" + syscall.reboot_linux(uint64(0x01234567)) def shutdown(): - """Power off the machine via ACPI S5.""" - syscall._syscall0(uint64(syscall.Syscall.SHUTDOWN)) + """Power off the machine via Linux reboot(169, LINUX_REBOOT_CMD_POWER_OFF).""" + syscall.reboot_linux(uint64(0x4321FEDC)) def thread_exit(): """当前用户线程显式退出:内核回收其用户栈并标记线程结束(不返回)。""" @@ -25,11 +25,29 @@ def get_pid() -> int: """当前进程 pid(getpid)。/dev/desk 的事件按 pid 过滤,建窗前就要拿到它。""" return int(syscall._syscall0(uint64(syscall.Syscall.GETPID))) +def get_ppid() -> int: + return int(syscall._syscall0(uint64(syscall.Syscall.GETPPID))) + def spawn(path: const str, blocking: int = 0) -> int: - return int(syscall._syscall2(uint64(syscall.Syscall.SPAWN), uint64(path), uint64(blocking))) + """ViperOS launcher: start a new child and keep the caller alive.""" + return int(syscall._syscall2(uint64(syscall.Syscall.SPAWN_PROC), uint64(path), uint64(blocking))) + +def execve(path: const str, argv: rawptr, envp: rawptr) -> int: + """Linux execve: replace the current process (returns only on failure).""" + return int(syscall._syscall3(uint64(syscall.Syscall.SPAWN), uint64(path), uint64(argv), uint64(envp))) + +def fork() -> int: + return int(syscall._syscall0(uint64(syscall.Syscall.FORK))) + +def clone(flags: uint64, stack: uint64, ptid: uint64 = 0, ctid: uint64 = 0, tls: uint64 = 0) -> int: + return int(syscall._syscall5(uint64(syscall.Syscall.CLONE), flags, stack, ptid, ctid, tls)) + +def wait4(pid: int, status: rawptr, options: uint32 = 0) -> int: + return int(syscall._syscall4(uint64(syscall.Syscall.WAIT4), uint64(pid), + uint64(status), uint64(options), 0)) def thread_create(func: rawptr, arg: rawptr = None) -> int: - return int(syscall._syscall2(uint64(syscall.Syscall.THREAD_CREATE), uint64(func), uint64(arg))) + return int(syscall._syscall2(uint64(syscall.Syscall.THREAD_CREATE_OLD), uint64(func), uint64(arg))) class Thread: diff --git a/vpsdk/syscall.vp b/vpsdk/syscall.vp index 0a909dc..dab1509 100644 --- a/vpsdk/syscall.vp +++ b/vpsdk/syscall.vp @@ -1,39 +1,89 @@ # ============================================================================ -# 系统调用号表(SDK 侧镜像)。**与 VKernel/Kernel/intr/syscall.vp 的同名 enum 逐条对应**: -# vpsdk 不允许依赖 VKernel,所以这里是一份必须手工同步的副本 —— 增删/改号/改名两边一起改。 -# 编号口径:与 Linux x86-64 ABI 同名同号;Linux 有而我们未实现的留空不占号; -# 我们独有的能力不再新增 syscall,最终走 devfs 设备文件或下沉到用户层(见 0x1000+ 段的标注)。 +# 系统调用号表(SDK 侧镜像,由 Scripts/syscall_table.py 生成,禁止手改)。 +# 编号口径:与 Linux x86-64 ABI 同名同号;Linux 有而尚未实现的调用暂不占号。 # ============================================================================ +# BEGIN GENERATED SYSCALL TABLE enum Syscall: - # ---- Linux 对齐段(号 = Linux x86-64 的号)---- - READ = 0 # read(fd, buf, count) - WRITE = 1 # write(fd, buf, count) - OPEN = 2 # open(path, flags) - CLOSE = 3 # close(fd) - STAT = 4 # stat(path, info)(结构体仍是本系统的 fileinfo) - SEEK = 8 # lseek(fd, offset, whence)(whence 目前恒 0 = SEEK_SET) - MMAP = 9 # mmap(size) → VA(ABI 待向 Linux 的 addr/len/prot/flags/fd/off 靠拢) - MUNMAP = 11 # munmap(ptr, size)(目前空实现) - IOCTL = 16 # ioctl(fd, request, arg):设备控制(当前用于 /dev/fb0 查几何) - YIELD = 24 # sched_yield() - GETPID = 39 # getpid():返回当前进程 pid(app 用它过滤 /dev/desk 事件) - THREAD_CREATE = 56 # clone():建用户线程,返回 tid - SPAWN = 59 # execve():加载并运行新 ELF,返回 pid - THREAD_EXIT = 60 # exit():退役当前线程 - MKDIR = 83 # mkdir(path) - REMOVE = 87 # unlink(path) - EXIT = 231 # exit_group():退役当前线程(窗口由 explorer 侧回收) - # ---- 临时私有段(0x1000+):Linux 无对应,且**逐条都会被消掉** ---- - # 目标形态:私有能力不占 syscall 号 → 改 devfs 设备文件。 - # S3e 删 UI 11 个;S4 删 SERIAL_PUTS(改 /dev/serial0);S5 删 TELL/SIZE - # (改 lseek 的 SEEK_CUR / SEEK_END,见 vpsdk/fs.vp 的 tell/fsize)。 - OPENDIR = 0x1003 # TODO(linux):openat(O_DIRECTORY) + getdents64 - READDIR = 0x1004 # TODO(linux):getdents64 - CLOSEDIR = 0x1005 # TODO(linux):close - LOAD_SO = 0x1006 # TODO(userspace): dynamic library loading - SO_CALL1 = 0x1007 # TODO(userspace): dynamic library call - REBOOT = 0x1008 - SHUTDOWN = 0x1009 + # ---- Linux 对齐段(号 = Linux x86-64 的号) ---- + READ = 0 # read(fd, buf, count) + WRITE = 1 # write(fd, buf, count) + OPEN = 2 # open(path, flags) - Linux O_* + CLOSE = 3 # close(fd) + STAT = 4 # stat(path, fileinfo*) - legacy struct + FSTAT = 5 # fstat(fd, struct stat*) + POLL = 7 # poll(fds, nfds, timeout) - stub 0 + SEEK = 8 # lseek(fd, offset, whence) -> new offset + MMAP = 9 # mmap(addr, len, prot, flags, fd, off) + MPROTECT = 10 # mprotect(addr, len, prot) + MUNMAP = 11 # munmap(addr, len) + BRK = 12 # brk(addr) -> new break + RT_SIGACTION = 13 # rt_sigaction() - bookkeeping + RT_SIGPROCMASK = 14 # rt_sigprocmask() - bookkeeping + RT_SIGRETURN = 15 # rt_sigreturn() + IOCTL = 16 # ioctl(fd, request, arg) + PREAD64 = 17 # pread64(fd, buf, count, offset) + PWRITE64 = 18 # pwrite64(fd, buf, count, offset) + READV = 19 # readv(fd, iov, iovcnt) + WRITEV = 20 # writev(fd, iov, iovcnt) + ACCESS = 21 # access(path, mode) + PIPE = 22 # pipe(int[2]) + YIELD = 24 # sched_yield() + MREMAP = 25 # mremap(old, oldsz, newsz, flags, newaddr) + MADVISE = 28 # madvise(addr, len, advice) + DUP = 32 # dup(fd) + DUP2 = 33 # dup2(oldfd, newfd) + NANOSLEEP = 35 # nanosleep(req, rem) + GETPID = 39 # getpid() + CLONE = 56 # clone(flags, stack, ptid, ctid, tls) + FORK = 57 # fork() - copy address space + SPAWN = 59 # execve(path, argv, envp) + THREAD_EXIT = 60 # exit(): retire current thread + WAIT4 = 61 # wait4(pid, status, options, rusage) + KILL = 62 # kill(pid, sig) - bookkeeping + UNAME = 63 # uname(utsname*) + FCNTL = 72 # fcntl(fd, cmd, arg) + GETCWD = 79 # getcwd(buf, size) + CHDIR = 80 # chdir(path) + RENAME = 82 # rename(old, new) + MKDIR = 83 # mkdir(path) + RMDIR = 84 # rmdir(path) + REMOVE = 87 # unlink(path) + READLINK = 89 # readlink(path, buf, size) - no symlinks + GETUID = 102 # getuid() + GETGID = 104 # getgid() + GETEUID = 107 # geteuid() + GETEGID = 108 # getegid() + GETTIMEOFDAY = 96 # gettimeofday(tv, tz) + GETPPID = 110 # getppid() + ARCH_PRCTL = 158 # arch_prctl(code, addr) + REBOOT_LINUX = 169 # reboot(magic1, magic2, cmd, arg) + GETTID = 186 # gettid() + FUTEX = 202 # futex(uaddr, op, val, timeout) + GETDENTS64 = 217 # getdents64(fd, buf, count) + SET_TID_ADDRESS = 218 # set_tid_address(tidptr) + CLOCK_GETTIME = 228 # clock_gettime(clockid, timespec*) + CLOCK_GETRES = 229 # clock_getres(clockid, timespec*) + EXIT = 231 # exit_group(): terminate all threads + OPENAT = 257 # openat(dirfd, path, flags, mode) + NEWFSTATAT = 262 # newfstatat(dirfd, path, stat*, flags) + UNLINKAT = 263 # unlinkat(dirfd, path, flags) + READLINKAT = 267 # readlinkat(dirfd, path, buf, size) + FACCESSAT = 269 # faccessat(dirfd, path, mode, flags) + SET_ROBUST_LIST = 273 # set_robust_list(head, len) + DUP3 = 292 # dup3(oldfd, newfd, flags) + PIPE2 = 293 # pipe2(int[2], flags) + PRLIMIT64 = 302 # prlimit64(pid, resource, new, old) + GETRANDOM = 318 # getrandom(buf, len, flags) + STATX = 332 # statx(...) - -ENOSYS, glibc falls back + RSEQ = 334 # rseq(rseq, len, flags) - -ENOSYS + # ---- 临时私有段(0x1000+):Linux 无对应,逐条迁出 ---- + LOAD_SO = 0x1006 # TODO(userspace): dynamic library loading + SO_CALL1 = 0x1007 # TODO(userspace): dynamic library call + REBOOT = 0x1008 # TODO(linux): reboot(169) + SHUTDOWN = 0x1009 # TODO(linux): reboot(RB_POWER_OFF) + SPAWN_PROC = 0x100A # TODO(userspace): Terminal run helper + THREAD_CREATE_OLD = 0x100B # TODO(userspace): ViperOS thread_create +# END GENERATED SYSCALL TABLE # ---- mmap 的 prot / flags(与内核 VKernel/intr/syscall.vp 及各 Linux 同名常量一致)---- define PROT_NONE = 0x0 @@ -46,6 +96,39 @@ define MAP_FIXED = 0x10 define MAP_ANONYMOUS = 0x20 # fd = -1(Linux 匿名映射约定) define MMAP_FD_ANON: uint64 = 0xFFFFFFFFFFFFFFFF +define MAP_FAILED: uint64 = 0xFFFFFFFFFFFFFFFF +define MREMAP_MAYMOVE = 1 +define MREMAP_FIXED = 2 + +# ---- AT_* / fcntl(Linux 值)---- +define AT_FDCWD = -100 +define AT_SYMLINK_NOFOLLOW = 256 +define AT_REMOVEDIR = 512 +define AT_EMPTY_PATH = 4096 + +define F_DUPFD = 0 +define F_GETFD = 1 +define F_SETFD = 2 +define F_GETFL = 3 +define F_SETFL = 4 +define F_DUPFD_CLOEXEC = 1030 +define FD_CLOEXEC = 1 + +# ---- TLS / futex / time / wait / reboot ---- +define ARCH_SET_GS = 0x1001 +define ARCH_SET_FS = 0x1002 +define ARCH_GET_FS = 0x1003 +define ARCH_GET_GS = 0x1004 +define FUTEX_WAIT = 0 +define FUTEX_WAKE = 1 +define FUTEX_PRIVATE_FLAG = 128 +define CLOCK_REALTIME = 0 +define CLOCK_MONOTONIC = 1 +define WNOHANG = 1 +define LINUX_REBOOT_MAGIC1 = 0xFEE1DEAD +define LINUX_REBOOT_MAGIC2 = 672274793 +define LINUX_REBOOT_CMD_RESTART = 0x01234567 +define LINUX_REBOOT_CMD_POWER_OFF = 0x4321FEDC def _syscall0(num: uint64) -> uint64: ret: uint64 @@ -91,6 +174,21 @@ def _syscall3(num: uint64, a1: uint64, a2: uint64, a3: uint64) -> uint64: ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDX]) return ret +def _syscall4(num: uint64, a1: uint64, a2: uint64, a3: uint64, a4: uint64) -> uint64: + ret: uint64 + __asm__(f"""mov rdi, {asminp(a1, ASM_DESCR.REG_ANY)} + mov rsi, {asminp(a2, ASM_DESCR.REG_ANY)} + mov rdx, {asminp(a3, ASM_DESCR.REG_ANY)} + mov r10, {asminp(a4, ASM_DESCR.REG_ANY)} + mov rax, {asminp(num, ASM_DESCR.REG_ANY)} + syscall""", + out=[asmout(ret, ASM_DESCR.OUTPUT_REG)], + op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RCX, + ASM_DESCR.CLOBBER_R11, ASM_DESCR.CLOBBER_RDI, + ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDX, + ASM_DESCR.CLOBBER_R10]) + return ret + def _syscall5(num: uint64, a1: uint64, a2: uint64, a3: uint64, a4: uint64, a5: uint64) -> uint64: ret: uint64 __asm__(f"""mov rdi, {asminp(a1, ASM_DESCR.REG_ANY)} @@ -126,10 +224,185 @@ def _syscall6(num: uint64, a1: uint64, a2: uint64, a3: uint64, a4: uint64, a5: u return ret def mmap(addr: uint64, length: uint64, prot: uint64, flags: uint64, fd: uint64, offset: uint64) -> uint64: - """mmap(addr, length, prot, flags, fd, offset):返回映射起始 VA,失败返回 0。 + """mmap(addr, length, prot, flags, fd, offset):返回映射起始 VA。 - 匿名映射:fd = MMAP_FD_ANON(-1) 且 flags 带 MAP_ANONYMOUS; - 映射设备(如 /dev/fb0):fd 传该设备的 fd。addr / MAP_FIXED 由内核挑地址。 - (定义放在 _syscall6 之后,避免同模块内前向引用。) + 内核遵循 Linux 语义(失败返回 MAP_FAILED = -1);SDK 为兼容既有调用方 + 把它归一化为 0(C 代码里判 `== 0`)。 """ - return _syscall6(uint64(Syscall.MMAP), addr, length, prot, flags, fd, offset) + va: uint64 = _syscall6(uint64(Syscall.MMAP), addr, length, prot, flags, fd, offset) + if va == MAP_FAILED: return uint64(0) + return va + +def munmap(addr: uint64, length: uint64) -> int: + return int(_syscall2(uint64(Syscall.MUNMAP), addr, length)) + +def mprotect(addr: uint64, length: uint64, prot: uint64) -> int: + return int(_syscall3(uint64(Syscall.MPROTECT), addr, length, prot)) + +def brk(addr: uint64) -> uint64: + return _syscall1(uint64(Syscall.BRK), addr) + +def mremap(old_addr: uint64, old_size: uint64, new_size: uint64, flags: uint64, new_addr: uint64 = 0) -> uint64: + va: uint64 = _syscall5(uint64(Syscall.MREMAP), old_addr, old_size, new_size, flags, new_addr) + if va == MAP_FAILED: return uint64(0) + return va + +def madvise(addr: uint64, length: uint64, advice: uint32) -> int: + return int(_syscall3(uint64(Syscall.MADVISE), addr, length, uint64(advice))) + +# ============================================================================ +# P0 flat wrappers (Linux ABI). Higher level helpers live in vpsdk.fs / process. +# ============================================================================ + +def uname(buf: rawptr) -> int: + return int(_syscall1(uint64(Syscall.UNAME), uint64(buf))) + +def getuid() -> int: + return int(_syscall0(uint64(Syscall.GETUID))) + +def geteuid() -> int: + return int(_syscall0(uint64(Syscall.GETEUID))) + +def getgid() -> int: + return int(_syscall0(uint64(Syscall.GETGID))) + +def getegid() -> int: + return int(_syscall0(uint64(Syscall.GETEGID))) + +def getppid() -> int: + return int(_syscall0(uint64(Syscall.GETPPID))) + +def gettid() -> int: + return int(_syscall0(uint64(Syscall.GETTID))) + +def getcwd(buf: rawptr, size: uint64) -> int: + return int(_syscall2(uint64(Syscall.GETCWD), uint64(buf), size)) + +def chdir(path: const str) -> int: + return int(_syscall1(uint64(Syscall.CHDIR), uint64(path))) + +def access(path: const str, mode: uint32) -> int: + return int(_syscall2(uint64(Syscall.ACCESS), uint64(path), uint64(mode))) + +def faccessat(dirfd: int, path: const str, mode: uint32, flags: uint32) -> int: + return int(_syscall4(uint64(Syscall.FACCESSAT), uint64(dirfd), uint64(path), + uint64(mode), uint64(flags))) + +def readlink(path: const str, buf: rawptr, size: uint64) -> int: + return int(_syscall3(uint64(Syscall.READLINK), uint64(path), uint64(buf), size)) + +def readlinkat(dirfd: int, path: const str, buf: rawptr, size: uint64) -> int: + return int(_syscall4(uint64(Syscall.READLINKAT), uint64(dirfd), uint64(path), + uint64(buf), size)) + +def set_tid_address(tidptr: rawptr) -> int: return int(_syscall1(uint64(Syscall.SET_TID_ADDRESS), uint64(tidptr))) + +def set_robust_list(head: rawptr, length: uint64) -> int: + return int(_syscall2(uint64(Syscall.SET_ROBUST_LIST), uint64(head), length)) + +def rseq(rseq_ptr: rawptr, length: uint32, flags: uint32) -> int: + return int(_syscall3(uint64(Syscall.RSEQ), uint64(rseq_ptr), + uint64(length), uint64(flags))) + +def prlimit64(pid: int, resource: uint32, new_limit: rawptr, old_limit: rawptr) -> int: + return int(_syscall4(uint64(Syscall.PRLIMIT64), uint64(pid), uint64(resource), + uint64(new_limit), uint64(old_limit))) + +def getrandom(buf: rawptr, length: uint64, flags: uint32) -> int: + return int(_syscall3(uint64(Syscall.GETRANDOM), uint64(buf), length, uint64(flags))) + +# ---------------------------------------------------------------- P1 ---- + +def openat(dirfd: int, path: const str, flags: uint32, mode: uint32 = 0) -> int: + return int(_syscall4(uint64(Syscall.OPENAT), uint64(dirfd), uint64(path), + uint64(flags), uint64(mode))) + +def fstat(fd: int, st: rawptr) -> int: + return int(_syscall2(uint64(Syscall.FSTAT), uint64(fd), uint64(st))) + +def newfstatat(dirfd: int, path: const str, st: rawptr, flags: uint32) -> int: + return int(_syscall4(uint64(Syscall.NEWFSTATAT), uint64(dirfd), uint64(path), + uint64(st), uint64(flags))) + +def getdents64(fd: int, buf: rawptr, count: uint64) -> int: + return int(_syscall3(uint64(Syscall.GETDENTS64), uint64(fd), uint64(buf), count)) + +def dup(fd: int) -> int: + return int(_syscall1(uint64(Syscall.DUP), uint64(fd))) + +def dup2(oldfd: int, newfd: int) -> int: + return int(_syscall2(uint64(Syscall.DUP2), uint64(oldfd), uint64(newfd))) + +def dup3(oldfd: int, newfd: int, flags: uint32) -> int: + return int(_syscall3(uint64(Syscall.DUP3), uint64(oldfd), uint64(newfd), uint64(flags))) + +def fcntl(fd: int, cmd: uint32, arg: uint64) -> int: + return int(_syscall3(uint64(Syscall.FCNTL), uint64(fd), uint64(cmd), arg)) + +def pread64(fd: int, buf: rawptr, count: uint32, offset: uint64) -> int: + return int(_syscall4(uint64(Syscall.PREAD64), uint64(fd), uint64(buf), + uint64(count), offset)) + +def pwrite64(fd: int, buf: rawptr, count: uint32, offset: uint64) -> int: + return int(_syscall4(uint64(Syscall.PWRITE64), uint64(fd), uint64(buf), + uint64(count), offset)) + +def rename(old_path: const str, new_path: const str) -> int: + return int(_syscall2(uint64(Syscall.RENAME), uint64(old_path), uint64(new_path))) + +def rmdir(path: const str) -> int: + return int(_syscall1(uint64(Syscall.RMDIR), uint64(path))) + +def unlinkat(dirfd: int, path: const str, flags: uint32) -> int: + return int(_syscall3(uint64(Syscall.UNLINKAT), uint64(dirfd), uint64(path), + uint64(flags))) + +# ---------------------------------------------------------------- P4/P5/P6 ---- + +def arch_prctl(code: int, addr: uint64) -> int: + return int(_syscall2(uint64(Syscall.ARCH_PRCTL), uint64(code), addr)) + +def futex(uaddr: uint64, op: uint32, val: uint32, timeout: uint64 = 0) -> int: + return int(_syscall4(uint64(Syscall.FUTEX), uaddr, uint64(op), uint64(val), timeout)) + +def clock_gettime(clockid: int, ts: rawptr) -> int: + return int(_syscall2(uint64(Syscall.CLOCK_GETTIME), uint64(clockid), uint64(ts))) + +def clock_getres(clockid: int, ts: rawptr) -> int: + return int(_syscall2(uint64(Syscall.CLOCK_GETRES), uint64(clockid), uint64(ts))) + +def gettimeofday(tv: rawptr, tz: rawptr = None) -> int: + return int(_syscall2(uint64(Syscall.GETTIMEOFDAY), uint64(tv), uint64(tz))) + +def nanosleep(req: rawptr, rem: rawptr = None) -> int: + return int(_syscall2(uint64(Syscall.NANOSLEEP), uint64(req), uint64(rem))) + +def rt_sigaction(sig: int, act: rawptr, oldact: rawptr, sigsetsize: uint64 = 8) -> int: + return int(_syscall4(uint64(Syscall.RT_SIGACTION), uint64(sig), uint64(act), + uint64(oldact), sigsetsize)) + +def rt_sigprocmask(how: int, setp: rawptr, oldset: rawptr, sigsetsize: uint64 = 8) -> int: + return int(_syscall4(uint64(Syscall.RT_SIGPROCMASK), uint64(how), uint64(setp), + uint64(oldset), sigsetsize)) + +def kill(pid: int, sig: int) -> int: + return int(_syscall2(uint64(Syscall.KILL), uint64(pid), uint64(sig))) + +def readv(fd: int, iov: rawptr, iovcnt: int) -> int: + return int(_syscall3(uint64(Syscall.READV), uint64(fd), uint64(iov), uint64(iovcnt))) + +def writev(fd: int, iov: rawptr, iovcnt: int) -> int: + return int(_syscall3(uint64(Syscall.WRITEV), uint64(fd), uint64(iov), uint64(iovcnt))) + +def poll(fds: rawptr, nfds: uint64, timeout: int) -> int: + return int(_syscall3(uint64(Syscall.POLL), uint64(fds), nfds, uint64(timeout))) + +def pipe2(fds: rawptr, flags: uint32 = 0) -> int: + return int(_syscall2(uint64(Syscall.PIPE2), uint64(fds), uint64(flags))) + +def pipe(fds: rawptr) -> int: + return int(_syscall1(uint64(Syscall.PIPE), uint64(fds))) + +def reboot_linux(cmd: uint64) -> int: + return int(_syscall4(uint64(Syscall.REBOOT_LINUX), uint64(LINUX_REBOOT_MAGIC1), + uint64(LINUX_REBOOT_MAGIC2), cmd, uint64(0))) diff --git a/vpsdk/vpui.vp b/vpsdk/vpui.vp index 5f8efaa..f0f4baf 100644 --- a/vpsdk/vpui.vp +++ b/vpsdk/vpui.vp @@ -1,2074 +1,2099 @@ -import vpsdk.window as window -import vpsdk.process as process -import vpsdk.devfs as devfs -import vpsdk.stdlib as stdlib -import vpsdk.fonttools.fonttools as fonttools -import string - -define TITLE_H = 32 -define MAX_WIDGETS = 24 -define CHAR_W = 8 -define CHAR_H = 16 - -define TEXT_MODE_DIRECT = 0 -define TEXT_MODE_MANAGED = 1 -define TEXT_QUEUE_MAX = 256 -define TEXT_CHUNK = 28 - -define WIDGET_LABEL = 1 -define WIDGET_BUTTON = 2 -define WIDGET_ENTRY = 3 -define WIDGET_TEXT = 4 -define WIDGET_SCROLLBAR = 5 -define WIDGET_FBCANVAS = 6 -define WIDGET_FRAME = 7 - -define VERTICAL = 1 -define HORIZONTAL = 2 -define TEXT_MAX_LINES = 200 - -define PACK_TOP = 1 -define PACK_BOTTOM = 2 -define PACK_LEFT = 3 -define PACK_RIGHT = 4 - -define FILL_NONE = 0 -define FILL_X = 1 -define FILL_Y = 2 -define FILL_BOTH = 3 - -define STICKY_NONE = 0 -define STICKY_W = 1 -define STICKY_E = 2 -define STICKY_N = 4 -define STICKY_S = 8 -define STICKY_NSEW = 15 - -_g_wl_type: [MAX_WIDGETS]int -_g_wl_side: [MAX_WIDGETS]int -_g_wl_fill: [MAX_WIDGETS]int -_g_wl_expand: [MAX_WIDGETS]int -_g_wl_padx: [MAX_WIDGETS]int -_g_wl_pady: [MAX_WIDGETS]int -_g_wl_row: [MAX_WIDGETS]int -_g_wl_col: [MAX_WIDGETS]int -_g_wl_rowspan: [MAX_WIDGETS]int -_g_wl_colspan: [MAX_WIDGETS]int -_g_wl_sticky: [MAX_WIDGETS]int -_g_layout_init: int - -_hex_tbl: [17]char = "0123456789ABCDEF" -_hex_buf: [20]char = "0000000000000000000" - -def _serial_puts(s: const str): - # S4:SERIAL_PUTS 私有 syscall 已删除,改走 /dev/serial0 设备 - devfs.serial_puts(s) - -def _ptr_to_hex(val: uint64, buf: rawptr): - buf[0] = 48 - buf[1] = 120 - i: int = 15 - while i >= 0: - nibble: int = int(val & uint64(0xF)) - buf[2 + i] = _hex_tbl[nibble] - val = val >> 4 - i -= 1 - buf[18] = 0 - -def _fill_rect(fb: uint32*, fw: int, fh: int, x1: int, y1: int, w: int, h: int, color: uint32): - if not fb: return - if x1 < 0: w += x1; x1 = 0 - if y1 < 0: h += y1; y1 = 0 - if x1 + w > fw: w = fw - x1 - if y1 + h > fh: h = fh - y1 - if w <= 0 or h <= 0: return - # 逐行批量填充(string.memset32 → rep stosd):整窗填充从"每个像素一次带边界判断的写" - # 降成"每行一次 rep stosd"。app 每帧都要把整窗填一遍背景色,逐像素在 QEMU 下是主要热点。 - py: int = y1 - while py < y1 + h: - string.memset32(rawptr(uint64(fb) + uint64((py * fw + x1) * 4)), color, uint64(w)) - py += 1 - -def _draw_hline(fb: uint32*, fw: int, fh: int, x1: int, y: int, w: int, color: uint32): - if not fb: return - if y < 0 or y >= fh: return - for px in range(x1, x1 + w): - if px >= 0 and px < fw: - fb[y * fw + px] = color - -def _draw_vline(fb: uint32*, fw: int, fh: int, x: int, y1: int, h: int, color: uint32): - if not fb: return - if x < 0 or x >= fw: return - for py in range(y1, y1 + h): - if py >= 0 and py < fh: - fb[py * fw + x] = color - -def _lighten(color: uint32, amount: int) -> uint32: - r: int = int(color & 0xFF) - g: int = int((color >> 8) & 0xFF) - b: int = int((color >> 16) & 0xFF) - r = r + ((255 - r) * amount) / 256 - g = g + ((255 - g) * amount) / 256 - b = b + ((255 - b) * amount) / 256 - return uint32(r | (g << 8) | (b << 16)) - -def _darken(color: uint32, amount: int) -> uint32: - r: int = int(color & 0xFF) - g: int = int((color >> 8) & 0xFF) - b: int = int((color >> 16) & 0xFF) - r = r - (r * amount) / 256 - g = g - (g * amount) / 256 - b = b - (b * amount) / 256 - return uint32(r | (g << 8) | (b << 16)) - - -font: [256][16]uint8 = [ - [0x00,0x00,0x00,0x00,0x00,0x40,0x55,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x00,0x00,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x00,0x02,0x56,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x00,0x42,0x56,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x55,0x55,0x57,0x71,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x57,0x54,0x57,0x71,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x57,0x54,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x57,0x51,0x52,0x72,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x57,0x55,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x57,0x55,0x57,0x71,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x56,0x51,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x54,0x54,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x50,0x50,0x57,0x74,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x51,0x51,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x57,0x55,0x57,0x74,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x53,0x54,0x56,0x74,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x37,0x15,0x15,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x32,0x16,0x12,0x12,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x20,0x6E,0x22,0x2E,0x28,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x37,0x11,0x13,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x35,0x15,0x17,0x11,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x37,0x14,0x17,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x37,0x14,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x37,0x11,0x12,0x12,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x37,0x15,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x37,0x15,0x17,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x36,0x11,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x34,0x14,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x30,0x10,0x17,0x14,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x31,0x11,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x37,0x15,0x17,0x14,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x10,0x33,0x14,0x16,0x14,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00], - [0x6C,0x6C,0x6C,0x6C,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x36,0x36,0x36,0x7F,0x7F,0x36,0x36,0x36,0x7F,0x7F,0x36,0x36,0x36,0x00,0x00,0x00], - [0x0C,0x0C,0x3E,0x7F,0x68,0x68,0x3E,0x0B,0x0B,0x7F,0x3E,0x18,0x18,0x00,0x00,0x00], - [0x60,0x60,0x66,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x66,0x06,0x06,0x00,0x00,0x00], - [0x38,0x6C,0x6C,0x6C,0x6C,0x6C,0x38,0x6C,0x6D,0x66,0x66,0x6F,0x3B,0x00,0x00,0x00], - [0x18,0x18,0x18,0x18,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x1C,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x1C,0x00,0x00], - [0x38,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x38,0x00,0x00], - [0x00,0x00,0x18,0x18,0x7E,0x3C,0x3C,0x3C,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x00,0x18,0x18,0x18,0x7E,0x7E,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x30,0x00], - [0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00], - [0x00,0x00,0x06,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x60,0x00,0x00,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x66,0x6E,0x6E,0x7E,0x76,0x76,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x18,0x38,0x78,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x06,0x06,0x0E,0x1C,0x38,0x70,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x06,0x06,0x1C,0x1C,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x0C,0x0C,0x1C,0x1C,0x3C,0x2C,0x6C,0x7E,0x7E,0x0C,0x0C,0x0C,0x0C,0x00,0x00,0x00], - [0x7E,0x7E,0x60,0x60,0x7C,0x7E,0x06,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x7E,0x7E,0x06,0x06,0x0C,0x0C,0x18,0x18,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x66,0x66,0x7E,0x3C,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x30,0x00], - [0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0x60,0x30,0x18,0x0C,0x06,0x02,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x40,0x60,0x30,0x18,0x0C,0x06,0x06,0x0C,0x18,0x30,0x60,0x40,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x06,0x0C,0x0C,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x66,0x6E,0x6A,0x6A,0x6A,0x6E,0x60,0x60,0x7C,0x3C,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x66,0x66,0x7E,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], - [0x7C,0x7E,0x66,0x66,0x66,0x7C,0x7C,0x66,0x66,0x66,0x66,0x7E,0x7C,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x78,0x7C,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0x7C,0x78,0x00,0x00,0x00], - [0x7E,0x7E,0x60,0x60,0x60,0x7C,0x7C,0x60,0x60,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00], - [0x7E,0x7E,0x60,0x60,0x60,0x7C,0x7C,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x60,0x60,0x60,0x6E,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x66,0x66,0x66,0x66,0x66,0x7E,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], - [0x7E,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00], - [0x3E,0x3E,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x6C,0x7C,0x38,0x00,0x00,0x00], - [0x66,0x66,0x66,0x6C,0x6C,0x78,0x78,0x78,0x6C,0x6C,0x66,0x66,0x66,0x00,0x00,0x00], - [0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00], - [0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00], - [0x66,0x66,0x66,0x66,0x76,0x76,0x7E,0x6E,0x6E,0x66,0x66,0x66,0x66,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x6A,0x6A,0x6C,0x7E,0x36,0x00,0x00,0x00], - [0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x6C,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], - [0x3C,0x7E,0x66,0x60,0x60,0x78,0x3C,0x0E,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x7E,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00], - [0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x18,0x00,0x00,0x00], - [0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x77,0x77,0x22,0x00,0x00,0x00], - [0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x3C,0x3C,0x66,0x66,0x66,0x66,0x00,0x00,0x00], - [0x66,0x66,0x66,0x66,0x7E,0x3C,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00], - [0x7E,0x7E,0x06,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00], - [0x78,0x78,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x78,0x78,0x00,0x00], - [0xC0,0xC0,0x60,0x60,0x30,0x30,0x18,0x0C,0x0C,0x06,0x06,0x03,0x03,0x00,0x00,0x00], - [0x1E,0x1E,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x1E,0x1E,0x00,0x00], - [0x3C,0x7E,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00], - [0x30,0x38,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x3C,0x3E,0x06,0x3E,0x7E,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00], - [0x60,0x60,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x7C,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x60,0x60,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x06,0x06,0x06,0x06,0x3E,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x7E,0x7E,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x1C,0x3E,0x30,0x30,0x30,0x7C,0x7C,0x30,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x3E,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x3E,0x3C,0x00], - [0x60,0x60,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], - [0x18,0x18,0x00,0x00,0x38,0x38,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00], - [0x0C,0x0C,0x00,0x00,0x3C,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x7C,0x78,0x00], - [0x60,0x60,0x60,0x60,0x66,0x66,0x6C,0x78,0x78,0x6C,0x6C,0x66,0x66,0x00,0x00,0x00], - [0x38,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x36,0x7F,0x6B,0x6B,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x60,0x60,0x60,0x60,0x00], - [0x00,0x00,0x00,0x00,0x3E,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x07,0x07,0x00], - [0x00,0x00,0x00,0x00,0x6C,0x7E,0x76,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x3C,0x7E,0x62,0x70,0x3C,0x0E,0x46,0x7E,0x3C,0x00,0x00,0x00], - [0x30,0x30,0x30,0x30,0x7C,0x7C,0x30,0x30,0x30,0x30,0x30,0x3C,0x1C,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x18,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x63,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x7F,0x36,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x66,0x66,0x3C,0x3C,0x18,0x3C,0x3C,0x66,0x66,0x00,0x00,0x00], - [0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x7E,0x7C,0x00], - [0x00,0x00,0x00,0x00,0x7E,0x7E,0x0C,0x0C,0x18,0x30,0x30,0x7E,0x7E,0x00,0x00,0x00], - [0x0E,0x1E,0x18,0x18,0x18,0x18,0x30,0x30,0x18,0x18,0x18,0x18,0x1E,0x0E,0x00,0x00], - [0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00], - [0x70,0x78,0x18,0x18,0x18,0x18,0x0C,0x0C,0x18,0x18,0x18,0x18,0x78,0x70,0x00,0x00], - [0x31,0x79,0x6B,0x4F,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], - [0x00,0x00,0x70,0x13,0x24,0x26,0x24,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], -] - -class TextCmd: - x: int - y: int - color: uint32 - s: [32]char - -_text_mode: int = TEXT_MODE_MANAGED -_ttf_font: fonttools.Font* = None -_ttf_atlas: uint8* = None -_ttf_state: int = 0 -_text_queue: [TEXT_QUEUE_MAX]TextCmd -_text_qn: int = 0 - -def set_text_mode(mode: int): - global _text_mode - _text_mode = mode - -def _ensure_ttf(): - global _ttf_font, _ttf_atlas, _ttf_state - if _ttf_state != 0: return - f: fonttools.Font* = fonttools.load("/FONTS/FONT.TTF") - if f is None: - _ttf_state = -1 - return - a: uint8* = fonttools.build_ascii_atlas(f, CHAR_W, CHAR_H) - if a is None: - _ttf_state = -1 - return - _ttf_font = f - _ttf_atlas = a - _ttf_state = 1 - -def _queue_text(x: int, y: int, s: const str, color: uint32): - global _text_qn - if _text_mode != TEXT_MODE_MANAGED: return - cx: int = x - cy: int = y - i: int = 0 - while s[i] != 0: - if s[i] == 10: - cx = x - cy += CHAR_H - i += 1 - continue - if _text_qn >= TEXT_QUEUE_MAX: return - cmd: TextCmd* = &_text_queue[_text_qn] - cmd.x = cx - cmd.y = cy - cmd.color = color - n: int = 0 - while n < TEXT_CHUNK and s[i] != 0 and s[i] != 10: - cmd.s[n] = s[i] - n += 1 - i += 1 - cmd.s[n] = 0 - _text_qn += 1 - cx += n * CHAR_W - -def _flush_text(win_id: int): - global _text_qn - if _text_qn == 0: return - for i in range(_text_qn): - cmd: TextCmd* = &_text_queue[i] - window.text(win_id, cmd.x, cmd.y, cmd.color, &cmd.s[0]) - _text_qn = 0 - -def _draw_char(buf: uint32*, bw: int, bh: int, x: int, y: int, ch: char, color: uint32): - if not buf: return - if bw <= 0 or bh <= 0: return - ci: int = int(uint8(ch)) - if _text_mode == TEXT_MODE_DIRECT: - _ensure_ttf() - if _ttf_state == 1 and ci < 128: - fy: int = 0 - while fy < CHAR_H: - py: int = y + fy - if py >= 0 and py < bh: - fx: int = 0 - while fx < CHAR_W: - a: int = int(_ttf_atlas[(ci * CHAR_H + fy) * CHAR_W + fx]) - if a != 0: - px: int = x + fx - if px >= 0 and px < bw: - idx: int = py * bw + px - if a >= 255: - buf[idx] = color - else: - dst: uint32 = buf[idx] - dr: int = int(dst & 0xFF) - dg: int = int((dst >> 8) & 0xFF) - db: int = int((dst >> 16) & 0xFF) - sr: int = int(color & 0xFF) - sg: int = int((color >> 8) & 0xFF) - sb: int = int((color >> 16) & 0xFF) - r: int = (sr * a + dr * (255 - a)) / 255 - g: int = (sg * a + dg * (255 - a)) / 255 - b: int = (sb * a + db * (255 - a)) / 255 - buf[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) - fx += 1 - fy += 1 - return - if ci >= 128: ci = 0 - fy: int = 0 - while fy < CHAR_H: - if y + fy < 0 or y + fy >= bh: - fy += 1 - continue - row: uint8 = font[ci][fy] - if row == 0: - fy += 1 - continue - for fx in range(CHAR_W): - if row & (0x80 >> fx): - px: int = x + fx - if px >= 0 and px < bw: - buf[(y + fy) * bw + px] = color - fy += 1 - -def _draw_text(buf: uint32*, bw: int, bh: int, x: int, y: int, s: const str, color: uint32): - if not buf: return - if bw <= 0 or bh <= 0: return - if _text_mode == TEXT_MODE_MANAGED: - _queue_text(x, y, s, color) - return - cx: int = x - cy: int = y - i: int = 0 - while True: - ch: char = s[i] - if ch == 0: break - if ch == 10: - cy += CHAR_H - cx = x - else: - _draw_char(buf, bw, bh, cx, cy, ch, color) - cx += CHAR_W - i += 1 - -# ============================================================================ -# tkinter 风格的抽象基类层次(仿 tkinter 原始抽象) -# -# Misc(mixin:master/tk 公共状态 + winfo_master/toplevel、bind 等公共方法) -# ├─ Tk(Wm, Misc) 主窗口类(Wm 声明 WM 协议面,Tk 提供落地实现) -# └─ BaseWidget(Misc) -# └─ Widget(BaseWidget) 所有普通控件的抽象基类(Button/Label/Entry… 都继承 Widget) -# -# 字段模型(与 Test/MultiInheritTest 一致):父类字段被**展平**进子类,子类自己的方法可 -# 直接访问全部展平字段;字段偏移按"定义该方法的那一层"自己的展平布局计算(该测试里 -# ColoredCircle(ShapeM, ColorM) = (ptr, i32, f64):偏移 0 是唯一的共享 vptr,各基类字段依次 -# 跟在后面,虚表被合并)。由此有三条硬约束: -# ① Misc 的字段必须在所有派生类里落到同一偏移 —— 偏移 0 被共享 vptr 占用,Misc 的字段 -# 从 8 起,只要它前面没有"带字段的基类"就成立; -# ② 因此 `Wm`(Tk 的最左基类)必须**无字段**,否则会把 Misc 的字段整体后移,而 Misc 自己 -# 的方法仍按自身布局取偏移 ⇒ 静默错位; -# ③ 基类方法里**不要写 `self.<虚拟方法>()` 的自我分派**:多继承下非最左基类的方法在派生 -# 类的合并虚表里整体后移,而方法体内按"定义类自己的虚表"取索引 ⇒ 会指到别的槽位。 -# 所以 winfo_*/title 这类"需要看具体对象状态"的方法一律在 Misc / Wm 里只做声明,由 -# Tk / Widget 覆写实现;外部按具体类型调用即可正常走虚表。 -# ============================================================================ - -# ---- bind 回调注册表:按 (对象, 事件类型) 记录回调。 -# tkinter 用 这类字符串作为 sequence,这里改用 window.EVENT_TYPE_* 整型 -# (本运行时的事件类型就是这些整型,比较字符串在 VPC 模型下既慢又易错)。 -define MAX_BINDINGS = 64 - -_bind_obj: [MAX_BINDINGS]Misc* = [None] -_bind_seq: [MAX_BINDINGS]int = [0] -_bind_cb: [MAX_BINDINGS]rawptr = [None] - -def _bind_add(obj: Misc*, seq: int, func: rawptr) -> int: - i: int = 0 - while i < MAX_BINDINGS: - if _bind_obj[i] is None: - _bind_obj[i] = obj - _bind_seq[i] = seq - _bind_cb[i] = func - return i - i += 1 - return -1 - -def _bind_remove(obj: Misc*, seq: int) -> int: - i: int = 0 - while i < MAX_BINDINGS: - if _bind_obj[i] is obj and _bind_seq[i] == seq: - _bind_obj[i] = None - _bind_cb[i] = None - return 0 - i += 1 - return -1 - -def _bind_invoke(obj: Misc*, seq: int, evt: window.InputEvent*) -> int: - i: int = 0 - while i < MAX_BINDINGS: - if _bind_obj[i] is obj and _bind_seq[i] == seq: - cb: rawptr = _bind_cb[i] - if cb is None: return 0 - # 与 Button 回调同一约定:rawptr 是 VPC 生成的闭包对象 {fn, env}, - # fn 期望 rdi = env ⇒ 必须取 [cb+8] 传 rdi、再由 [cb] 调用。 - __asm__(f"""push r12 -push r13 -mov r13, {asminp(cb, ASM_DESCR.REG_ANY)} -mov rdi, qword ptr [r13 + 8] -mov r12, rsp -and rsp, -16 -call qword ptr [r13] -mov rsp, r12 -pop r13 -pop r12""", - op=[ASM_DESCR.CLOBBER_MEMORY, - ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, - ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, - ASM_DESCR.CLOBBER_RDI, - ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, - ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) - return 1 - i += 1 - return 0 - -# ---- Misc:Tk 与所有控件的公共 mixin ---- -# 只放"所有 Tk 对象都有的东西":公共状态(master/tk)、公共方法(winfo_master/toplevel、bind), -# 以及需要各具体类自己回答的查询(winfo_x/… 这里只声明,由 Tk / Widget 覆写)。 -# 约束:这里**不做 `self.<虚拟方法>()` 的自我分派**。多继承下非最左基类的方法在派生类的 -# 合并虚表里会整体后移,而方法体内按"定义类自己的虚表"取索引 ⇒ 会指到别的槽位。 -# 因此查询一律由具体类覆写、由外部按具体类型调用;Misc 自身只用自己声明的字段。 -virtual class Misc: - master: Misc* # 父容器;主窗口为 None - tk: Tk* # 所属顶层窗口;主窗口指向自身 - - # 公共状态初始化(各具体类的 __init__ 调用;不依赖基类 __init__ 链) - def _misc_setup(self, master: Misc* = None): - self.master = master - self.tk = None - if master is not None: - self.tk = master.tk - - # ---- winfo 系(tkinter Misc.winfo_* 的对应物)---- - def winfo_master(self) -> Misc*: - return self.master - - def winfo_toplevel(self) -> Tk*: - return self.tk - - # ---- bind 系(回调在 BaseWidget.dispatch_event 里派发)---- - def bind(self, seq: int, func: rawptr) -> int: - return _bind_add(Misc*(self), seq, func) - - def unbind(self, seq: int) -> int: - return _bind_remove(Misc*(self), seq) - - def _invoke_binding(self, seq: int, evt: window.InputEvent*) -> int: - return _bind_invoke(Misc*(self), seq, evt) - - # ---- 查询/生命周期:抽象,由 Tk / Widget 覆写 ---- - def winfo_exists(self) -> int: - return 0 - - def winfo_ismapped(self) -> int: - return 0 - - def winfo_x(self) -> int: - return 0 - - def winfo_y(self) -> int: - return 0 - - def winfo_width(self) -> int: - return 0 - - def winfo_height(self) -> int: - return 0 - - def destroy(self): - pass - - def update(self): - pass - -# ---- Wm:顶层窗口的"窗口管理器协议"(tkinter Wm 的对应物)---- -# 无字段(见文件头的布局约束),这里只**声明**协议面,落地实现由持有窗口状态的 Tk 覆写。 -# 同样不做自我分派(理由见 Misc 上方注释)。 -virtual class Wm: - def title(self, text: str): - pass - - def geometry(self, x: int, y: int, w: int, h: int): - pass - - def resizable(self, rw: int, rh: int): - pass - -# ---- BaseWidget:控件公共部分(tkinter BaseWidget 的对应物)---- -virtual class BaseWidget(Misc): - # 事件派发:先跑 bind 注册的回调,再交给控件自己的 handle_event。 - # 放在基类是为了让 Tk 的事件循环只调这一个入口,各控件无需关心 bind。 - def dispatch_event(self, evt: window.InputEvent*) -> int: - hit: int = self._invoke_binding(int(evt.type), evt) - handled: int = self.handle_event(evt) - if hit or handled: - return 1 - return 0 - - # 控件自身的事件处理(抽象,由具体控件覆写) - def handle_event(self, evt: window.InputEvent*) -> int: - return 0 - - # 抽象:具体控件在 Widget 层覆写(visible 是 Widget 的字段,不能在这里访问) - def destroy(self): - pass - -def _register_widget(master: Misc*, widget: Widget*) -> int: - tk: Tk* = master.tk - if tk is None: return -1 - idx: int = tk.widget_count - if idx < MAX_WIDGETS: - tk.widgets[idx] = widget - tk.widget_count = idx + 1 - return idx - -virtual class Widget(BaseWidget): - x: int - y: int - w: int - h: int - visible: int - widget_type: int - focused: int - hovered: int - - def destroy(self): - self.visible = 0 - - # ---- Misc 查询的覆写:控件用自己的几何信息回答 winfo_* ---- - def winfo_exists(self) -> int: - return self.visible - - def winfo_ismapped(self) -> int: - return self.visible - - def winfo_x(self) -> int: - return self.x - - def winfo_y(self) -> int: - return self.y - - def winfo_width(self) -> int: - return self.w - - def winfo_height(self) -> int: - return self.h - - def draw(self, fb: uint32*, fw: int, fh: int): - pass - - def handle_event(self, evt: window.InputEvent*) -> int: - return 0 - - def contains(self, px: int, py: int) -> int: - if px >= self.x and px < self.x + self.w and py >= self.y and py < self.y + self.h: - return 1 - return 0 - - def place(self, x: int = 0, y: int = 0, w: int = 0, h: int = 0): - self.x = x - self.y = y - if w > 0: - self.w = w - if h > 0: - self.h = h - -# 容器中坐标叠加:master 不是顶层窗口(即 Frame 这类容器)时,子控件先按 Tk 的口径算位置, -# 再叠加容器自身原点 ⇒ 视觉上落在容器内部。本运行时的 pack/grid 是 Tk 级、按全局下标走, -# 所以这里只做"原点叠加"这一层,不做真正的嵌套布局。 -def _container_offset(root: Tk*, w: Widget*): - if w.master is None: return - if uint64(w.master) == uint64(root): return - pw: Widget* = Widget*(w.master) - if pw.widget_type == WIDGET_FRAME: - w.x += pw.x - w.y += pw.y - -# ---- Frame:容器控件(tkinter Frame 的对应物)---- -# 可作 master 使用:子控件经 _misc_setup 继承它的 tk,照常注册进顶层窗口;位置再叠加本 -# 容器原点(见 _container_offset)。 -virtual class Frame(Widget): - fill_color: uint32 - border_color: uint32 - border_w: int - - def __init__(self, master: Misc*, w: int, h: int): - self.x = 0 - self.y = 0 - self.w = w - self.h = h - self.visible = 1 - self._misc_setup(master) - self.widget_type = WIDGET_FRAME - self.focused = 0 - self.hovered = 0 - self.fill_color = uint32(0xFF2A2A3A) - self.border_color = uint32(0xFF505070) - self.border_w = 1 - _register_widget(master, Widget*(self)) - - def draw(self, fb: uint32*, fw: int, fh: int): - _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.fill_color) - if self.border_w > 0: - _draw_hline(fb, fw, fh, self.x, self.y, self.w, self.border_color) - _draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, self.border_color) - _draw_vline(fb, fw, fh, self.x, self.y, self.h, self.border_color) - _draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, self.border_color) - - def handle_event(self, evt: window.InputEvent*) -> int: - return 0 - - def config(self, fill_color: uint32 = None, border_color: uint32 = None): - if fill_color is not None: - self.fill_color = fill_color - if border_color is not None: - self.border_color = border_color - -virtual class Label(Widget): - text: [64]char - color: uint32 - - def __init__(self, master: Misc*, text: str, color: uint32): - self.x = 0 - self.y = 0 - self.w = 0 - self.h = 0 - self.visible = 1 - self._misc_setup(master) - self.widget_type = WIDGET_LABEL - self.focused = 0 - self.hovered = 0 - self.color = color - i: int = 0 - for ch in text: - if i >= 63: break - self.text[i] = ch - i += 1 - self.text[i] = 0 - # Label 没有显式 w/h 参数:自然尺寸必须由文本推出。否则 pack 拿到的请求尺寸是 - # 0×0 ⇒ 不占布局空间,且 PACK_BOTTOM/非 fill 时会被贴到可视区域之外(文字被裁掉)。 - self._fit_natural() - _register_widget(master, Widget*(self)) - - # 按当前 text 计算自然尺寸:宽取最长行,高取行数(多行 Label 也成立)。 - def _fit_natural(self): - line_chars: int = 0 - max_chars: int = 0 - lines: int = 1 - i: int = 0 - while i < 64: - ch: char = self.text[i] - if ch == 0: break - if ch == 10: - if line_chars > max_chars: max_chars = line_chars - line_chars = 0 - lines += 1 - else: - line_chars += 1 - i += 1 - if line_chars > max_chars: max_chars = line_chars - self.w = max_chars * CHAR_W - self.h = lines * CHAR_H - - def draw(self, fb: uint32*, fw: int, fh: int): - _draw_text(fb, fw, fh, self.x, self.y, &self.text[0], self.color) - - def handle_event(self, evt: window.InputEvent*) -> int: - return 0 - - def config(self, text: const str = None, color: uint32 = None): - if text is not None: - i: int = 0 - while i < 63: - ch: char = *(text + i) - self.text[i] = ch - if ch == 0: break - i += 1 - self.text[i] = 0 - self._fit_natural() - if color is not None: - self.color = color - -virtual class Button(Widget): - text: [32]char - fill_color: uint32 - text_color: uint32 - pressed: int - callback: rawptr - - def __init__(self, master: Misc*, w: int, h: int, text: str, callback: rawptr): - self.x = 0 - self.y = 0 - self.w = w - self.h = h - self.visible = 1 - self._misc_setup(master) - self.widget_type = WIDGET_BUTTON - self.focused = 0 - self.hovered = 0 - self.fill_color = uint32(0x3C3C5A) - self.text_color = uint32(0xFFFFFFFF) - self.pressed = 0 - self.callback = callback - i: int = 0 - for ch in text: - if i >= 31: break - self.text[i] = ch - i += 1 - self.text[i] = 0 - _register_widget(master, Widget*(self)) - - def draw(self, fb: uint32*, fw: int, fh: int): - fill: uint32 = self.fill_color - border: uint32 = uint32(0x8080B0) - if self.pressed: - fill = _lighten(self.fill_color, 60) - border = uint32(0xA0A0D0) - elif self.hovered: - fill = _lighten(self.fill_color, 30) - border = uint32(0xA0A0D0) - _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, fill) - top_left: uint32 = _lighten(fill, 80) - bottom_right: uint32 = _darken(fill, 80) - if self.pressed: - top_left = _darken(fill, 60) - bottom_right = _lighten(fill, 40) - _draw_hline(fb, fw, fh, self.x, self.y, self.w, top_left) - _draw_hline(fb, fw, fh, self.x, self.y + 1, self.w, top_left) - _draw_vline(fb, fw, fh, self.x, self.y, self.h, top_left) - _draw_vline(fb, fw, fh, self.x + 1, self.y, self.h, top_left) - _draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, bottom_right) - _draw_hline(fb, fw, fh, self.x, self.y + self.h - 2, self.w, bottom_right) - _draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, bottom_right) - _draw_vline(fb, fw, fh, self.x + self.w - 2, self.y, self.h, bottom_right) - _draw_hline(fb, fw, fh, self.x - 1, self.y - 1, self.w + 2, border) - _draw_hline(fb, fw, fh, self.x - 1, self.y + self.h, self.w + 2, border) - _draw_vline(fb, fw, fh, self.x - 1, self.y - 1, self.h + 2, border) - _draw_vline(fb, fw, fh, self.x + self.w, self.y - 1, self.h + 2, border) - tl: int = int(string.strlen(&self.text[0])) - tx: int = self.x + (self.w - 8 * tl) / 2 - ty: int = self.y + (self.h - CHAR_H) / 2 - _draw_text(fb, fw, fh, tx, ty, &self.text[0], self.text_color) - - def handle_event(self, evt: window.InputEvent*) -> int: - etype: uint32 = evt.type - ex: int = evt.x - ey: int = evt.y - if etype == window.EVENT_TYPE_HOVER: - if self.contains(ex, ey): - if not self.hovered: - self.hovered = 1 - return 1 - else: - if self.hovered and not self.pressed: - self.hovered = 0 - return 1 - elif etype == window.EVENT_TYPE_HOVER_LEAVE: - if self.hovered: - self.hovered = 0 - return 1 - elif etype == window.EVENT_TYPE_CLICK: - if self.contains(ex, ey): - self.pressed = 1 - return 1 - elif etype == window.EVENT_TYPE_RELEASE: - if self.pressed: - self.pressed = 0 - if self.contains(ex, ey) and self.callback is not None: - # 闭包调用约定(VPC 生成):callback 是堆上的 {fn, env} 对象,fn 期望 rdi = env。 - # 之前直接 `call r13`(r13 = 闭包对象地址)会跳到堆上执行 - # (现场:rip=0x2040400a 在堆区、cr2=0x1、err=0x5)。 - __asm__(f"""push r12 -push r13 -mov r13, {asminp(self.callback, ASM_DESCR.REG_ANY)} -mov rdi, qword ptr [r13 + 8] -mov r12, rsp -and rsp, -16 -call qword ptr [r13] -mov rsp, r12 -pop r13 -pop r12""", - op=[ASM_DESCR.CLOBBER_MEMORY, - ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, - ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, - ASM_DESCR.CLOBBER_RDI, - ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, - ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) - return 1 - return 0 - - def config(self, text: const str = None, fill_color: uint32 = None, text_color: uint32 = None, callback: rawptr = None): - if text is not None: - i: int = 0 - while i < 31: - ch: char = *(text + i) - self.text[i] = ch - if ch == 0: break - i += 1 - self.text[i] = 0 - if fill_color is not None: - self.fill_color = fill_color - if text_color is not None: - self.text_color = text_color - if callback is not None: - self.callback = callback - -virtual class Entry(Widget): - text: [64]char - text_len: int - cursor_pos: int - max_len: int - bg_color: uint32 - border_color: uint32 - text_color: uint32 - cursor_color: uint32 - - def __init__(self, master: Misc*, w: int, h: int): - self.x = 0 - self.y = 0 - self.w = w - self.h = h - self.visible = 1 - self._misc_setup(master) - self.widget_type = WIDGET_ENTRY - self.focused = 0 - self.hovered = 0 - self.text_len = 0 - self.cursor_pos = 0 - self.max_len = 30 - self.bg_color = uint32(0xFF231919) - self.border_color = uint32(0x606080) - self.text_color = uint32(0x000000) - self.cursor_color = uint32(0x000000) - for i in range(64): - self.text[i] = 0 - _register_widget(master, Widget*(self)) - - def draw(self, fb: uint32*, fw: int, fh: int): - bg: uint32 = self.bg_color - bdr: uint32 = self.border_color - if self.focused: - bg = uint32(0xFFFFFF) - bdr = uint32(0x4488FF) - elif self.hovered: - bdr = uint32(0x8080B0) - _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, bg) - _draw_hline(fb, fw, fh, self.x, self.y, self.w, bdr) - _draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, bdr) - _draw_vline(fb, fw, fh, self.x, self.y, self.h, bdr) - _draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, bdr) - if self.text_len > 0: - tc: uint32 = self.text_color - if not self.focused: - tc = uint32(0xCCCCCC) - _draw_text(fb, fw, fh, self.x + 6, self.y + 7, &self.text[0], tc) - elif not self.focused: - _draw_text(fb, fw, fh, self.x + 6, self.y + 7, "Type here...", uint32(0x888888)) - if self.focused: - cx_pos: int = self.x + 6 + self.cursor_pos * CHAR_W - if cx_pos < self.x + self.w - 2: - _draw_vline(fb, fw, fh, cx_pos, self.y + 5, self.h - 10, self.cursor_color) - - def handle_event(self, evt: window.InputEvent*) -> int: - etype: uint32 = evt.type - ex: int = evt.x - ey: int = evt.y - if etype == window.EVENT_TYPE_HOVER: - if self.contains(ex, ey): - if not self.hovered: - self.hovered = 1 - return 1 - else: - if self.hovered: - self.hovered = 0 - return 1 - elif etype == window.EVENT_TYPE_HOVER_LEAVE: - if self.hovered: - self.hovered = 0 - return 1 - elif etype == window.EVENT_TYPE_CLICK: - if self.contains(ex, ey): - if not self.focused: - self.focused = 1 - tk4: Tk* = self.tk - tk4.focused_widget = Widget*(self) - rel_x: int = ex - self.x - 6 - if rel_x < 0: rel_x = 0 - new_pos: int = (rel_x + CHAR_W / 2) / CHAR_W - if new_pos > self.text_len: new_pos = self.text_len - self.cursor_pos = new_pos - return 1 - else: - if self.focused: - self.focused = 0 - tk5: Tk* = self.tk - tk5.focused_widget = None - return 1 - elif etype == window.EVENT_TYPE_KEY: - if self.focused: - ebtn: uint8 = evt.button - if ebtn == window.KEY_STATE_RELEASE: - return 0 - ch: int = ex - if ch == 8: - if self.cursor_pos > 0: - for i in range(self.cursor_pos, self.text_len): - self.text[i - 1] = self.text[i] - self.text_len -= 1 - self.cursor_pos -= 1 - self.text[self.text_len] = 0 - return 1 - elif ch == window.VK_LEFT: - if self.cursor_pos > 0: - self.cursor_pos -= 1 - return 1 - elif ch == window.VK_RIGHT: - if self.cursor_pos < self.text_len: - self.cursor_pos += 1 - return 1 - elif ch == window.VK_HOME: - self.cursor_pos = 0 - return 1 - elif ch == window.VK_END: - self.cursor_pos = self.text_len - return 1 - elif ch == window.VK_DELETE: - if self.cursor_pos < self.text_len: - for i in range(self.cursor_pos, self.text_len - 1): - self.text[i] = self.text[i + 1] - self.text_len -= 1 - self.text[self.text_len] = 0 - return 1 - elif ch >= 32 and ch < 127: - if self.text_len < self.max_len: - i: int = self.text_len - while i > self.cursor_pos: - self.text[i] = self.text[i - 1] - i -= 1 - self.text[self.cursor_pos] = char(ch) - self.text_len += 1 - self.cursor_pos += 1 - self.text[self.text_len] = 0 - return 1 - return 0 - - def get(self) -> const str: - return &self.text[0] - - def delete(self, start: int, end: int): - self.text_len = 0 - for i in range(64): - self.text[i] = 0 - - def config(self, bg_color: uint32 = None, text_color: uint32 = None, border_color: uint32 = None): - if bg_color is not None: - self.bg_color = bg_color - if text_color is not None: - self.text_color = text_color - if border_color is not None: - self.border_color = border_color - -virtual class Scrollbar(Widget): - orient: int - command: rawptr - slider_first: int - slider_last: int - action: int - action_value: int - pressed: int - dragging: int - drag_start_y: int - drag_start_first: int - trough_color: uint32 - slider_color: uint32 - arrow_color: uint32 - slider_hover: int - - def __init__(self, master: Misc*, w: int, h: int, orient: int): - self.x = 0 - self.y = 0 - self.w = w - self.h = h - self.visible = 1 - self._misc_setup(master) - self.widget_type = WIDGET_SCROLLBAR - self.focused = 0 - self.hovered = 0 - self.orient = orient - self.command = None - self.slider_first = 0 - self.slider_last = 1000 - self.action = 0 - self.action_value = 0 - self.pressed = 0 - self.dragging = 0 - self.drag_start_y = 0 - self.drag_start_first = 0 - self.trough_color = uint32(0x1E1E2E) - self.slider_color = uint32(0x4A4A6A) - self.arrow_color = uint32(0xAAAAAA) - self.slider_hover = 0 - _register_widget(master, Widget*(self)) - - def draw(self, fb: uint32*, fw: int, fh: int): - _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.trough_color) - if self.orient != VERTICAL: return - arrow_sz: int = self.w - if arrow_sz > 16: arrow_sz = 16 - self._draw_arrow_btn(fb, fw, fh, self.x, self.y, self.w, arrow_sz, 0, 1 if self.pressed == 1 else 0) - self._draw_arrow_btn(fb, fw, fh, self.x, self.y + self.h - arrow_sz, self.w, arrow_sz, 1, 1 if self.pressed == 2 else 0) - trough_y: int = self.y + arrow_sz - trough_h: int = self.h - 2 * arrow_sz - if trough_h <= 0: return - if self.slider_last > self.slider_first: - sl_h: int = (self.slider_last - self.slider_first) * trough_h / 1000 - if sl_h < 8: sl_h = 8 - sl_y: int = trough_y + self.slider_first * trough_h / 1000 - if sl_y + sl_h > trough_y + trough_h: - sl_y = trough_y + trough_h - sl_h - if sl_y < trough_y: sl_y = trough_y - color: uint32 = self.slider_color - if self.dragging: - color = _lighten(self.slider_color, 40) - elif self.slider_hover: - color = _lighten(self.slider_color, 20) - _fill_rect(fb, fw, fh, self.x + 1, sl_y, self.w - 2, sl_h, color) - top_c: uint32 = _lighten(color, 60) - bot_c: uint32 = _darken(color, 60) - _draw_hline(fb, fw, fh, self.x + 1, sl_y, self.w - 2, top_c) - _draw_hline(fb, fw, fh, self.x + 1, sl_y + sl_h - 1, self.w - 2, bot_c) - _draw_vline(fb, fw, fh, self.x + 1, sl_y, sl_h, top_c) - _draw_vline(fb, fw, fh, self.x + self.w - 2, sl_y, sl_h, bot_c) - - def _draw_arrow_btn(self, fb: uint32*, fw: int, fh: int, bx: int, by: int, bw: int, bh: int, direction: int, is_pressed: int): - color: uint32 = self.slider_color - if is_pressed: - color = _lighten(self.slider_color, 30) - _fill_rect(fb, fw, fh, bx, by, bw, bh, color) - top_c: uint32 = _lighten(color, 60) - bot_c: uint32 = _darken(color, 60) - if is_pressed: - top_c = _darken(color, 40) - bot_c = _lighten(color, 30) - _draw_hline(fb, fw, fh, bx, by, bw, top_c) - _draw_hline(fb, fw, fh, bx, by + bh - 1, bw, bot_c) - _draw_vline(fb, fw, fh, bx, by, bh, top_c) - _draw_vline(fb, fw, fh, bx + bw - 1, by, bh, bot_c) - cx: int = bx + bw / 2 - cy: int = by + bh / 2 - if direction == 0: - _fill_rect(fb, fw, fh, cx - 1, cy - 3, 3, 1, self.arrow_color) - _fill_rect(fb, fw, fh, cx - 2, cy - 2, 5, 1, self.arrow_color) - _fill_rect(fb, fw, fh, cx - 3, cy - 1, 7, 1, self.arrow_color) - else: - _fill_rect(fb, fw, fh, cx - 3, cy - 1, 7, 1, self.arrow_color) - _fill_rect(fb, fw, fh, cx - 2, cy, 5, 1, self.arrow_color) - _fill_rect(fb, fw, fh, cx - 1, cy + 1, 3, 1, self.arrow_color) - - def handle_event(self, evt: window.InputEvent*) -> int: - etype: uint32 = evt.type - ex: int = evt.x - ey: int = evt.y - if etype == window.EVENT_TYPE_HOVER: - if self.dragging: - self._handle_drag(ey) - return 1 - if self.contains(ex, ey): - if not self.slider_hover: - self.slider_hover = 1 - return 1 - else: - if self.slider_hover and self.pressed == 0: - self.slider_hover = 0 - return 1 - return 0 - elif etype == window.EVENT_TYPE_HOVER_LEAVE: - if self.slider_hover: - self.slider_hover = 0 - if not self.dragging: - self.pressed = 0 - return 1 - return 0 - elif etype == window.EVENT_TYPE_CLICK: - if self.contains(ex, ey): - if self.orient == VERTICAL: - arrow_sz: int = self.w - if arrow_sz > 16: arrow_sz = 16 - if ey < self.y + arrow_sz: - self.pressed = 1 - self.action = 1 - self._fire_command() - return 1 - elif ey >= self.y + self.h - arrow_sz: - self.pressed = 2 - self.action = 2 - self._fire_command() - return 1 - else: - trough_y: int = self.y + arrow_sz - trough_h: int = self.h - 2 * arrow_sz - if trough_h <= 0: return 1 - sl_h: int = (self.slider_last - self.slider_first) * trough_h / 1000 - if sl_h < 8: sl_h = 8 - sl_y: int = trough_y + self.slider_first * trough_h / 1000 - if sl_y + sl_h > trough_y + trough_h: - sl_y = trough_y + trough_h - sl_h - if ey >= sl_y and ey < sl_y + sl_h: - self.pressed = 4 - self.dragging = 1 - self.drag_start_y = ey - self.drag_start_first = self.slider_first - else: - self.pressed = 3 - if ey < sl_y: - self.action = 3 - else: - self.action = 4 - self._fire_command() - return 1 - return 0 - elif etype == window.EVENT_TYPE_RELEASE: - if self.pressed: - self.pressed = 0 - self.dragging = 0 - return 1 - return 0 - return 0 - - def _handle_drag(self, ey: int): - arrow_sz: int = self.w - if arrow_sz > 16: arrow_sz = 16 - trough_y: int = self.y + arrow_sz - trough_h: int = self.h - 2 * arrow_sz - if trough_h <= 0: return - delta_y: int = ey - self.drag_start_y - delta_first: int = delta_y * 1000 / trough_h - new_first: int = self.drag_start_first + delta_first - slider_size: int = self.slider_last - self.slider_first - if slider_size <= 0: slider_size = 1 - if new_first < 0: new_first = 0 - if new_first + slider_size > 1000: new_first = 1000 - slider_size - if new_first != self.slider_first: - self.slider_first = new_first - self.slider_last = new_first + slider_size - self.action = 5 - self.action_value = new_first - self._fire_command() - - def _fire_command(self): - if self.command is not None: - # 同 Button.handle_event:command 是闭包 {fn, env},必须 rdi = env 后 call fn。 - __asm__(f"""push r12 -push r13 -mov r13, {asminp(self.command, ASM_DESCR.REG_ANY)} -mov rdi, qword ptr [r13 + 8] -mov r12, rsp -and rsp, -16 -call qword ptr [r13] -mov rsp, r12 -pop r13 -pop r12""", - op=[ASM_DESCR.CLOBBER_MEMORY, - ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, - ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, - ASM_DESCR.CLOBBER_RDI, - ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, - ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) - - def set(self, first: int, last: int): - self.slider_first = first - self.slider_last = last - -virtual class FBCanvas(Widget): - _fb: uint32* - _cw: int - _ch: int - - def __init__(self, master: Misc*, w: int, h: int): - self.x = 0 - self.y = 0 - self.w = w - self.h = h - self.visible = 1 - self._misc_setup(master) - self.widget_type = WIDGET_FBCANVAS - self.focused = 0 - self.hovered = 0 - self._fb = None - self._cw = w - self._ch = h - _register_widget(master, Widget*(self)) - - def draw(self, fb: uint32*, fw: int, fh: int): - if not self._fb: return - if self._cw <= 0 or self._ch <= 0: return - copy_w: int = self._cw - if copy_w > self.w: copy_w = self.w - copy_h: int = self._ch - if copy_h > self.h: copy_h = self.h - for py in range(copy_h): - ty: int = self.y + py - if ty < 0 or ty >= fh: continue - for px in range(copy_w): - tx: int = self.x + px - if tx < 0 or tx >= fw: continue - fb[ty * fw + tx] = self._fb[py * self._cw + px] - - def handle_event(self, evt: window.InputEvent*) -> int: - return 0 - - def set_fb(self, fb: uint32*, cw: int, ch: int): - self._fb = fb - self._cw = cw - self._ch = ch - - def get_fb(self) -> uint32*: - return self._fb - - def get_w(self) -> int: - return self._cw - - def get_h(self) -> int: - return self._ch - -virtual class Text(Widget): - lines: [TEXT_MAX_LINES][80]char - line_count: int - input_buf: [80]char - input_len: int - cursor_pos: int - prompt: [80]char - bg_color: uint32 - text_color: uint32 - prompt_color: uint32 - cursor_color: uint32 - border_color: uint32 - on_command: rawptr - history: [16][80]char - history_count: int - history_idx: int - scroll_offset: int - max_lines: int - yscrollbar: rawptr - - def __init__(self, master: Misc*, w: int, h: int): - self.x = 0 - self.y = 0 - self.w = w - self.h = h - self.visible = 1 - self._misc_setup(master) - self.widget_type = WIDGET_TEXT - self.focused = 0 - self.hovered = 0 - self.line_count = 0 - self.input_len = 0 - self.cursor_pos = 0 - self.prompt[0] = '>' - self.prompt[1] = ' ' - self.prompt[2] = 0 - self.bg_color = uint32(0x0C0C0C) - self.text_color = uint32(0xCCCCCC) - self.prompt_color = uint32(0xCCCCCC) - self.cursor_color = uint32(0xCCCCCC) - self.border_color = uint32(0x333333) - self.on_command = None - self.history_count = 0 - self.history_idx = 0 - self.scroll_offset = 0 - self.max_lines = TEXT_MAX_LINES - self.yscrollbar = None - for i in range(TEXT_MAX_LINES): - for j in range(79 + 1): - self.lines[i][j] = 0 - for i in range(80): - self.input_buf[i] = 0 - _register_widget(master, Widget*(self)) - - def draw(self, fb: uint32*, fw: int, fh: int): - self._update_scrollbar() - _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.bg_color) - _fill_rect(fb, fw, fh, self.x, self.y + self.h - 2, self.w, 2, self.border_color) - visible: int = self._visible_lines() - for i in range(visible): - line_idx: int = self.scroll_offset + i - if line_idx < self.line_count: - _draw_text(fb, fw, fh, self.x + 4, self.y + i * CHAR_H, &self.lines[line_idx][0], self.text_color) - # 输入行不是一个固定在底部的控件,而是紧跟在最后一条输出之后的一行; - # 输出增多时它随之下移,超出可视区时由 scroll_offset 把它保持在最后一行。 - prompt_row: int = self.line_count - self.scroll_offset - if prompt_row >= 0 and prompt_row < visible: - prompt_y: int = self.y + prompt_row * CHAR_H - plen: int = int(string.strlen(&self.prompt[0])) - _draw_text(fb, fw, fh, self.x + 4, prompt_y, &self.prompt[0], self.prompt_color) - if self.input_len > 0: - _draw_text(fb, fw, fh, self.x + 4 + plen * CHAR_W, prompt_y, - &self.input_buf[0], self.text_color) - cx_pos: int = self.x + 4 + (plen + self.cursor_pos) * CHAR_W - if cx_pos < self.x + self.w - 4: - _fill_rect(fb, fw, fh, cx_pos, prompt_y, CHAR_W // 2, CHAR_H, self.cursor_color) - - def handle_event(self, evt: window.InputEvent*) -> int: - etype: uint32 = evt.type - ex: int = evt.x - ey: int = evt.y - if etype == window.EVENT_TYPE_CLICK: - if self.contains(ex, ey): - if not self.focused: - self.focused = 1 - tk: Tk* = self.tk - tk.focused_widget = Widget*(self) - prompt_row: int = self.line_count - self.scroll_offset - prompt_y: int = self.y + prompt_row * CHAR_H - if prompt_row >= 0 and prompt_row < self._visible_lines() and ey >= prompt_y and ey < prompt_y + CHAR_H: - plen: int = int(string.strlen(&self.prompt[0])) - rel_x: int = ex - self.x - 4 - plen * CHAR_W - if rel_x < 0: rel_x = 0 - new_pos: int = (rel_x + CHAR_W / 2) / CHAR_W - if new_pos > self.input_len: new_pos = self.input_len - self.cursor_pos = new_pos - return 1 - else: - if self.focused: - self.focused = 0 - tk2: Tk* = self.tk - tk2.focused_widget = None - return 1 - elif etype == window.EVENT_TYPE_WHEEL: - if self.contains(ex, ey): - w: int = int(evt.button) - if w >= 128: w = w - 256 - vis: int = self._visible_lines() - self.scroll_offset = self.scroll_offset - w * 3 - max_off: int = self.line_count + 1 - vis - if max_off < 0: max_off = 0 - if self.scroll_offset < 0: self.scroll_offset = 0 - if self.scroll_offset > max_off: self.scroll_offset = max_off - self._update_scrollbar() - return 1 - return 0 - elif etype == window.EVENT_TYPE_KEY: - if self.focused: - ebtn2: uint8 = evt.button - if ebtn2 == window.KEY_STATE_RELEASE: - return 0 - ch: int = ex - if ch == 8: - if self.cursor_pos > 0: - i: int = self.cursor_pos - while i < self.input_len: - self.input_buf[i - 1] = self.input_buf[i] - i += 1 - self.input_len -= 1 - self.cursor_pos -= 1 - self.input_buf[self.input_len] = 0 - return 1 - elif ch == 13 or ch == 10: - self._exec_input() - self.input_len = 0 - self.cursor_pos = 0 - for i in range(80): - self.input_buf[i] = 0 - return 1 - elif ch == window.VK_LEFT: - if self.cursor_pos > 0: - self.cursor_pos -= 1 - return 1 - elif ch == window.VK_RIGHT: - if self.cursor_pos < self.input_len: - self.cursor_pos += 1 - return 1 - elif ch == window.VK_UP: - if self.history_count > 0: - if self.history_idx < self.history_count: - self.history_idx += 1 - hi: int = self.history_count - self.history_idx - if hi < 0: hi = 0 - for j in range(80): - self.input_buf[j] = self.history[hi][j] - self.input_len = 0 - while self.input_len < 79 and self.input_buf[self.input_len] != 0: - self.input_len += 1 - self.cursor_pos = self.input_len - return 1 - elif ch == window.VK_DOWN: - if self.history_count > 0: - if self.history_idx > 0: - self.history_idx -= 1 - if self.history_idx == 0: - self.input_len = 0 - self.cursor_pos = 0 - for j2 in range(80): - self.input_buf[j2] = 0 - else: - hi2: int = self.history_count - self.history_idx - for j3 in range(80): - self.input_buf[j3] = self.history[hi2][j3] - self.input_len = 0 - while self.input_len < 79 and self.input_buf[self.input_len] != 0: - self.input_len += 1 - self.cursor_pos = self.input_len - return 1 - elif ch == window.VK_HOME: - self.cursor_pos = 0 - return 1 - elif ch == window.VK_END: - self.cursor_pos = self.input_len - return 1 - elif ch == window.VK_DELETE: - if self.cursor_pos < self.input_len: - i: int = self.cursor_pos - while i < self.input_len - 1: - self.input_buf[i] = self.input_buf[i + 1] - i += 1 - self.input_len -= 1 - self.input_buf[self.input_len] = 0 - return 1 - elif ch == window.VK_PRIOR: - if self.scroll_offset > 0: - vis: int = self._visible_lines() - self.scroll_offset -= vis - if self.scroll_offset < 0: self.scroll_offset = 0 - self._update_scrollbar() - return 1 - elif ch == window.VK_NEXT: - vis2: int = self._visible_lines() - max_off: int = self.line_count + 1 - vis2 - if max_off < 0: max_off = 0 - if self.scroll_offset < max_off: - self.scroll_offset += vis2 - if self.scroll_offset > max_off: self.scroll_offset = max_off - self._update_scrollbar() - return 1 - elif ch >= 32 and ch < 127: - if self.input_len < 79: - i: int = self.input_len - while i > self.cursor_pos: - self.input_buf[i] = self.input_buf[i - 1] - i -= 1 - self.input_buf[self.cursor_pos] = char(ch) - self.input_len += 1 - self.cursor_pos += 1 - self.input_buf[self.input_len] = 0 - return 1 - return 0 - - def _exec_input(self): - self.insert(&self.prompt[0]) - plen: int = int(string.strlen(&self.prompt[0])) - if self.input_len > 0: - last: int = self.line_count - 1 - j: int = 0 - while j < self.input_len and plen + j < 79: - self.lines[last][plen + j] = self.input_buf[j] - j += 1 - self.lines[last][plen + j] = 0 - if self.history_count < 16: - for k in range(80): - self.history[self.history_count][k] = self.input_buf[k] - self.history_count += 1 - else: - for i in range(15): - for k2 in range(80): - self.history[i][k2] = self.history[i + 1][k2] - for k3 in range(80): - self.history[15][k3] = self.input_buf[k3] - self.history_idx = 0 - - cmd: rawptr = self.on_command - before_lines: int = self.line_count - if cmd is not None: - # 同 Button.handle_event:on_command 是闭包 {fn, env},必须 rdi = env 后 call fn。 - __asm__(f"""push r12 -push r13 -mov r13, {asminp(cmd, ASM_DESCR.REG_ANY)} -mov rdi, qword ptr [r13 + 8] -mov r12, rsp -and rsp, -16 -call qword ptr [r13] -mov rsp, r12 -pop r13 -pop r12""", - op=[ASM_DESCR.CLOBBER_MEMORY, - ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, - ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, - ASM_DESCR.CLOBBER_RDI, - ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, - ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) - else: - self.insert("(no callback)") - - # 本命令确实产生了输出时,在日志与下一个输入行之间留一个空行。 - # 这样提示符不会紧贴最后一行日志(用实际写入的行数判断,clear 不会被补空行)。 - if self.line_count > before_lines: - self.insert("\n") - - def insert(self, text: str): - start: int = 0 - pos: int = 0 - while True: - ch: char = *(text + pos) - if ch == 0: - if pos > start: - self._insert_substring(text, start, pos) - break - if ch == 10: - self._insert_substring(text, start, pos) - start = pos + 1 - pos += 1 - - def insert_ptr(self, text: const str): - start: int = 0 - pos: int = 0 - while True: - ch: char = *(text + pos) - if ch == 0: - if pos > start: - self._insert_substring(text, start, pos) - break - if ch == 10: - self._insert_substring(text, start, pos) - start = pos + 1 - pos += 1 - - def _insert_substring(self, text: const str, start: int, end: int): - if self.line_count >= self.max_lines: - for i in range(self.max_lines - 1): - for j in range(79 + 1): - self.lines[i][j] = self.lines[i + 1][j] - self.line_count -= 1 - if self.scroll_offset > 0: - self.scroll_offset -= 1 - idx: int = self.line_count - j: int = 0 - k: int = start - while k < end and j < 79: - ch: char = *(text + k) - if ch == 9: - tab_stop: int = 8 - (j % 8) - while tab_stop > 0 and j < 79: - self.lines[idx][j] = 32 - j += 1 - tab_stop -= 1 - elif ch == 13: - pass - else: - self.lines[idx][j] = ch - j += 1 - k += 1 - self.lines[idx][j] = 0 - self.line_count += 1 - vis: int = self._visible_lines() - total: int = self.line_count + 1 # 输出行 + 输入行 - if total > vis: - self.scroll_offset = total - vis - else: - self.scroll_offset = 0 - self._update_scrollbar() - - def delete(self, start: int, end: int): - self.line_count = 0 - self.scroll_offset = 0 - self._update_scrollbar() - - def get(self) -> const str: - return &self.input_buf[0] - - def set_prompt(self, text: const str): - i: int = 0 - while i < 79: - ch: char = *(text + i) - self.prompt[i] = ch - if ch == 0: break - i += 1 - self.prompt[i] = 0 - - def config(self, text_color: uint32 = None, bg_color: uint32 = None, prompt_color: uint32 = None, cursor_color: uint32 = None, border_color: uint32 = None): - if text_color is not None: - self.text_color = text_color - if bg_color is not None: - self.bg_color = bg_color - if prompt_color is not None: - self.prompt_color = prompt_color - if cursor_color is not None: - self.cursor_color = cursor_color - if border_color is not None: - self.border_color = border_color - - def _visible_lines(self) -> int: - return (self.h - 2) / CHAR_H - - def _update_scrollbar(self): - vis: int = self._visible_lines() - total: int = self.line_count + 1 # 输出行 + 输入行 - if total <= vis: - self.scroll_offset = 0 - else: - max_off: int = total - vis - if self.scroll_offset > max_off: - self.scroll_offset = max_off - if self.yscrollbar is not None: - sb: Scrollbar* = Scrollbar*(self.yscrollbar) - if total <= vis: - sb.set(0, 1000) - else: - first: int = self.scroll_offset * 1000 / total - last: int = (self.scroll_offset + vis) * 1000 / total - sb.set(first, last) - -# ---- Tk:主窗口类 = Wm(窗口管理器协议,无字段)+ Misc(公共状态/方法)---- -virtual class Tk(Wm, Misc): - win_id: int - buf: uint32* - bw: int - bh: int - style: int - bg_color: uint32 - widgets: [MAX_WIDGETS]Widget* - widget_count: int - focused_widget: Widget* - hovered_widget: Widget* - _win_x: int - _win_y: int - _title: [32]char - resizable_w: int - resizable_h: int - _grid_rows: int - _grid_cols: int - _bb: uint32* # 后备缓冲:整帧画完再一次性拷进共享 arena,避免合成器读到半成品帧 - _bb_px: int # 后备缓冲容量(像素数),窗口变大时才重新分配 - _ansi_state: int # ESC 序列解析状态:0=普通 1=已收 ESC 2=已收 ESC[ - _ansi_param: int # CSI 数值参数(如 ESC[5~ 的 5) - - def __init__(self): - global _g_layout_init - # Misc 的公共状态:主窗口没有父容器,tk 指向自身(控件经 _misc_setup 继承它) - self.master = None - self.tk = Tk*(self) - self.widget_count = 0 - self.focused_widget = None - self.hovered_widget = None - self.win_id = -1 - self._win_x = 0 - self._win_y = 0 - self.style = 0 - self.bg_color = uint32(0xFF231919) - self.resizable_w = 1 - self.resizable_h = 1 - self._grid_rows = 0 - self._grid_cols = 0 - if _g_layout_init == 0: - for i in range(MAX_WIDGETS): - _g_wl_type[i] = 0 - _g_wl_side[i] = 0 - _g_wl_fill[i] = 0 - _g_wl_expand[i] = 0 - _g_wl_padx[i] = 0 - _g_wl_pady[i] = 0 - _g_wl_row[i] = 0 - _g_wl_col[i] = 0 - _g_wl_rowspan[i] = 0 - _g_wl_colspan[i] = 1 - _g_wl_sticky[i] = 0 - _g_layout_init = 1 - self._bb = None - self._bb_px = 0 - self._ansi_state = 0 - self._ansi_param = 0 - for j in range(32): - self._title[j] = 0 - - def pack(self, idx: int, side: int, fill: int, expand: int, padx: int, pady: int): - _g_wl_type[idx] = 2 - _g_wl_side[idx] = side - _g_wl_fill[idx] = fill - _g_wl_expand[idx] = expand - _g_wl_padx[idx] = padx - _g_wl_pady[idx] = pady - - def grid(self, idx: int, row: int, col: int, rowspan: int, colspan: int, sticky: int): - _g_wl_type[idx] = 3 - _g_wl_row[idx] = row - _g_wl_col[idx] = col - _g_wl_rowspan[idx] = rowspan - _g_wl_colspan[idx] = colspan - _g_wl_sticky[idx] = sticky - - # ---- Wm 协议的落地实现(Tk 持有窗口状态,覆写 Wm 声明的协议面)---- - def title(self, text: str): - i: int = 0 - while i < 31: - ch: char = text[i] - if ch == 0: break - self._title[i] = ch - i += 1 - self._title[i] = 0 - if self.win_id >= 0: - window.set_title(self.win_id, &self._title[0]) - - def set_style(self, s: int): - self.style = s - - def resizable(self, rw: int, rh: int): - self.resizable_w = rw - self.resizable_h = rh - if self.win_id >= 0: - window.set_resizable(self.win_id, rw, rh) - - def geometry(self, x: int, y: int, w: int, h: int): - self._win_x = x - self._win_y = y - self.bw = w - self.bh = h - TITLE_H - 2 - if self.win_id < 0: - self.win_id = window.create_window(x, y, w, h, &self._title[0], self.style) - self.buf = window.get_winbuf(self.win_id) - window.set_resizable(self.win_id, self.resizable_w, self.resizable_h) - else: - window.set_geometry(self.win_id, x, y, w, h) - self.buf = window.get_winbuf(self.win_id) - - def setup(self, x: int, y: int, w: int, h: int, title: const str, style: int): - self._win_x = x - self._win_y = y - self.win_id = window.create_window(x, y, w, h, title, style) - self.buf = window.get_winbuf(self.win_id) - self.bw = w - self.bh = h - TITLE_H - 2 - self.style = style - self.bg_color = uint32(0xFF231919) - - def redraw(self): - global _text_qn - # 窗口槽已回收(用户点了关闭):立即返回,不再触碰共享 arena / flush。 - if self.win_id < 0: return - if window.is_active(self.win_id) == 0: return - self.buf = window.get_winbuf(self.win_id) - sz: int = window.get_win_size(self.win_id) - if sz != 0: - self.bw = sz >> 16 - self.bh = sz & 0xFFFF - if not self.buf: return - if self.bw <= 0 or self.bh <= 0: return - # 先刷新尺寸再布局:窗口 resize 后若沿用上一帧的 bw/bh,布局会滞后一帧。 - self._do_layout() - _text_qn = 0 - # 双缓冲:整帧先画在**进程本地**的后备缓冲里,画完一次性拷进共享 arena,再 flush。 - # 为什么必须这么做:arena 正是 explorer 合成时读的那块内存(render_window_rows - # 直接从它逐行 blit 上屏)。若直接往 arena 上画,合成器会在"整窗已填成背景色、 - # 控件还没画"的中途采样 ⇒ 窗口闪烁;光标移动让 explorer 重画窗口所在行时同样撞上。 - px: int = self.bw * self.bh - if self._bb_px < px: - if self._bb: stdlib.free(rawptr(self._bb)) - self._bb = uint32*(stdlib.malloc(uint64(px * 4))) - self._bb_px = px - tgt: uint32* = self._bb - if not tgt: - tgt = self.buf - _fill_rect(tgt, self.bw, self.bh, 0, 0, self.bw, self.bh, self.bg_color) - for i in range(self.widget_count): - w: Widget* = self.widgets[i] - if w.visible: - w.draw(tgt, self.bw, self.bh) - if tgt != self.buf: - string.memcpy(self.buf, tgt, uint64(px * 4)) - _flush_text(self.win_id) - window.flush(self.win_id) - - def _do_layout(self): - self._grid_rows = 0 - self._grid_cols = 0 - cav_x: int = 0 - cav_y: int = 0 - cav_w: int = self.bw - cav_h: int = self.bh - for i in range(self.widget_count): - w: Widget* = self.widgets[i] - lt: int = _g_wl_type[i] - if lt == 2: - side: int = _g_wl_side[i] - fill: int = _g_wl_fill[i] - expand: int = _g_wl_expand[i] - px: int = _g_wl_padx[i] - py: int = _g_wl_pady[i] - ww: int = w.w - wh: int = w.h - # 交叉轴上未 fill 时按 Tk 默认 anchor=center 居中;沿 pack 方向仍贴腔体边缘 + - # padding(与 tkinter 一致)。此前一律贴左上,导致不 fill 的控件全部挤在左边缘。 - if side == PACK_TOP: - if fill == FILL_X or fill == FILL_BOTH: - ww = cav_w - 2 * px - w.x = cav_x + px - else: - w.x = cav_x + px + (cav_w - 2 * px - ww) / 2 - if expand: - if fill == FILL_Y or fill == FILL_BOTH: - wh = cav_h - 2 * py - w.y = cav_y + py - w.w = ww - w.h = wh - cav_y += wh + 2 * py - cav_h -= wh + 2 * py - elif side == PACK_BOTTOM: - if fill == FILL_X or fill == FILL_BOTH: - ww = cav_w - 2 * px - w.x = cav_x + px - else: - w.x = cav_x + px + (cav_w - 2 * px - ww) / 2 - if expand: - if fill == FILL_Y or fill == FILL_BOTH: - wh = cav_h - 2 * py - w.y = cav_y + cav_h - wh - py - w.w = ww - w.h = wh - cav_h -= wh + 2 * py - elif side == PACK_LEFT: - if fill == FILL_Y or fill == FILL_BOTH: - wh = cav_h - 2 * py - w.y = cav_y + py - else: - w.y = cav_y + py + (cav_h - 2 * py - wh) / 2 - if expand: - if fill == FILL_X or fill == FILL_BOTH: - ww = cav_w - 2 * px - w.x = cav_x + px - w.w = ww - w.h = wh - cav_x += ww + 2 * px - cav_w -= ww + 2 * px - elif side == PACK_RIGHT: - if fill == FILL_Y or fill == FILL_BOTH: - wh = cav_h - 2 * py - w.y = cav_y + py - else: - w.y = cav_y + py + (cav_h - 2 * py - wh) / 2 - if expand: - if fill == FILL_X or fill == FILL_BOTH: - ww = cav_w - 2 * px - w.x = cav_x + cav_w - ww - px - w.w = ww - w.h = wh - cav_w -= ww + 2 * px - _container_offset(self, w) - elif lt == 3: - r: int = _g_wl_row[i] + _g_wl_rowspan[i] - c: int = _g_wl_col[i] + _g_wl_colspan[i] - if r > self._grid_rows: self._grid_rows = r - if c > self._grid_cols: self._grid_cols = c - for i in range(self.widget_count): - w: Widget* = self.widgets[i] - if _g_wl_type[i] == 3: - if self._grid_rows <= 0: self._grid_rows = 1 - if self._grid_cols <= 0: self._grid_cols = 1 - cell_w: int = self.bw / self._grid_cols - cell_h: int = self.bh / self._grid_rows - wx: int = _g_wl_col[i] * cell_w - wy: int = _g_wl_row[i] * cell_h - ww: int = _g_wl_colspan[i] * cell_w - wh: int = _g_wl_rowspan[i] * cell_h - sticky: int = _g_wl_sticky[i] - if sticky == 0: - ox: int = (ww - w.w) / 2 - oy: int = (wh - w.h) / 2 - w.x = wx + ox - w.y = wy + oy - else: - w.x = wx - w.y = wy - if (sticky & 1) != 0: - w.w = ww - if (sticky & 2) != 0: - w.h = wh - _container_offset(self, w) - - def update(self): - self.redraw() - - def winfo_exists(self) -> int: - if self.win_id < 0: return 0 - return window.is_active(self.win_id) - - # ---- Misc 查询的覆写:主窗口用自己的窗口状态回答 winfo_* ---- - def winfo_ismapped(self) -> int: - if self.win_id < 0: return 0 - return window.is_active(self.win_id) - - def winfo_x(self) -> int: - return self._win_x - - def winfo_y(self) -> int: - return self._win_y - - def winfo_width(self) -> int: - return self.bw - - def winfo_height(self) -> int: - return self.bh - - # 无显式焦点控件时,按注册顺序取第一个可见文本控件(Text/Entry) - def _first_text_widget(self) -> Widget*: - for i in range(self.widget_count): - w: Widget* = self.widgets[i] - if w.visible != 0: - if w.widget_type == WIDGET_TEXT or w.widget_type == WIDGET_ENTRY: - return w - return None - - # 逐字节投递的按键流 -> ANSI CSI 状态机;返回 0=本字节被吞掉,1=可分发 - def _fold_ansi_key(self, evt: window.InputEvent*) -> int: - ch: int = evt.x - if self._ansi_state == 0: - if ch == 27: - self._ansi_state = 1 - self._ansi_param = 0 - return 0 - return 1 - if self._ansi_state == 1: - if ch == 91: - self._ansi_state = 2 - return 0 - # 孤立 ESC:只丢弃 ESC,当前字节照常分发 —— 否则会连带吞掉下一个键入字符。 - self._ansi_state = 0 - return 1 - if ch >= 48 and ch <= 57: - self._ansi_param = self._ansi_param * 10 + (ch - 48) - return 0 - self._ansi_state = 0 - vk: int = 0 - if ch == 65: vk = window.VK_UP - elif ch == 66: vk = window.VK_DOWN - elif ch == 67: vk = window.VK_RIGHT - elif ch == 68: vk = window.VK_LEFT - elif ch == 72: vk = window.VK_HOME - elif ch == 70: vk = window.VK_END - elif ch == 126: - if self._ansi_param == 3: vk = window.VK_DELETE - elif self._ansi_param == 5: vk = window.VK_PRIOR - elif self._ansi_param == 6: vk = window.VK_NEXT - if vk == 0: return 0 - evt.x = vk - return 1 - - # 指针下的最上层控件决定光标形状:Entry/Text → I 形,Button → 手指。 - # 与控件自己的 hover 视觉解耦(后者仍由 dispatch_event 维护)。 - def _update_cursor_for_hover(self, ex: int, ey: int): - hw: Widget* = None - i: int = self.widget_count - 1 - while i >= 0: - w: Widget* = self.widgets[i] - if w.visible and w.contains(ex, ey): - hw = w - break - i -= 1 - if hw == self.hovered_widget: return - self.hovered_widget = hw - ct: int = window.CURSOR_DEFAULT - if hw is not None: - if hw.widget_type == WIDGET_ENTRY or hw.widget_type == WIDGET_TEXT: - ct = window.CURSOR_IBEAM - elif hw.widget_type == WIDGET_BUTTON: - ct = window.CURSOR_HAND - window.set_cursor_type(self.win_id, ct) - - def mainloop(self): - self.redraw() - evt: window.InputEvent - while window.is_active(self.win_id): - result: int = window.poll_event(&evt) - if result != 0: - if evt.win_id == self.win_id: - need_redraw: int = 0 - etype: uint32 = evt.type - if etype == window.EVENT_TYPE_PING: - window.flush(self.win_id) - elif etype == window.EVENT_TYPE_RESIZE: - self.buf = window.get_winbuf(self.win_id) - sz: int = window.get_win_size(self.win_id) - self.bw = sz >> 16 - self.bh = sz & 0xFFFF - need_redraw = 1 - elif etype == window.EVENT_TYPE_KEY or etype == window.EVENT_TYPE_KEY_RELEASE: - folded: int = 1 - if etype == window.EVENT_TYPE_KEY: - folded = self._fold_ansi_key(&evt) - if folded != 0: - fw: Widget* = self.focused_widget - if fw is None: - fw = self._first_text_widget() - if fw is not None: - fw.focused = 1 - self.focused_widget = fw - if fw is not None: - handled: int = fw.dispatch_event(&evt) - if handled: need_redraw = 1 - else: - if etype == window.EVENT_TYPE_HOVER: - self._update_cursor_for_hover(evt.x, evt.y) - i: int = self.widget_count - 1 - while i >= 0: - w: Widget* = self.widgets[i] - if w.visible: - handled2: int = w.dispatch_event(&evt) - if handled2: - need_redraw = 1 - break - i -= 1 - else: - for i in range(self.widget_count): - w: Widget* = self.widgets[i] - if w.visible: - handled2: int = w.dispatch_event(&evt) - if handled2: need_redraw = 1 - if need_redraw: - self.redraw() - else: - process.yield_cpu() +import vpsdk.window as window +import vpsdk.process as process +import vpsdk.devfs as devfs +import vpsdk.stdlib as stdlib +import vpsdk.fonttools.fonttools as fonttools +import string + +define TITLE_H = 32 +define MAX_WIDGETS = 24 +define CHAR_W = 8 +define CHAR_H = 16 + +define TEXT_MODE_DIRECT = 0 +define TEXT_MODE_MANAGED = 1 +define TEXT_QUEUE_MAX = 256 +define TEXT_CHUNK = 28 + +define WIDGET_LABEL = 1 +define WIDGET_BUTTON = 2 +define WIDGET_ENTRY = 3 +define WIDGET_TEXT = 4 +define WIDGET_SCROLLBAR = 5 +define WIDGET_FBCANVAS = 6 +define WIDGET_FRAME = 7 + +define VERTICAL = 1 +define HORIZONTAL = 2 +define TEXT_MAX_LINES = 200 + +define PACK_TOP = 1 +define PACK_BOTTOM = 2 +define PACK_LEFT = 3 +define PACK_RIGHT = 4 + +define FILL_NONE = 0 +define FILL_X = 1 +define FILL_Y = 2 +define FILL_BOTH = 3 + +define STICKY_NONE = 0 +define STICKY_W = 1 +define STICKY_E = 2 +define STICKY_N = 4 +define STICKY_S = 8 +define STICKY_NSEW = 15 + +_g_wl_type: [MAX_WIDGETS]int +_g_wl_side: [MAX_WIDGETS]int +_g_wl_fill: [MAX_WIDGETS]int +_g_wl_expand: [MAX_WIDGETS]int +_g_wl_padx: [MAX_WIDGETS]int +_g_wl_pady: [MAX_WIDGETS]int +_g_wl_row: [MAX_WIDGETS]int +_g_wl_col: [MAX_WIDGETS]int +_g_wl_rowspan: [MAX_WIDGETS]int +_g_wl_colspan: [MAX_WIDGETS]int +_g_wl_sticky: [MAX_WIDGETS]int +_g_layout_init: int + +_hex_tbl: [17]char = "0123456789ABCDEF" +_hex_buf: [20]char = "0000000000000000000" + +def _serial_puts(s: const str): + # S4:SERIAL_PUTS 私有 syscall 已删除,改走 /dev/serial0 设备 + devfs.serial_puts(s) + +def _ptr_to_hex(val: uint64, buf: rawptr): + buf[0] = 48 + buf[1] = 120 + i: int = 15 + while i >= 0: + nibble: int = int(val & uint64(0xF)) + buf[2 + i] = _hex_tbl[nibble] + val = val >> 4 + i -= 1 + buf[18] = 0 + +def _fill_rect(fb: uint32*, fw: int, fh: int, x1: int, y1: int, w: int, h: int, color: uint32): + if not fb: return + if x1 < 0: w += x1; x1 = 0 + if y1 < 0: h += y1; y1 = 0 + if x1 + w > fw: w = fw - x1 + if y1 + h > fh: h = fh - y1 + if w <= 0 or h <= 0: return + # 逐行批量填充(string.memset32 → rep stosd):整窗填充从"每个像素一次带边界判断的写" + # 降成"每行一次 rep stosd"。app 每帧都要把整窗填一遍背景色,逐像素在 QEMU 下是主要热点。 + py: int = y1 + while py < y1 + h: + string.memset32(rawptr(uint64(fb) + uint64((py * fw + x1) * 4)), color, uint64(w)) + py += 1 + +def _draw_hline(fb: uint32*, fw: int, fh: int, x1: int, y: int, w: int, color: uint32): + if not fb: return + if y < 0 or y >= fh: return + for px in range(x1, x1 + w): + if px >= 0 and px < fw: + fb[y * fw + px] = color + +def _draw_vline(fb: uint32*, fw: int, fh: int, x: int, y1: int, h: int, color: uint32): + if not fb: return + if x < 0 or x >= fw: return + for py in range(y1, y1 + h): + if py >= 0 and py < fh: + fb[py * fw + x] = color + +def _lighten(color: uint32, amount: int) -> uint32: + r: int = int(color & 0xFF) + g: int = int((color >> 8) & 0xFF) + b: int = int((color >> 16) & 0xFF) + r = r + ((255 - r) * amount) / 256 + g = g + ((255 - g) * amount) / 256 + b = b + ((255 - b) * amount) / 256 + return uint32(r | (g << 8) | (b << 16)) + +def _darken(color: uint32, amount: int) -> uint32: + r: int = int(color & 0xFF) + g: int = int((color >> 8) & 0xFF) + b: int = int((color >> 16) & 0xFF) + r = r - (r * amount) / 256 + g = g - (g * amount) / 256 + b = b - (b * amount) / 256 + return uint32(r | (g << 8) | (b << 16)) + + +font: [256][16]uint8 = [ + [0x00,0x00,0x00,0x00,0x00,0x40,0x55,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x00,0x00,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x00,0x02,0x56,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x00,0x42,0x56,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x55,0x55,0x57,0x71,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x57,0x54,0x57,0x71,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x57,0x54,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x57,0x51,0x52,0x72,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x57,0x55,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x57,0x55,0x57,0x71,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x56,0x51,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x54,0x54,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x50,0x50,0x57,0x74,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x51,0x51,0x57,0x75,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x57,0x55,0x57,0x74,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x53,0x54,0x56,0x74,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x37,0x15,0x15,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x32,0x16,0x12,0x12,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x20,0x6E,0x22,0x2E,0x28,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x37,0x11,0x13,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x35,0x15,0x17,0x11,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x37,0x14,0x17,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x37,0x14,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x37,0x11,0x12,0x12,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x37,0x15,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x37,0x15,0x17,0x11,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x36,0x11,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x34,0x14,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x30,0x10,0x17,0x14,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x31,0x11,0x17,0x15,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x37,0x15,0x17,0x14,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x10,0x33,0x14,0x16,0x14,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00], + [0x6C,0x6C,0x6C,0x6C,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x36,0x36,0x36,0x7F,0x7F,0x36,0x36,0x36,0x7F,0x7F,0x36,0x36,0x36,0x00,0x00,0x00], + [0x0C,0x0C,0x3E,0x7F,0x68,0x68,0x3E,0x0B,0x0B,0x7F,0x3E,0x18,0x18,0x00,0x00,0x00], + [0x60,0x60,0x66,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x66,0x06,0x06,0x00,0x00,0x00], + [0x38,0x6C,0x6C,0x6C,0x6C,0x6C,0x38,0x6C,0x6D,0x66,0x66,0x6F,0x3B,0x00,0x00,0x00], + [0x18,0x18,0x18,0x18,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x1C,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x1C,0x00,0x00], + [0x38,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x38,0x00,0x00], + [0x00,0x00,0x18,0x18,0x7E,0x3C,0x3C,0x3C,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x00,0x18,0x18,0x18,0x7E,0x7E,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x30,0x00], + [0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00], + [0x00,0x00,0x06,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x60,0x00,0x00,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x66,0x6E,0x6E,0x7E,0x76,0x76,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x18,0x38,0x78,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x06,0x06,0x0E,0x1C,0x38,0x70,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x06,0x06,0x1C,0x1C,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x0C,0x0C,0x1C,0x1C,0x3C,0x2C,0x6C,0x7E,0x7E,0x0C,0x0C,0x0C,0x0C,0x00,0x00,0x00], + [0x7E,0x7E,0x60,0x60,0x7C,0x7E,0x06,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x7E,0x7E,0x06,0x06,0x0C,0x0C,0x18,0x18,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x66,0x66,0x7E,0x3C,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x30,0x00], + [0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0x60,0x30,0x18,0x0C,0x06,0x02,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x7E,0x7E,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x40,0x60,0x30,0x18,0x0C,0x06,0x06,0x0C,0x18,0x30,0x60,0x40,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x06,0x0C,0x0C,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x66,0x6E,0x6A,0x6A,0x6A,0x6E,0x60,0x60,0x7C,0x3C,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x66,0x66,0x7E,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], + [0x7C,0x7E,0x66,0x66,0x66,0x7C,0x7C,0x66,0x66,0x66,0x66,0x7E,0x7C,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x78,0x7C,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0x7C,0x78,0x00,0x00,0x00], + [0x7E,0x7E,0x60,0x60,0x60,0x7C,0x7C,0x60,0x60,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00], + [0x7E,0x7E,0x60,0x60,0x60,0x7C,0x7C,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x60,0x60,0x60,0x6E,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x66,0x66,0x66,0x66,0x66,0x7E,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], + [0x7E,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00], + [0x3E,0x3E,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x6C,0x7C,0x38,0x00,0x00,0x00], + [0x66,0x66,0x66,0x6C,0x6C,0x78,0x78,0x78,0x6C,0x6C,0x66,0x66,0x66,0x00,0x00,0x00], + [0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00], + [0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00], + [0x66,0x66,0x66,0x66,0x76,0x76,0x7E,0x6E,0x6E,0x66,0x66,0x66,0x66,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x6A,0x6A,0x6C,0x7E,0x36,0x00,0x00,0x00], + [0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x6C,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], + [0x3C,0x7E,0x66,0x60,0x60,0x78,0x3C,0x0E,0x06,0x06,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x7E,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00], + [0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x18,0x00,0x00,0x00], + [0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x77,0x77,0x22,0x00,0x00,0x00], + [0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x3C,0x3C,0x66,0x66,0x66,0x66,0x00,0x00,0x00], + [0x66,0x66,0x66,0x66,0x7E,0x3C,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00], + [0x7E,0x7E,0x06,0x06,0x0C,0x0C,0x18,0x30,0x30,0x60,0x60,0x7E,0x7E,0x00,0x00,0x00], + [0x78,0x78,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x78,0x78,0x00,0x00], + [0xC0,0xC0,0x60,0x60,0x30,0x30,0x18,0x0C,0x0C,0x06,0x06,0x03,0x03,0x00,0x00,0x00], + [0x1E,0x1E,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x1E,0x1E,0x00,0x00], + [0x3C,0x7E,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00], + [0x30,0x38,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x3C,0x3E,0x06,0x3E,0x7E,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00], + [0x60,0x60,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x7C,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x60,0x60,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x06,0x06,0x06,0x06,0x3E,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x7E,0x7E,0x60,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x1C,0x3E,0x30,0x30,0x30,0x7C,0x7C,0x30,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x3E,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x3E,0x3C,0x00], + [0x60,0x60,0x60,0x60,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], + [0x18,0x18,0x00,0x00,0x38,0x38,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00], + [0x0C,0x0C,0x00,0x00,0x3C,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x7C,0x78,0x00], + [0x60,0x60,0x60,0x60,0x66,0x66,0x6C,0x78,0x78,0x6C,0x6C,0x66,0x66,0x00,0x00,0x00], + [0x38,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x36,0x7F,0x6B,0x6B,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x7C,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x3C,0x7E,0x66,0x66,0x66,0x66,0x66,0x7E,0x3C,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x7C,0x7E,0x66,0x66,0x66,0x7E,0x7C,0x60,0x60,0x60,0x60,0x00], + [0x00,0x00,0x00,0x00,0x3E,0x7E,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x07,0x07,0x00], + [0x00,0x00,0x00,0x00,0x6C,0x7E,0x76,0x60,0x60,0x60,0x60,0x60,0x60,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x3C,0x7E,0x62,0x70,0x3C,0x0E,0x46,0x7E,0x3C,0x00,0x00,0x00], + [0x30,0x30,0x30,0x30,0x7C,0x7C,0x30,0x30,0x30,0x30,0x30,0x3C,0x1C,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x3C,0x3C,0x18,0x18,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x63,0x63,0x63,0x63,0x6B,0x6B,0x7F,0x7F,0x36,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x66,0x66,0x3C,0x3C,0x18,0x3C,0x3C,0x66,0x66,0x00,0x00,0x00], + [0x00,0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x7E,0x3E,0x06,0x06,0x7E,0x7C,0x00], + [0x00,0x00,0x00,0x00,0x7E,0x7E,0x0C,0x0C,0x18,0x30,0x30,0x7E,0x7E,0x00,0x00,0x00], + [0x0E,0x1E,0x18,0x18,0x18,0x18,0x30,0x30,0x18,0x18,0x18,0x18,0x1E,0x0E,0x00,0x00], + [0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00], + [0x70,0x78,0x18,0x18,0x18,0x18,0x0C,0x0C,0x18,0x18,0x18,0x18,0x78,0x70,0x00,0x00], + [0x31,0x79,0x6B,0x4F,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + [0x00,0x00,0x70,0x13,0x24,0x26,0x24,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], +] + +class TextCmd: + x: int + y: int + color: uint32 + s: [32]char + +_text_mode: int = TEXT_MODE_MANAGED +_ttf_font: fonttools.Font* = None +_ttf_atlas: uint8* = None +_ttf_state: int = 0 +_text_queue: [TEXT_QUEUE_MAX]TextCmd +_text_qn: int = 0 + +def set_text_mode(mode: int): + global _text_mode + _text_mode = mode + +def _ensure_ttf(): + global _ttf_font, _ttf_atlas, _ttf_state + if _ttf_state != 0: return + f: fonttools.Font* = fonttools.load("/FONTS/FONT.TTF") + if f is None: + _ttf_state = -1 + return + a: uint8* = fonttools.build_ascii_atlas(f, CHAR_W, CHAR_H) + if a is None: + _ttf_state = -1 + return + _ttf_font = f + _ttf_atlas = a + _ttf_state = 1 + +def _queue_text(x: int, y: int, s: const str, color: uint32): + global _text_qn + if _text_mode != TEXT_MODE_MANAGED: return + cx: int = x + cy: int = y + i: int = 0 + while s[i] != 0: + if s[i] == 10: + cx = x + cy += CHAR_H + i += 1 + continue + if _text_qn >= TEXT_QUEUE_MAX: return + cmd: TextCmd* = &_text_queue[_text_qn] + cmd.x = cx + cmd.y = cy + cmd.color = color + n: int = 0 + while n < TEXT_CHUNK and s[i] != 0 and s[i] != 10: + cmd.s[n] = s[i] + n += 1 + i += 1 + cmd.s[n] = 0 + _text_qn += 1 + cx += n * CHAR_W + +def _flush_text(win_id: int): + global _text_qn + if _text_qn == 0: return + for i in range(_text_qn): + cmd: TextCmd* = &_text_queue[i] + window.text(win_id, cmd.x, cmd.y, cmd.color, &cmd.s[0]) + _text_qn = 0 + +def _draw_char(buf: uint32*, bw: int, bh: int, x: int, y: int, ch: char, color: uint32): + if not buf: return + if bw <= 0 or bh <= 0: return + ci: int = int(uint8(ch)) + if _text_mode == TEXT_MODE_DIRECT: + _ensure_ttf() + if _ttf_state == 1 and ci < 128: + fy: int = 0 + while fy < CHAR_H: + py: int = y + fy + if py >= 0 and py < bh: + fx: int = 0 + while fx < CHAR_W: + a: int = int(_ttf_atlas[(ci * CHAR_H + fy) * CHAR_W + fx]) + if a != 0: + px: int = x + fx + if px >= 0 and px < bw: + idx: int = py * bw + px + if a >= 255: + buf[idx] = color + else: + dst: uint32 = buf[idx] + dr: int = int(dst & 0xFF) + dg: int = int((dst >> 8) & 0xFF) + db: int = int((dst >> 16) & 0xFF) + sr: int = int(color & 0xFF) + sg: int = int((color >> 8) & 0xFF) + sb: int = int((color >> 16) & 0xFF) + r: int = (sr * a + dr * (255 - a)) / 255 + g: int = (sg * a + dg * (255 - a)) / 255 + b: int = (sb * a + db * (255 - a)) / 255 + buf[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) + fx += 1 + fy += 1 + return + if ci >= 128: ci = 0 + fy: int = 0 + while fy < CHAR_H: + if y + fy < 0 or y + fy >= bh: + fy += 1 + continue + row: uint8 = font[ci][fy] + if row == 0: + fy += 1 + continue + for fx in range(CHAR_W): + if row & (0x80 >> fx): + px: int = x + fx + if px >= 0 and px < bw: + buf[(y + fy) * bw + px] = color + fy += 1 + +def _draw_text(buf: uint32*, bw: int, bh: int, x: int, y: int, s: const str, color: uint32): + if not buf: return + if bw <= 0 or bh <= 0: return + if _text_mode == TEXT_MODE_MANAGED: + _queue_text(x, y, s, color) + return + cx: int = x + cy: int = y + i: int = 0 + while True: + ch: char = s[i] + if ch == 0: break + if ch == 10: + cy += CHAR_H + cx = x + else: + _draw_char(buf, bw, bh, cx, cy, ch, color) + cx += CHAR_W + i += 1 + +# ============================================================================ +# tkinter 风格的抽象基类层次(仿 tkinter 原始抽象) +# +# Misc(mixin:master/tk 公共状态 + winfo_master/toplevel、bind 等公共方法) +# ├─ Tk(Wm, Misc) 主窗口类(Wm 声明 WM 协议面,Tk 提供落地实现) +# └─ BaseWidget(Misc) +# └─ Widget(BaseWidget) 所有普通控件的抽象基类(Button/Label/Entry… 都继承 Widget) +# +# 字段模型(与 Test/MultiInheritTest 一致):父类字段被**展平**进子类,子类自己的方法可 +# 直接访问全部展平字段;字段偏移按"定义该方法的那一层"自己的展平布局计算(该测试里 +# ColoredCircle(ShapeM, ColorM) = (ptr, i32, f64):偏移 0 是唯一的共享 vptr,各基类字段依次 +# 跟在后面,虚表被合并)。由此有三条硬约束: +# ① Misc 的字段必须在所有派生类里落到同一偏移 —— 偏移 0 被共享 vptr 占用,Misc 的字段 +# 从 8 起,只要它前面没有"带字段的基类"就成立; +# ② 因此 `Wm`(Tk 的最左基类)必须**无字段**,否则会把 Misc 的字段整体后移,而 Misc 自己 +# 的方法仍按自身布局取偏移 ⇒ 静默错位; +# ③ 基类方法里**不要写 `self.<虚拟方法>()` 的自我分派**:多继承下非最左基类的方法在派生 +# 类的合并虚表里整体后移,而方法体内按"定义类自己的虚表"取索引 ⇒ 会指到别的槽位。 +# 所以 winfo_*/title 这类"需要看具体对象状态"的方法一律在 Misc / Wm 里只做声明,由 +# Tk / Widget 覆写实现;外部按具体类型调用即可正常走虚表。 +# ============================================================================ + +# ---- bind 回调注册表:按 (对象, 事件类型) 记录回调。 +# tkinter 用 这类字符串作为 sequence,这里改用 window.EVENT_TYPE_* 整型 +# (本运行时的事件类型就是这些整型,比较字符串在 VPC 模型下既慢又易错)。 +define MAX_BINDINGS = 64 + +_bind_obj: [MAX_BINDINGS]Misc* = [None] +_bind_seq: [MAX_BINDINGS]int = [0] +_bind_cb: [MAX_BINDINGS]rawptr = [None] + +def _bind_add(obj: Misc*, seq: int, func: rawptr) -> int: + i: int = 0 + while i < MAX_BINDINGS: + if _bind_obj[i] is None: + _bind_obj[i] = obj + _bind_seq[i] = seq + _bind_cb[i] = func + return i + i += 1 + return -1 + +def _bind_remove(obj: Misc*, seq: int) -> int: + i: int = 0 + while i < MAX_BINDINGS: + if _bind_obj[i] is obj and _bind_seq[i] == seq: + _bind_obj[i] = None + _bind_cb[i] = None + return 0 + i += 1 + return -1 + +def _bind_invoke(obj: Misc*, seq: int, evt: window.InputEvent*) -> int: + i: int = 0 + while i < MAX_BINDINGS: + if _bind_obj[i] is obj and _bind_seq[i] == seq: + cb: rawptr = _bind_cb[i] + if cb is None: return 0 + # 与 Button 回调同一约定:rawptr 是 VPC 生成的闭包对象 {fn, env}, + # fn 期望 rdi = env ⇒ 必须取 [cb+8] 传 rdi、再由 [cb] 调用。 + __asm__(f"""push r12 +push r13 +mov r13, {asminp(cb, ASM_DESCR.REG_ANY)} +mov rdi, qword ptr [r13 + 8] +mov r12, rsp +and rsp, -16 +call qword ptr [r13] +mov rsp, r12 +pop r13 +pop r12""", + op=[ASM_DESCR.CLOBBER_MEMORY, + ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, + ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, + ASM_DESCR.CLOBBER_RDI, + ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, + ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) + return 1 + i += 1 + return 0 + +# ---- Misc:Tk 与所有控件的公共 mixin ---- +# 只放"所有 Tk 对象都有的东西":公共状态(master/tk)、公共方法(winfo_master/toplevel、bind), +# 以及需要各具体类自己回答的查询(winfo_x/… 这里只声明,由 Tk / Widget 覆写)。 +# 约束:这里**不做 `self.<虚拟方法>()` 的自我分派**。多继承下非最左基类的方法在派生类的 +# 合并虚表里会整体后移,而方法体内按"定义类自己的虚表"取索引 ⇒ 会指到别的槽位。 +# 因此查询一律由具体类覆写、由外部按具体类型调用;Misc 自身只用自己声明的字段。 +virtual class Misc: + master: Misc* # 父容器;主窗口为 None + tk: Tk* # 所属顶层窗口;主窗口指向自身 + + # 公共状态初始化(各具体类的 __init__ 调用;不依赖基类 __init__ 链) + def _misc_setup(self, master: Misc* = None): + self.master = master + self.tk = None + if master is not None: + self.tk = master.tk + + # ---- winfo 系(tkinter Misc.winfo_* 的对应物)---- + def winfo_master(self) -> Misc*: + return self.master + + def winfo_toplevel(self) -> Tk*: + return self.tk + + # ---- bind 系(回调在 BaseWidget.dispatch_event 里派发)---- + def bind(self, seq: int, func: rawptr) -> int: + return _bind_add(Misc*(self), seq, func) + + def unbind(self, seq: int) -> int: + return _bind_remove(Misc*(self), seq) + + def _invoke_binding(self, seq: int, evt: window.InputEvent*) -> int: + return _bind_invoke(Misc*(self), seq, evt) + + # ---- 查询/生命周期:抽象,由 Tk / Widget 覆写 ---- + def winfo_exists(self) -> int: + return 0 + + def winfo_ismapped(self) -> int: + return 0 + + def winfo_x(self) -> int: + return 0 + + def winfo_y(self) -> int: + return 0 + + def winfo_width(self) -> int: + return 0 + + def winfo_height(self) -> int: + return 0 + + def destroy(self): + pass + + def update(self): + pass + +# ---- Wm:顶层窗口的"窗口管理器协议"(tkinter Wm 的对应物)---- +# 无字段(见文件头的布局约束),这里只**声明**协议面,落地实现由持有窗口状态的 Tk 覆写。 +# 同样不做自我分派(理由见 Misc 上方注释)。 +virtual class Wm: + def title(self, text: str): + pass + + def geometry(self, x: int, y: int, w: int, h: int): + pass + + def resizable(self, rw: int, rh: int): + pass + +# ---- BaseWidget:控件公共部分(tkinter BaseWidget 的对应物)---- +virtual class BaseWidget(Misc): + # 事件派发:先跑 bind 注册的回调,再交给控件自己的 handle_event。 + # 放在基类是为了让 Tk 的事件循环只调这一个入口,各控件无需关心 bind。 + def dispatch_event(self, evt: window.InputEvent*) -> int: + hit: int = self._invoke_binding(int(evt.type), evt) + handled: int = self.handle_event(evt) + if hit or handled: + return 1 + return 0 + + # 控件自身的事件处理(抽象,由具体控件覆写) + def handle_event(self, evt: window.InputEvent*) -> int: + return 0 + + # 抽象:具体控件在 Widget 层覆写(visible 是 Widget 的字段,不能在这里访问) + def destroy(self): + pass + +def _register_widget(master: Misc*, widget: Widget*) -> int: + tk: Tk* = master.tk + if tk is None: return -1 + idx: int = tk.widget_count + if idx < 0 or idx >= MAX_WIDGETS: + return -1 + tk.widgets[idx] = widget + tk.widget_count = idx + 1 + return idx + +virtual class Widget(BaseWidget): + x: int + y: int + w: int + h: int + visible: int + widget_type: int + focused: int + hovered: int + + def destroy(self): + self.visible = 0 + + # ---- Misc 查询的覆写:控件用自己的几何信息回答 winfo_* ---- + def winfo_exists(self) -> int: + return self.visible + + def winfo_ismapped(self) -> int: + return self.visible + + def winfo_x(self) -> int: + return self.x + + def winfo_y(self) -> int: + return self.y + + def winfo_width(self) -> int: + return self.w + + def winfo_height(self) -> int: + return self.h + + def draw(self, fb: uint32*, fw: int, fh: int): + pass + + def handle_event(self, evt: window.InputEvent*) -> int: + return 0 + + def contains(self, px: int, py: int) -> int: + if px >= self.x and px < self.x + self.w and py >= self.y and py < self.y + self.h: + return 1 + return 0 + + def place(self, x: int = 0, y: int = 0, w: int = 0, h: int = 0): + self.x = x + self.y = y + if w > 0: + self.w = w + if h > 0: + self.h = h + +# 容器中坐标叠加:master 不是顶层窗口(即 Frame 这类容器)时,子控件先按 Tk 的口径算位置, +# 再叠加容器自身原点 ⇒ 视觉上落在容器内部。本运行时的 pack/grid 是 Tk 级、按全局下标走, +# 所以这里只做"原点叠加"这一层,不做真正的嵌套布局。 +def _container_offset(root: Tk*, w: Widget*): + if w.master is None: return + if uint64(w.master) == uint64(root): return + pw: Widget* = Widget*(w.master) + if pw.widget_type == WIDGET_FRAME: + w.x += pw.x + w.y += pw.y + +# ---- Frame:容器控件(tkinter Frame 的对应物)---- +# 可作 master 使用:子控件经 _misc_setup 继承它的 tk,照常注册进顶层窗口;位置再叠加本 +# 容器原点(见 _container_offset)。 +virtual class Frame(Widget): + fill_color: uint32 + border_color: uint32 + border_w: int + + def __init__(self, master: Misc*, w: int, h: int): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self._misc_setup(master) + self.widget_type = WIDGET_FRAME + self.focused = 0 + self.hovered = 0 + self.fill_color = uint32(0xFF2A2A3A) + self.border_color = uint32(0xFF505070) + self.border_w = 1 + _register_widget(master, Widget*(self)) + + def draw(self, fb: uint32*, fw: int, fh: int): + _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.fill_color) + if self.border_w > 0: + _draw_hline(fb, fw, fh, self.x, self.y, self.w, self.border_color) + _draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, self.border_color) + _draw_vline(fb, fw, fh, self.x, self.y, self.h, self.border_color) + _draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, self.border_color) + + def handle_event(self, evt: window.InputEvent*) -> int: + return 0 + + def config(self, fill_color: uint32 = None, border_color: uint32 = None): + if fill_color is not None: + self.fill_color = fill_color + if border_color is not None: + self.border_color = border_color + +virtual class Label(Widget): + text: [64]char + color: uint32 + + def __init__(self, master: Misc*, text: str, color: uint32): + self.x = 0 + self.y = 0 + self.w = 0 + self.h = 0 + self.visible = 1 + self._misc_setup(master) + self.widget_type = WIDGET_LABEL + self.focused = 0 + self.hovered = 0 + self.color = color + i: int = 0 + for ch in text: + if i >= 63: break + self.text[i] = ch + i += 1 + self.text[i] = 0 + # Label 没有显式 w/h 参数:自然尺寸必须由文本推出。否则 pack 拿到的请求尺寸是 + # 0×0 ⇒ 不占布局空间,且 PACK_BOTTOM/非 fill 时会被贴到可视区域之外(文字被裁掉)。 + self._fit_natural() + _register_widget(master, Widget*(self)) + + # 按当前 text 计算自然尺寸:宽取最长行,高取行数(多行 Label 也成立)。 + def _fit_natural(self): + line_chars: int = 0 + max_chars: int = 0 + lines: int = 1 + i: int = 0 + while i < 64: + ch: char = self.text[i] + if ch == 0: break + if ch == 10: + if line_chars > max_chars: max_chars = line_chars + line_chars = 0 + lines += 1 + else: + line_chars += 1 + i += 1 + if line_chars > max_chars: max_chars = line_chars + self.w = max_chars * CHAR_W + self.h = lines * CHAR_H + + def draw(self, fb: uint32*, fw: int, fh: int): + _draw_text(fb, fw, fh, self.x, self.y, &self.text[0], self.color) + + def handle_event(self, evt: window.InputEvent*) -> int: + return 0 + + def config(self, text: const str = None, color: uint32 = None): + if text is not None: + i: int = 0 + while i < 63: + ch: char = *(text + i) + self.text[i] = ch + if ch == 0: break + i += 1 + self.text[i] = 0 + self._fit_natural() + if color is not None: + self.color = color + +virtual class Button(Widget): + text: [32]char + fill_color: uint32 + text_color: uint32 + pressed: int + callback: rawptr + + def __init__(self, master: Misc*, w: int, h: int, text: str, callback: rawptr): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self._misc_setup(master) + self.widget_type = WIDGET_BUTTON + self.focused = 0 + self.hovered = 0 + self.fill_color = uint32(0x3C3C5A) + self.text_color = uint32(0xFFFFFFFF) + self.pressed = 0 + self.callback = callback + i: int = 0 + for ch in text: + if i >= 31: break + self.text[i] = ch + i += 1 + self.text[i] = 0 + _register_widget(master, Widget*(self)) + + def draw(self, fb: uint32*, fw: int, fh: int): + fill: uint32 = self.fill_color + border: uint32 = uint32(0x8080B0) + if self.pressed: + fill = _lighten(self.fill_color, 60) + border = uint32(0xA0A0D0) + elif self.hovered: + fill = _lighten(self.fill_color, 30) + border = uint32(0xA0A0D0) + _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, fill) + top_left: uint32 = _lighten(fill, 80) + bottom_right: uint32 = _darken(fill, 80) + if self.pressed: + top_left = _darken(fill, 60) + bottom_right = _lighten(fill, 40) + _draw_hline(fb, fw, fh, self.x, self.y, self.w, top_left) + _draw_hline(fb, fw, fh, self.x, self.y + 1, self.w, top_left) + _draw_vline(fb, fw, fh, self.x, self.y, self.h, top_left) + _draw_vline(fb, fw, fh, self.x + 1, self.y, self.h, top_left) + _draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, bottom_right) + _draw_hline(fb, fw, fh, self.x, self.y + self.h - 2, self.w, bottom_right) + _draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, bottom_right) + _draw_vline(fb, fw, fh, self.x + self.w - 2, self.y, self.h, bottom_right) + _draw_hline(fb, fw, fh, self.x - 1, self.y - 1, self.w + 2, border) + _draw_hline(fb, fw, fh, self.x - 1, self.y + self.h, self.w + 2, border) + _draw_vline(fb, fw, fh, self.x - 1, self.y - 1, self.h + 2, border) + _draw_vline(fb, fw, fh, self.x + self.w, self.y - 1, self.h + 2, border) + tl: int = int(string.strlen(&self.text[0])) + tx: int = self.x + (self.w - 8 * tl) / 2 + ty: int = self.y + (self.h - CHAR_H) / 2 + _draw_text(fb, fw, fh, tx, ty, &self.text[0], self.text_color) + + def handle_event(self, evt: window.InputEvent*) -> int: + etype: uint32 = evt.type + ex: int = evt.x + ey: int = evt.y + if etype == window.EVENT_TYPE_HOVER: + if self.contains(ex, ey): + if not self.hovered: + self.hovered = 1 + return 1 + else: + if self.hovered and not self.pressed: + self.hovered = 0 + return 1 + elif etype == window.EVENT_TYPE_HOVER_LEAVE: + if self.hovered: + self.hovered = 0 + return 1 + elif etype == window.EVENT_TYPE_CLICK: + if self.contains(ex, ey): + self.pressed = 1 + return 1 + elif etype == window.EVENT_TYPE_RELEASE: + if self.pressed: + self.pressed = 0 + if self.contains(ex, ey) and self.callback is not None: + # 闭包调用约定(VPC 生成):callback 是堆上的 {fn, env} 对象,fn 期望 rdi = env。 + # 之前直接 `call r13`(r13 = 闭包对象地址)会跳到堆上执行 + # (现场:rip=0x2040400a 在堆区、cr2=0x1、err=0x5)。 + __asm__(f"""push r12 +push r13 +mov r13, {asminp(self.callback, ASM_DESCR.REG_ANY)} +mov rdi, qword ptr [r13 + 8] +mov r12, rsp +and rsp, -16 +call qword ptr [r13] +mov rsp, r12 +pop r13 +pop r12""", + op=[ASM_DESCR.CLOBBER_MEMORY, + ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, + ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, + ASM_DESCR.CLOBBER_RDI, + ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, + ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) + return 1 + return 0 + + def config(self, text: const str = None, fill_color: uint32 = None, text_color: uint32 = None, callback: rawptr = None): + if text is not None: + i: int = 0 + while i < 31: + ch: char = *(text + i) + self.text[i] = ch + if ch == 0: break + i += 1 + self.text[i] = 0 + if fill_color is not None: + self.fill_color = fill_color + if text_color is not None: + self.text_color = text_color + if callback is not None: + self.callback = callback + +virtual class Entry(Widget): + text: [64]char + text_len: int + cursor_pos: int + max_len: int + bg_color: uint32 + border_color: uint32 + text_color: uint32 + cursor_color: uint32 + + def __init__(self, master: Misc*, w: int, h: int): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self._misc_setup(master) + self.widget_type = WIDGET_ENTRY + self.focused = 0 + self.hovered = 0 + self.text_len = 0 + self.cursor_pos = 0 + self.max_len = 30 + self.bg_color = uint32(0xFF231919) + self.border_color = uint32(0x606080) + self.text_color = uint32(0x000000) + self.cursor_color = uint32(0x000000) + for i in range(64): + self.text[i] = 0 + _register_widget(master, Widget*(self)) + + def draw(self, fb: uint32*, fw: int, fh: int): + bg: uint32 = self.bg_color + bdr: uint32 = self.border_color + if self.focused: + bg = uint32(0xFFFFFF) + bdr = uint32(0x4488FF) + elif self.hovered: + bdr = uint32(0x8080B0) + _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, bg) + _draw_hline(fb, fw, fh, self.x, self.y, self.w, bdr) + _draw_hline(fb, fw, fh, self.x, self.y + self.h - 1, self.w, bdr) + _draw_vline(fb, fw, fh, self.x, self.y, self.h, bdr) + _draw_vline(fb, fw, fh, self.x + self.w - 1, self.y, self.h, bdr) + if self.text_len > 0: + tc: uint32 = self.text_color + if not self.focused: + tc = uint32(0xCCCCCC) + _draw_text(fb, fw, fh, self.x + 6, self.y + 7, &self.text[0], tc) + elif not self.focused: + _draw_text(fb, fw, fh, self.x + 6, self.y + 7, "Type here...", uint32(0x888888)) + if self.focused: + cx_pos: int = self.x + 6 + self.cursor_pos * CHAR_W + if cx_pos < self.x + self.w - 2: + _draw_vline(fb, fw, fh, cx_pos, self.y + 5, self.h - 10, self.cursor_color) + + def handle_event(self, evt: window.InputEvent*) -> int: + etype: uint32 = evt.type + ex: int = evt.x + ey: int = evt.y + if etype == window.EVENT_TYPE_HOVER: + if self.contains(ex, ey): + if not self.hovered: + self.hovered = 1 + return 1 + else: + if self.hovered: + self.hovered = 0 + return 1 + elif etype == window.EVENT_TYPE_HOVER_LEAVE: + if self.hovered: + self.hovered = 0 + return 1 + elif etype == window.EVENT_TYPE_CLICK: + if self.contains(ex, ey): + if not self.focused: + self.focused = 1 + tk4: Tk* = self.tk + tk4.focused_widget = Widget*(self) + rel_x: int = ex - self.x - 6 + if rel_x < 0: rel_x = 0 + new_pos: int = (rel_x + CHAR_W / 2) / CHAR_W + if new_pos > self.text_len: new_pos = self.text_len + self.cursor_pos = new_pos + return 1 + else: + if self.focused: + self.focused = 0 + tk5: Tk* = self.tk + tk5.focused_widget = None + return 1 + elif etype == window.EVENT_TYPE_KEY: + if self.focused: + ebtn: uint8 = evt.button + if ebtn == window.KEY_STATE_RELEASE: + return 0 + ch: int = ex + if ch == 8: + if self.cursor_pos > 0: + for i in range(self.cursor_pos, self.text_len): + self.text[i - 1] = self.text[i] + self.text_len -= 1 + self.cursor_pos -= 1 + self.text[self.text_len] = 0 + return 1 + elif ch == window.VK_LEFT: + if self.cursor_pos > 0: + self.cursor_pos -= 1 + return 1 + elif ch == window.VK_RIGHT: + if self.cursor_pos < self.text_len: + self.cursor_pos += 1 + return 1 + elif ch == window.VK_HOME: + self.cursor_pos = 0 + return 1 + elif ch == window.VK_END: + self.cursor_pos = self.text_len + return 1 + elif ch == window.VK_DELETE: + if self.cursor_pos < self.text_len: + for i in range(self.cursor_pos, self.text_len - 1): + self.text[i] = self.text[i + 1] + self.text_len -= 1 + self.text[self.text_len] = 0 + return 1 + elif ch >= 32 and ch < 127: + if self.text_len < self.max_len: + i: int = self.text_len + while i > self.cursor_pos: + self.text[i] = self.text[i - 1] + i -= 1 + self.text[self.cursor_pos] = char(ch) + self.text_len += 1 + self.cursor_pos += 1 + self.text[self.text_len] = 0 + return 1 + return 0 + + def get(self) -> const str: + return &self.text[0] + + def delete(self, start: int, end: int): + self.text_len = 0 + for i in range(64): + self.text[i] = 0 + + def config(self, bg_color: uint32 = None, text_color: uint32 = None, border_color: uint32 = None): + if bg_color is not None: + self.bg_color = bg_color + if text_color is not None: + self.text_color = text_color + if border_color is not None: + self.border_color = border_color + +virtual class Scrollbar(Widget): + orient: int + command: rawptr + slider_first: int + slider_last: int + action: int + action_value: int + pressed: int + dragging: int + drag_start_y: int + drag_start_first: int + trough_color: uint32 + slider_color: uint32 + arrow_color: uint32 + slider_hover: int + + def __init__(self, master: Misc*, w: int, h: int, orient: int): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self._misc_setup(master) + self.widget_type = WIDGET_SCROLLBAR + self.focused = 0 + self.hovered = 0 + self.orient = orient + self.command = None + self.slider_first = 0 + self.slider_last = 1000 + self.action = 0 + self.action_value = 0 + self.pressed = 0 + self.dragging = 0 + self.drag_start_y = 0 + self.drag_start_first = 0 + self.trough_color = uint32(0x1E1E2E) + self.slider_color = uint32(0x4A4A6A) + self.arrow_color = uint32(0xAAAAAA) + self.slider_hover = 0 + _register_widget(master, Widget*(self)) + + def draw(self, fb: uint32*, fw: int, fh: int): + _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.trough_color) + if self.orient != VERTICAL: return + arrow_sz: int = self.w + if arrow_sz > 16: arrow_sz = 16 + self._draw_arrow_btn(fb, fw, fh, self.x, self.y, self.w, arrow_sz, 0, 1 if self.pressed == 1 else 0) + self._draw_arrow_btn(fb, fw, fh, self.x, self.y + self.h - arrow_sz, self.w, arrow_sz, 1, 1 if self.pressed == 2 else 0) + trough_y: int = self.y + arrow_sz + trough_h: int = self.h - 2 * arrow_sz + if trough_h <= 0: return + if self.slider_last > self.slider_first: + sl_h: int = (self.slider_last - self.slider_first) * trough_h / 1000 + if sl_h < 8: sl_h = 8 + sl_y: int = trough_y + self.slider_first * trough_h / 1000 + if sl_y + sl_h > trough_y + trough_h: + sl_y = trough_y + trough_h - sl_h + if sl_y < trough_y: sl_y = trough_y + color: uint32 = self.slider_color + if self.dragging: + color = _lighten(self.slider_color, 40) + elif self.slider_hover: + color = _lighten(self.slider_color, 20) + _fill_rect(fb, fw, fh, self.x + 1, sl_y, self.w - 2, sl_h, color) + top_c: uint32 = _lighten(color, 60) + bot_c: uint32 = _darken(color, 60) + _draw_hline(fb, fw, fh, self.x + 1, sl_y, self.w - 2, top_c) + _draw_hline(fb, fw, fh, self.x + 1, sl_y + sl_h - 1, self.w - 2, bot_c) + _draw_vline(fb, fw, fh, self.x + 1, sl_y, sl_h, top_c) + _draw_vline(fb, fw, fh, self.x + self.w - 2, sl_y, sl_h, bot_c) + + def _draw_arrow_btn(self, fb: uint32*, fw: int, fh: int, bx: int, by: int, bw: int, bh: int, direction: int, is_pressed: int): + color: uint32 = self.slider_color + if is_pressed: + color = _lighten(self.slider_color, 30) + _fill_rect(fb, fw, fh, bx, by, bw, bh, color) + top_c: uint32 = _lighten(color, 60) + bot_c: uint32 = _darken(color, 60) + if is_pressed: + top_c = _darken(color, 40) + bot_c = _lighten(color, 30) + _draw_hline(fb, fw, fh, bx, by, bw, top_c) + _draw_hline(fb, fw, fh, bx, by + bh - 1, bw, bot_c) + _draw_vline(fb, fw, fh, bx, by, bh, top_c) + _draw_vline(fb, fw, fh, bx + bw - 1, by, bh, bot_c) + cx: int = bx + bw / 2 + cy: int = by + bh / 2 + if direction == 0: + _fill_rect(fb, fw, fh, cx - 1, cy - 3, 3, 1, self.arrow_color) + _fill_rect(fb, fw, fh, cx - 2, cy - 2, 5, 1, self.arrow_color) + _fill_rect(fb, fw, fh, cx - 3, cy - 1, 7, 1, self.arrow_color) + else: + _fill_rect(fb, fw, fh, cx - 3, cy - 1, 7, 1, self.arrow_color) + _fill_rect(fb, fw, fh, cx - 2, cy, 5, 1, self.arrow_color) + _fill_rect(fb, fw, fh, cx - 1, cy + 1, 3, 1, self.arrow_color) + + def handle_event(self, evt: window.InputEvent*) -> int: + etype: uint32 = evt.type + ex: int = evt.x + ey: int = evt.y + if etype == window.EVENT_TYPE_HOVER: + if self.dragging: + self._handle_drag(ey) + return 1 + if self.contains(ex, ey): + if not self.slider_hover: + self.slider_hover = 1 + return 1 + else: + if self.slider_hover and self.pressed == 0: + self.slider_hover = 0 + return 1 + return 0 + elif etype == window.EVENT_TYPE_HOVER_LEAVE: + if self.slider_hover: + self.slider_hover = 0 + if not self.dragging: + self.pressed = 0 + return 1 + return 0 + elif etype == window.EVENT_TYPE_CLICK: + if self.contains(ex, ey): + if self.orient == VERTICAL: + arrow_sz: int = self.w + if arrow_sz > 16: arrow_sz = 16 + if ey < self.y + arrow_sz: + self.pressed = 1 + self.action = 1 + self._fire_command() + return 1 + elif ey >= self.y + self.h - arrow_sz: + self.pressed = 2 + self.action = 2 + self._fire_command() + return 1 + else: + trough_y: int = self.y + arrow_sz + trough_h: int = self.h - 2 * arrow_sz + if trough_h <= 0: return 1 + sl_h: int = (self.slider_last - self.slider_first) * trough_h / 1000 + if sl_h < 8: sl_h = 8 + sl_y: int = trough_y + self.slider_first * trough_h / 1000 + if sl_y + sl_h > trough_y + trough_h: + sl_y = trough_y + trough_h - sl_h + if ey >= sl_y and ey < sl_y + sl_h: + self.pressed = 4 + self.dragging = 1 + self.drag_start_y = ey + self.drag_start_first = self.slider_first + else: + self.pressed = 3 + if ey < sl_y: + self.action = 3 + else: + self.action = 4 + self._fire_command() + return 1 + return 0 + elif etype == window.EVENT_TYPE_RELEASE: + if self.pressed: + self.pressed = 0 + self.dragging = 0 + return 1 + return 0 + return 0 + + def _handle_drag(self, ey: int): + arrow_sz: int = self.w + if arrow_sz > 16: arrow_sz = 16 + trough_y: int = self.y + arrow_sz + trough_h: int = self.h - 2 * arrow_sz + if trough_h <= 0: return + delta_y: int = ey - self.drag_start_y + delta_first: int = delta_y * 1000 / trough_h + new_first: int = self.drag_start_first + delta_first + slider_size: int = self.slider_last - self.slider_first + if slider_size <= 0: slider_size = 1 + if new_first < 0: new_first = 0 + if new_first + slider_size > 1000: new_first = 1000 - slider_size + if new_first != self.slider_first: + self.slider_first = new_first + self.slider_last = new_first + slider_size + self.action = 5 + self.action_value = new_first + self._fire_command() + + def _fire_command(self): + if self.command is not None: + # 同 Button.handle_event:command 是闭包 {fn, env},必须 rdi = env 后 call fn。 + __asm__(f"""push r12 +push r13 +mov r13, {asminp(self.command, ASM_DESCR.REG_ANY)} +mov rdi, qword ptr [r13 + 8] +mov r12, rsp +and rsp, -16 +call qword ptr [r13] +mov rsp, r12 +pop r13 +pop r12""", + op=[ASM_DESCR.CLOBBER_MEMORY, + ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, + ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, + ASM_DESCR.CLOBBER_RDI, + ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, + ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) + + def set(self, first: int, last: int): + self.slider_first = first + self.slider_last = last + +virtual class FBCanvas(Widget): + _fb: uint32* + _cw: int + _ch: int + + def __init__(self, master: Misc*, w: int, h: int): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self._misc_setup(master) + self.widget_type = WIDGET_FBCANVAS + self.focused = 0 + self.hovered = 0 + self._fb = None + self._cw = w + self._ch = h + _register_widget(master, Widget*(self)) + + def draw(self, fb: uint32*, fw: int, fh: int): + if not self._fb: return + if self._cw <= 0 or self._ch <= 0: return + copy_w: int = self._cw + if copy_w > self.w: copy_w = self.w + copy_h: int = self._ch + if copy_h > self.h: copy_h = self.h + for py in range(copy_h): + ty: int = self.y + py + if ty < 0 or ty >= fh: continue + for px in range(copy_w): + tx: int = self.x + px + if tx < 0 or tx >= fw: continue + fb[ty * fw + tx] = self._fb[py * self._cw + px] + + def handle_event(self, evt: window.InputEvent*) -> int: + return 0 + + def set_fb(self, fb: uint32*, cw: int, ch: int): + self._fb = fb + self._cw = cw + self._ch = ch + + def get_fb(self) -> uint32*: + return self._fb + + def get_w(self) -> int: + return self._cw + + def get_h(self) -> int: + return self._ch + +virtual class Text(Widget): + lines: [TEXT_MAX_LINES][80]char + line_count: int + input_buf: [80]char + input_len: int + cursor_pos: int + prompt: [80]char + bg_color: uint32 + text_color: uint32 + prompt_color: uint32 + cursor_color: uint32 + border_color: uint32 + on_command: rawptr + history: [16][80]char + history_count: int + history_idx: int + scroll_offset: int + max_lines: int + yscrollbar: rawptr + + def __init__(self, master: Misc*, w: int, h: int): + self.x = 0 + self.y = 0 + self.w = w + self.h = h + self.visible = 1 + self._misc_setup(master) + self.widget_type = WIDGET_TEXT + self.focused = 0 + self.hovered = 0 + self.line_count = 0 + self.input_len = 0 + self.cursor_pos = 0 + self.prompt[0] = '>' + self.prompt[1] = ' ' + self.prompt[2] = 0 + self.bg_color = uint32(0x0C0C0C) + self.text_color = uint32(0xCCCCCC) + self.prompt_color = uint32(0xCCCCCC) + self.cursor_color = uint32(0xCCCCCC) + self.border_color = uint32(0x333333) + self.on_command = None + self.history_count = 0 + self.history_idx = 0 + self.scroll_offset = 0 + self.max_lines = TEXT_MAX_LINES + self.yscrollbar = None + for i in range(TEXT_MAX_LINES): + for j in range(79 + 1): + self.lines[i][j] = 0 + for i in range(80): + self.input_buf[i] = 0 + _register_widget(master, Widget*(self)) + + def draw(self, fb: uint32*, fw: int, fh: int): + self._update_scrollbar() + _fill_rect(fb, fw, fh, self.x, self.y, self.w, self.h, self.bg_color) + _fill_rect(fb, fw, fh, self.x, self.y + self.h - 2, self.w, 2, self.border_color) + visible: int = self._visible_lines() + for i in range(visible): + line_idx: int = self.scroll_offset + i + if line_idx < self.line_count: + _draw_text(fb, fw, fh, self.x + 4, self.y + i * CHAR_H, &self.lines[line_idx][0], self.text_color) + # 输入行不是一个固定在底部的控件,而是紧跟在最后一条输出之后的一行; + # 输出增多时它随之下移,超出可视区时由 scroll_offset 把它保持在最后一行。 + prompt_row: int = self.line_count - self.scroll_offset + if prompt_row >= 0 and prompt_row < visible: + prompt_y: int = self.y + prompt_row * CHAR_H + plen: int = int(string.strlen(&self.prompt[0])) + _draw_text(fb, fw, fh, self.x + 4, prompt_y, &self.prompt[0], self.prompt_color) + if self.input_len > 0: + _draw_text(fb, fw, fh, self.x + 4 + plen * CHAR_W, prompt_y, + &self.input_buf[0], self.text_color) + cx_pos: int = self.x + 4 + (plen + self.cursor_pos) * CHAR_W + if cx_pos < self.x + self.w - 4: + _fill_rect(fb, fw, fh, cx_pos, prompt_y, CHAR_W // 2, CHAR_H, self.cursor_color) + + def handle_event(self, evt: window.InputEvent*) -> int: + etype: uint32 = evt.type + ex: int = evt.x + ey: int = evt.y + if etype == window.EVENT_TYPE_CLICK: + if self.contains(ex, ey): + if not self.focused: + self.focused = 1 + tk: Tk* = self.tk + tk.focused_widget = Widget*(self) + prompt_row: int = self.line_count - self.scroll_offset + prompt_y: int = self.y + prompt_row * CHAR_H + if prompt_row >= 0 and prompt_row < self._visible_lines() and ey >= prompt_y and ey < prompt_y + CHAR_H: + plen: int = int(string.strlen(&self.prompt[0])) + rel_x: int = ex - self.x - 4 - plen * CHAR_W + if rel_x < 0: rel_x = 0 + new_pos: int = (rel_x + CHAR_W / 2) / CHAR_W + if new_pos > self.input_len: new_pos = self.input_len + self.cursor_pos = new_pos + return 1 + else: + if self.focused: + self.focused = 0 + tk2: Tk* = self.tk + tk2.focused_widget = None + return 1 + elif etype == window.EVENT_TYPE_WHEEL: + if self.contains(ex, ey): + w: int = int(evt.button) + if w >= 128: w = w - 256 + vis: int = self._visible_lines() + self.scroll_offset = self.scroll_offset - w * 3 + max_off: int = self.line_count + 1 - vis + if max_off < 0: max_off = 0 + if self.scroll_offset < 0: self.scroll_offset = 0 + if self.scroll_offset > max_off: self.scroll_offset = max_off + self._update_scrollbar() + return 1 + return 0 + elif etype == window.EVENT_TYPE_KEY: + if self.focused: + ebtn2: uint8 = evt.button + if ebtn2 == window.KEY_STATE_RELEASE: + return 0 + ch: int = ex + if ch == 8: + if self.cursor_pos > 0: + i: int = self.cursor_pos + while i < self.input_len: + self.input_buf[i - 1] = self.input_buf[i] + i += 1 + self.input_len -= 1 + self.cursor_pos -= 1 + self.input_buf[self.input_len] = 0 + return 1 + elif ch == 13 or ch == 10: + self._exec_input() + self.input_len = 0 + self.cursor_pos = 0 + for i in range(80): + self.input_buf[i] = 0 + return 1 + elif ch == window.VK_LEFT: + if self.cursor_pos > 0: + self.cursor_pos -= 1 + return 1 + elif ch == window.VK_RIGHT: + if self.cursor_pos < self.input_len: + self.cursor_pos += 1 + return 1 + elif ch == window.VK_UP: + if self.history_count > 0: + if self.history_idx < self.history_count: + self.history_idx += 1 + hi: int = self.history_count - self.history_idx + if hi < 0: hi = 0 + for j in range(80): + self.input_buf[j] = self.history[hi][j] + self.input_len = 0 + while self.input_len < 79 and self.input_buf[self.input_len] != 0: + self.input_len += 1 + self.cursor_pos = self.input_len + return 1 + elif ch == window.VK_DOWN: + if self.history_count > 0: + if self.history_idx > 0: + self.history_idx -= 1 + if self.history_idx == 0: + self.input_len = 0 + self.cursor_pos = 0 + for j2 in range(80): + self.input_buf[j2] = 0 + else: + hi2: int = self.history_count - self.history_idx + for j3 in range(80): + self.input_buf[j3] = self.history[hi2][j3] + self.input_len = 0 + while self.input_len < 79 and self.input_buf[self.input_len] != 0: + self.input_len += 1 + self.cursor_pos = self.input_len + return 1 + elif ch == window.VK_HOME: + self.cursor_pos = 0 + return 1 + elif ch == window.VK_END: + self.cursor_pos = self.input_len + return 1 + elif ch == window.VK_DELETE: + if self.cursor_pos < self.input_len: + i: int = self.cursor_pos + while i < self.input_len - 1: + self.input_buf[i] = self.input_buf[i + 1] + i += 1 + self.input_len -= 1 + self.input_buf[self.input_len] = 0 + return 1 + elif ch == window.VK_PRIOR: + if self.scroll_offset > 0: + vis: int = self._visible_lines() + self.scroll_offset -= vis + if self.scroll_offset < 0: self.scroll_offset = 0 + self._update_scrollbar() + return 1 + elif ch == window.VK_NEXT: + vis2: int = self._visible_lines() + max_off: int = self.line_count + 1 - vis2 + if max_off < 0: max_off = 0 + if self.scroll_offset < max_off: + self.scroll_offset += vis2 + if self.scroll_offset > max_off: self.scroll_offset = max_off + self._update_scrollbar() + return 1 + elif ch >= 32 and ch < 127: + if self.input_len < 79: + i: int = self.input_len + while i > self.cursor_pos: + self.input_buf[i] = self.input_buf[i - 1] + i -= 1 + self.input_buf[self.cursor_pos] = char(ch) + self.input_len += 1 + self.cursor_pos += 1 + self.input_buf[self.input_len] = 0 + return 1 + return 0 + + def _exec_input(self): + self.insert(&self.prompt[0]) + plen: int = int(string.strlen(&self.prompt[0])) + if self.input_len > 0: + last: int = self.line_count - 1 + j: int = 0 + while j < self.input_len and plen + j < 79: + self.lines[last][plen + j] = self.input_buf[j] + j += 1 + self.lines[last][plen + j] = 0 + if self.history_count < 16: + for k in range(80): + self.history[self.history_count][k] = self.input_buf[k] + self.history_count += 1 + else: + for i in range(15): + for k2 in range(80): + self.history[i][k2] = self.history[i + 1][k2] + for k3 in range(80): + self.history[15][k3] = self.input_buf[k3] + self.history_idx = 0 + + cmd: rawptr = self.on_command + before_lines: int = self.line_count + if cmd is not None: + # 同 Button.handle_event:on_command 是闭包 {fn, env},必须 rdi = env 后 call fn。 + __asm__(f"""push r12 +push r13 +mov r13, {asminp(cmd, ASM_DESCR.REG_ANY)} +mov rdi, qword ptr [r13 + 8] +mov r12, rsp +and rsp, -16 +call qword ptr [r13] +mov rsp, r12 +pop r13 +pop r12""", + op=[ASM_DESCR.CLOBBER_MEMORY, + ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, + ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, + ASM_DESCR.CLOBBER_RDI, + ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, + ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) + else: + self.insert("(no callback)") + + # 本命令确实产生了输出时,在日志与下一个输入行之间留一个空行。 + # 这样提示符不会紧贴最后一行日志(用实际写入的行数判断,clear 不会被补空行)。 + if self.line_count > before_lines: + self.insert("\n") + + def insert(self, text: str): + start: int = 0 + pos: int = 0 + while True: + ch: char = *(text + pos) + if ch == 0: + if pos > start: + self._insert_substring(text, start, pos) + break + if ch == 10: + self._insert_substring(text, start, pos) + start = pos + 1 + pos += 1 + + def insert_ptr(self, text: const str): + start: int = 0 + pos: int = 0 + while True: + ch: char = *(text + pos) + if ch == 0: + if pos > start: + self._insert_substring(text, start, pos) + break + if ch == 10: + self._insert_substring(text, start, pos) + start = pos + 1 + pos += 1 + + def _insert_substring(self, text: const str, start: int, end: int): + if self.line_count >= self.max_lines: + for i in range(self.max_lines - 1): + for j in range(79 + 1): + self.lines[i][j] = self.lines[i + 1][j] + self.line_count -= 1 + if self.scroll_offset > 0: + self.scroll_offset -= 1 + idx: int = self.line_count + j: int = 0 + k: int = start + while k < end and j < 79: + ch: char = *(text + k) + if ch == 9: + tab_stop: int = 8 - (j % 8) + while tab_stop > 0 and j < 79: + self.lines[idx][j] = 32 + j += 1 + tab_stop -= 1 + elif ch == 13: + pass + else: + self.lines[idx][j] = ch + j += 1 + k += 1 + self.lines[idx][j] = 0 + self.line_count += 1 + vis: int = self._visible_lines() + total: int = self.line_count + 1 # 输出行 + 输入行 + if total > vis: + self.scroll_offset = total - vis + else: + self.scroll_offset = 0 + self._update_scrollbar() + + def delete(self, start: int, end: int): + self.line_count = 0 + self.scroll_offset = 0 + self._update_scrollbar() + + def get(self) -> const str: + return &self.input_buf[0] + + def set_prompt(self, text: const str): + i: int = 0 + while i < 79: + ch: char = *(text + i) + self.prompt[i] = ch + if ch == 0: break + i += 1 + self.prompt[i] = 0 + + def config(self, text_color: uint32 = None, bg_color: uint32 = None, prompt_color: uint32 = None, cursor_color: uint32 = None, border_color: uint32 = None): + if text_color is not None: + self.text_color = text_color + if bg_color is not None: + self.bg_color = bg_color + if prompt_color is not None: + self.prompt_color = prompt_color + if cursor_color is not None: + self.cursor_color = cursor_color + if border_color is not None: + self.border_color = border_color + + def _visible_lines(self) -> int: + return (self.h - 2) / CHAR_H + + def _update_scrollbar(self): + vis: int = self._visible_lines() + total: int = self.line_count + 1 # 输出行 + 输入行 + if total <= vis: + self.scroll_offset = 0 + else: + max_off: int = total - vis + if self.scroll_offset > max_off: + self.scroll_offset = max_off + if self.yscrollbar is not None: + sb: Scrollbar* = Scrollbar*(self.yscrollbar) + if total <= vis: + sb.set(0, 1000) + else: + first: int = self.scroll_offset * 1000 / total + last: int = (self.scroll_offset + vis) * 1000 / total + sb.set(first, last) + +# ---- Tk:主窗口类 = Wm(窗口管理器协议,无字段)+ Misc(公共状态/方法)---- +virtual class Tk(Wm, Misc): + win_id: int + buf: uint32* + bw: int + bh: int + style: int + bg_color: uint32 + widgets: [MAX_WIDGETS]Widget* + widget_count: int + focused_widget: Widget* + hovered_widget: Widget* + _win_x: int + _win_y: int + _title: [32]char + resizable_w: int + resizable_h: int + _grid_rows: int + _grid_cols: int + _bb: uint32* # 后备缓冲:整帧画完再一次性拷进共享 arena,避免合成器读到半成品帧 + _bb_px: int # 后备缓冲容量(像素数),窗口变大时才重新分配 + _ansi_state: int # ESC 序列解析状态:0=普通 1=已收 ESC 2=已收 ESC[ + _ansi_param: int # CSI 数值参数(如 ESC[5~ 的 5) + + def __init__(self): + global _g_layout_init, _text_mode + # 运行时显式保持 MANAGED:文字交给 explorer 统一用 TTF 渲染, + # 避免每个 Tk 应用各自加载 13MB 字体。 + _text_mode = TEXT_MODE_MANAGED + # Misc 的公共状态:主窗口没有父容器,tk 指向自身(控件经 _misc_setup 继承它) + self.master = None + self.tk = Tk*(self) + self.widget_count = 0 + for i in range(MAX_WIDGETS): + self.widgets[i] = None + self.focused_widget = None + self.hovered_widget = None + self.win_id = -1 + self._win_x = 0 + self._win_y = 0 + self.style = 0 + self.bg_color = uint32(0xFF231919) + self.resizable_w = 1 + self.resizable_h = 1 + self._grid_rows = 0 + self._grid_cols = 0 + if _g_layout_init == 0: + for i in range(MAX_WIDGETS): + _g_wl_type[i] = 0 + _g_wl_side[i] = 0 + _g_wl_fill[i] = 0 + _g_wl_expand[i] = 0 + _g_wl_padx[i] = 0 + _g_wl_pady[i] = 0 + _g_wl_row[i] = 0 + _g_wl_col[i] = 0 + _g_wl_rowspan[i] = 0 + _g_wl_colspan[i] = 1 + _g_wl_sticky[i] = 0 + _g_layout_init = 1 + self._bb = None + self._bb_px = 0 + self._ansi_state = 0 + self._ansi_param = 0 + for j in range(32): + self._title[j] = 0 + + def widget_limit(self) -> int: + c: int = self.widget_count + if c < 0: return 0 + if c > MAX_WIDGETS: return MAX_WIDGETS + return c + + def pack(self, idx: int, side: int, fill: int, expand: int, padx: int, pady: int): + if idx < 0 or idx >= MAX_WIDGETS: return + _g_wl_type[idx] = 2 + _g_wl_side[idx] = side + _g_wl_fill[idx] = fill + _g_wl_expand[idx] = expand + _g_wl_padx[idx] = padx + _g_wl_pady[idx] = pady + + def grid(self, idx: int, row: int, col: int, rowspan: int, colspan: int, sticky: int): + if idx < 0 or idx >= MAX_WIDGETS: return + _g_wl_type[idx] = 3 + _g_wl_row[idx] = row + _g_wl_col[idx] = col + _g_wl_rowspan[idx] = rowspan + _g_wl_colspan[idx] = colspan + _g_wl_sticky[idx] = sticky + + # ---- Wm 协议的落地实现(Tk 持有窗口状态,覆写 Wm 声明的协议面)---- + def title(self, text: str): + i: int = 0 + while i < 31: + ch: char = text[i] + if ch == 0: break + self._title[i] = ch + i += 1 + self._title[i] = 0 + if self.win_id >= 0: + window.set_title(self.win_id, &self._title[0]) + + def set_style(self, s: int): + self.style = s + + def resizable(self, rw: int, rh: int): + self.resizable_w = rw + self.resizable_h = rh + if self.win_id >= 0: + window.set_resizable(self.win_id, rw, rh) + + def geometry(self, x: int, y: int, w: int, h: int): + self._win_x = x + self._win_y = y + self.bw = w + self.bh = h - TITLE_H - 2 + if self.win_id < 0: + self.win_id = window.create_window(x, y, w, h, &self._title[0], self.style) + self.buf = window.get_winbuf(self.win_id) + window.set_resizable(self.win_id, self.resizable_w, self.resizable_h) + else: + window.set_geometry(self.win_id, x, y, w, h) + self.buf = window.get_winbuf(self.win_id) + + def setup(self, x: int, y: int, w: int, h: int, title: const str, style: int): + self._win_x = x + self._win_y = y + self.win_id = window.create_window(x, y, w, h, title, style) + self.buf = window.get_winbuf(self.win_id) + self.bw = w + self.bh = h - TITLE_H - 2 + self.style = style + self.bg_color = uint32(0xFF231919) + + def redraw(self): + global _text_qn + # 窗口槽已回收(用户点了关闭):立即返回,不再触碰共享 arena / flush。 + if self.win_id < 0: return + if window.is_active(self.win_id) == 0: return + self.buf = window.get_winbuf(self.win_id) + sz: int = window.get_win_size(self.win_id) + if sz != 0: + self.bw = sz >> 16 + self.bh = sz & 0xFFFF + if not self.buf: return + if self.bw <= 0 or self.bh <= 0: return + # 先刷新尺寸再布局:窗口 resize 后若沿用上一帧的 bw/bh,布局会滞后一帧。 + self._do_layout() + _text_qn = 0 + # 双缓冲:整帧先画在**进程本地**的后备缓冲里,画完一次性拷进共享 arena,再 flush。 + # 为什么必须这么做:arena 正是 explorer 合成时读的那块内存(render_window_rows + # 直接从它逐行 blit 上屏)。若直接往 arena 上画,合成器会在"整窗已填成背景色、 + # 控件还没画"的中途采样 ⇒ 窗口闪烁;光标移动让 explorer 重画窗口所在行时同样撞上。 + px: int = self.bw * self.bh + if self._bb_px < px: + if self._bb: stdlib.free(rawptr(self._bb)) + self._bb = uint32*(stdlib.malloc(uint64(px * 4))) + self._bb_px = px + tgt: uint32* = self._bb + if not tgt: + tgt = self.buf + _fill_rect(tgt, self.bw, self.bh, 0, 0, self.bw, self.bh, self.bg_color) + for i in range(self.widget_limit()): + w: Widget* = self.widgets[i] + if w is None: continue + if w.visible: + w.draw(tgt, self.bw, self.bh) + if tgt != self.buf: + string.memcpy(self.buf, tgt, uint64(px * 4)) + _flush_text(self.win_id) + window.flush(self.win_id) + + def _do_layout(self): + self._grid_rows = 0 + self._grid_cols = 0 + cav_x: int = 0 + cav_y: int = 0 + cav_w: int = self.bw + cav_h: int = self.bh + for i in range(self.widget_limit()): + w: Widget* = self.widgets[i] + if w is None: continue + lt: int = _g_wl_type[i] + if lt == 2: + side: int = _g_wl_side[i] + fill: int = _g_wl_fill[i] + expand: int = _g_wl_expand[i] + px: int = _g_wl_padx[i] + py: int = _g_wl_pady[i] + ww: int = w.w + wh: int = w.h + # 交叉轴上未 fill 时按 Tk 默认 anchor=center 居中;沿 pack 方向仍贴腔体边缘 + + # padding(与 tkinter 一致)。此前一律贴左上,导致不 fill 的控件全部挤在左边缘。 + if side == PACK_TOP: + if fill == FILL_X or fill == FILL_BOTH: + ww = cav_w - 2 * px + w.x = cav_x + px + else: + w.x = cav_x + px + (cav_w - 2 * px - ww) / 2 + if expand: + if fill == FILL_Y or fill == FILL_BOTH: + wh = cav_h - 2 * py + w.y = cav_y + py + w.w = ww + w.h = wh + cav_y += wh + 2 * py + cav_h -= wh + 2 * py + elif side == PACK_BOTTOM: + if fill == FILL_X or fill == FILL_BOTH: + ww = cav_w - 2 * px + w.x = cav_x + px + else: + w.x = cav_x + px + (cav_w - 2 * px - ww) / 2 + if expand: + if fill == FILL_Y or fill == FILL_BOTH: + wh = cav_h - 2 * py + w.y = cav_y + cav_h - wh - py + w.w = ww + w.h = wh + cav_h -= wh + 2 * py + elif side == PACK_LEFT: + if fill == FILL_Y or fill == FILL_BOTH: + wh = cav_h - 2 * py + w.y = cav_y + py + else: + w.y = cav_y + py + (cav_h - 2 * py - wh) / 2 + if expand: + if fill == FILL_X or fill == FILL_BOTH: + ww = cav_w - 2 * px + w.x = cav_x + px + w.w = ww + w.h = wh + cav_x += ww + 2 * px + cav_w -= ww + 2 * px + elif side == PACK_RIGHT: + if fill == FILL_Y or fill == FILL_BOTH: + wh = cav_h - 2 * py + w.y = cav_y + py + else: + w.y = cav_y + py + (cav_h - 2 * py - wh) / 2 + if expand: + if fill == FILL_X or fill == FILL_BOTH: + ww = cav_w - 2 * px + w.x = cav_x + cav_w - ww - px + w.w = ww + w.h = wh + cav_w -= ww + 2 * px + _container_offset(self, w) + elif lt == 3: + r: int = _g_wl_row[i] + _g_wl_rowspan[i] + c: int = _g_wl_col[i] + _g_wl_colspan[i] + if r > self._grid_rows: self._grid_rows = r + if c > self._grid_cols: self._grid_cols = c + for i in range(self.widget_limit()): + w: Widget* = self.widgets[i] + if w is None: continue + if _g_wl_type[i] == 3: + if self._grid_rows <= 0: self._grid_rows = 1 + if self._grid_cols <= 0: self._grid_cols = 1 + cell_w: int = self.bw / self._grid_cols + cell_h: int = self.bh / self._grid_rows + wx: int = _g_wl_col[i] * cell_w + wy: int = _g_wl_row[i] * cell_h + ww: int = _g_wl_colspan[i] * cell_w + wh: int = _g_wl_rowspan[i] * cell_h + sticky: int = _g_wl_sticky[i] + if sticky == 0: + ox: int = (ww - w.w) / 2 + oy: int = (wh - w.h) / 2 + w.x = wx + ox + w.y = wy + oy + else: + w.x = wx + w.y = wy + if (sticky & 1) != 0: + w.w = ww + if (sticky & 2) != 0: + w.h = wh + _container_offset(self, w) + + def update(self): + self.redraw() + + def winfo_exists(self) -> int: + if self.win_id < 0: return 0 + return window.is_active(self.win_id) + + # ---- Misc 查询的覆写:主窗口用自己的窗口状态回答 winfo_* ---- + def winfo_ismapped(self) -> int: + if self.win_id < 0: return 0 + return window.is_active(self.win_id) + + def winfo_x(self) -> int: + return self._win_x + + def winfo_y(self) -> int: + return self._win_y + + def winfo_width(self) -> int: + return self.bw + + def winfo_height(self) -> int: + return self.bh + + # 无显式焦点控件时,按注册顺序取第一个可见文本控件(Text/Entry) + def _first_text_widget(self) -> Widget*: + for i in range(self.widget_limit()): + w: Widget* = self.widgets[i] + if w is None: continue + if w.visible != 0: + if w.widget_type == WIDGET_TEXT or w.widget_type == WIDGET_ENTRY: + return w + return None + + # 逐字节投递的按键流 -> ANSI CSI 状态机;返回 0=本字节被吞掉,1=可分发 + def _fold_ansi_key(self, evt: window.InputEvent*) -> int: + ch: int = evt.x + if self._ansi_state == 0: + if ch == 27: + self._ansi_state = 1 + self._ansi_param = 0 + return 0 + return 1 + if self._ansi_state == 1: + if ch == 91: + self._ansi_state = 2 + return 0 + # 孤立 ESC:只丢弃 ESC,当前字节照常分发 —— 否则会连带吞掉下一个键入字符。 + self._ansi_state = 0 + return 1 + if ch >= 48 and ch <= 57: + self._ansi_param = self._ansi_param * 10 + (ch - 48) + return 0 + self._ansi_state = 0 + vk: int = 0 + if ch == 65: vk = window.VK_UP + elif ch == 66: vk = window.VK_DOWN + elif ch == 67: vk = window.VK_RIGHT + elif ch == 68: vk = window.VK_LEFT + elif ch == 72: vk = window.VK_HOME + elif ch == 70: vk = window.VK_END + elif ch == 126: + if self._ansi_param == 3: vk = window.VK_DELETE + elif self._ansi_param == 5: vk = window.VK_PRIOR + elif self._ansi_param == 6: vk = window.VK_NEXT + if vk == 0: return 0 + evt.x = vk + return 1 + + # 指针下的最上层控件决定光标形状:Entry/Text → I 形,Button → 手指。 + # 与控件自己的 hover 视觉解耦(后者仍由 dispatch_event 维护)。 + def _update_cursor_for_hover(self, ex: int, ey: int): + hw: Widget* = None + i: int = self.widget_limit() - 1 + while i >= 0: + w: Widget* = self.widgets[i] + if w is None: + i -= 1 + continue + if w.visible and w.contains(ex, ey): + hw = w + break + i -= 1 + if hw == self.hovered_widget: return + self.hovered_widget = hw + ct: int = window.CURSOR_DEFAULT + if hw is not None: + if hw.widget_type == WIDGET_ENTRY or hw.widget_type == WIDGET_TEXT: + ct = window.CURSOR_IBEAM + elif hw.widget_type == WIDGET_BUTTON: + ct = window.CURSOR_HAND + window.set_cursor_type(self.win_id, ct) + + def mainloop(self): + self.redraw() + evt: window.InputEvent + while window.is_active(self.win_id): + result: int = window.poll_event(&evt) + if result != 0: + if evt.win_id == self.win_id: + need_redraw: int = 0 + etype: uint32 = evt.type + if etype == window.EVENT_TYPE_PING: + window.flush(self.win_id) + elif etype == window.EVENT_TYPE_RESIZE: + self.buf = window.get_winbuf(self.win_id) + sz: int = window.get_win_size(self.win_id) + self.bw = sz >> 16 + self.bh = sz & 0xFFFF + need_redraw = 1 + elif etype == window.EVENT_TYPE_KEY or etype == window.EVENT_TYPE_KEY_RELEASE: + folded: int = 1 + if etype == window.EVENT_TYPE_KEY: + folded = self._fold_ansi_key(&evt) + if folded != 0: + fw: Widget* = self.focused_widget + if fw is None: + fw = self._first_text_widget() + if fw is not None: + fw.focused = 1 + self.focused_widget = fw + if fw is not None: + handled: int = fw.dispatch_event(&evt) + if handled: need_redraw = 1 + else: + if etype == window.EVENT_TYPE_HOVER: + self._update_cursor_for_hover(evt.x, evt.y) + i: int = self.widget_limit() - 1 + while i >= 0: + w: Widget* = self.widgets[i] + if w is None: + i -= 1 + continue + if w.visible: + handled2: int = w.dispatch_event(&evt) + if handled2: + need_redraw = 1 + break + i -= 1 + else: + for i in range(self.widget_limit()): + w: Widget* = self.widgets[i] + if w is None: continue + if w.visible: + handled2: int = w.dispatch_event(&evt) + if handled2: need_redraw = 1 + if need_redraw: + self.redraw() + else: + process.yield_cpu()