fix(mm): 加分配失败分步诊断;build.py 改用 --phase2command 产出 kernel.elf

- mm.vp: 在 malloc(slab 路径)、_create_page、allocate_block 三处失败分支各加一条
  [mm] 诊断打印,用于定位“[desktop] dirty_rows malloc FAILED”
  (768B → ≤SLAB_THRESHOLD → slab size class 5(1024B) → _create_page 需 order≥1 伙伴块;
   DumpBuddy 显示伙伴空闲链健康,故需进一步区分是哪一步返回 NULL)
- build.py: kernel.elf 改为调用 viperc --phase2command 拿到“精确链接命令”再改写产出。
  自行 glob .obj 去链会因对象顺序不同得到与 kernel.bin 不一致的段布局
  (实测前 247KB 里 230KB 不同),无法把日志 rip 映射回函数。
This commit is contained in:
TermiNexus committed 2026-10-14 13:36:01 +08:00
1 parent cb5eb679c1
commit 449f2dbc37
2 files changed
+38 -13

No files matched your search

+17 -2
View File
@@ -193,7 +193,12 @@ class _BuddySystemObject:
if self.buddy.free_lists[i]:
found_order = i
break
if found_order == -1: return None
if found_order == -1:
# [DIAG] 池面耗尽/链表坏:打印请求大小与所需阶数
_adb: [128]char
viperlib.snprintf(&_adb, 128, "[mm] allocate_block(size=%lu) need order=%d -> NO FREE BLOCK\n", size, order)
serial.puts(&_adb)
return None
# 取出空闲块
block: memory_block* = self.buddy.free_lists[found_order]
# 从空闲链表中移除块
@@ -287,7 +292,12 @@ class _SlabAllocator:
obj_size: uint16 = _slab_obj_size(size_class)
aligned_size: uint16 = uint16((obj_size + 15) & ~15)
block: memory_block* = BuddySystemObject.allocate_block(4096 + memory_block.__sizeof__())
if not block: return None
if not block:
# [DIAG] slab 建页拿不到伙伴块
_cpb: [96]char
viperlib.snprintf(&_cpb, 96, "[mm] slab _create_page(sc=%d) allocate_block(4128) -> NULL\n", size_class)
serial.puts(&_cpb)
return None
hdr_off: uint64 = memory_block.__sizeof__()
page: slab_page* = slab_page*(uint64(block) + hdr_off)
page.magic = SLAB_MAGIC
@@ -353,6 +363,11 @@ export def malloc(size: uint64) -> rawptr:
sc: int = _slab_size_class(size)
if sc >= 0:
result: rawptr = SlabAllocator.alloc(sc)
if result is None:
# [DIAG] slab 分配失败(缓存链与建页都失败)
_mb: [96]char
viperlib.snprintf(&_mb, 96, "[mm] malloc(%lu) slab sc=%d -> NULL\n", size, sc)
serial.puts(&_mb)
return result
hdr: uint64 = memory_block.__sizeof__()
if size >= 4096:
+21 -11
View File
@@ -3,7 +3,6 @@ import os
import sys
import subprocess
import threading
import glob
# 解析参数
@@ -23,19 +22,30 @@ os.system(cmd)
shutil.copy("VKernel/__vpcbuild__/output/kernel.bin", "./disk/sys/kernel/x86_64/kernel.bin")
# 额外链接一份带符号的 kernel.elf(仅供分析:objdump / nm / readelf 定位崩溃 rip)。
# kernel.bin 是 --oformat binary --strip-all 的裸镜像,无符号表,不便把日志里的
# rip=0x130d45 映射回函数。这里用同一批 .obj + 同一 linker.ld 再链一次(只去掉
# --oformat binary/--strip-all),段布局与 kernel.bin 一致 → ELF 里的地址可直接对上运行镜像。
# 额外产出带符号的 kernel.elf(仅供分析:objdump / nm / readelf 定位崩溃 rip)。
# kernel.bin 是 --oformat binary --strip-all 的裸镜像、无符号表,不便把日志里的
# rip 映射回函数。要点:**对象顺序决定段内布局**,自行 glob 一组 .obj 去链会得到
# 与 kernel.bin 不同的地址(实测前 247KB 里 230KB 不同),没法对上日志。
# 因此用 viperc 自己的 --phase2command:它把 llc/链接命令按“精确顺序”写进 .bat,
# 我们只把该链接行里的 --oformat binary/--strip-all 去掉、输出改为 kernel.elf 再执行。
kernel_elf = "VKernel/__vpcbuild__/output/kernel.elf"
kernel_objs = sorted(glob.glob("VKernel/__vpcbuild__/output/*/*.obj"))
if kernel_objs:
print(f"[build] linking {kernel_elf} for analysis ({len(kernel_objs)} objects)...")
link_rc = subprocess.call(["ld.lld.exe", "-m", "elf_x86_64", "-T", "VKernel/linker.ld", "-o", kernel_elf] + kernel_objs)
if link_rc != 0:
p2c_bat = "VKernel/__vpcbuild__/phase2cmd.bat"
os.system(r'..\..\ViperC\output\viperc.exe --project ./VKernel/project.vpj --phase2command ' + p2c_bat)
link_line = None
if os.path.exists(p2c_bat):
with open(p2c_bat, "r", encoding="utf-8", errors="replace") as f:
for line in f:
low = line.lower()
if "ld.lld" in low and (".bin" in low or "output" in low):
link_line = line.rstrip("\r\n")
if link_line is not None:
elf_line = link_line.replace("--oformat binary", "").replace("--strip-all", "")
elf_line = elf_line.replace("kernel.bin", "kernel.elf")
print("[build] linking kernel.elf for analysis ...")
if os.system(elf_line) != 0:
print("[build] 警告: kernel.elf 链接失败(不影响 kernel.bin)")
else:
print("[build] 警告: 未找到 kernel 目标文件,跳过 kernel.elf")
print("[build] 警告: 未从 phase2command 取到链接命令,跳过 kernel.elf")
# --noapp 优先级高于 --kernel-only:只编译内核,不编译所有库和应用