Files
ViperOS/build.py
T
TermiNexus 8b0d24e0d5 feat(build, kernel): 内核链接直接产出带符号 kernel.elf,再由 objcopy 二次产出 kernel.bin
按方案调整:
- VKernel/project.vpj:linker 去掉 --oformat binary / --strip-all,output 改为 kernel.elf。
- build.py:用 objcopy -O binary --set-section-flags .bss=alloc,load,contents 由 kernel.elf
  二次产出 kernel.bin,再拷到 disk/。同源产出 ⇒ 两者布局逐字节一致,日志里的 rip 可直接用
  objdump / nm / readelf 落到函数名;原来那段依赖 phase2cmd.bat 的改写字样已整体删除。
- --set-section-flags .bss=...contents 把 .bss 一并写进裸镜像(ELF 里 .bss 默认 NOBITS),
  于是 bootloader 按“文件大小”预留即可覆盖 .bss(boot.c 里另有 256KB 余量,双保险)。
- linker.ld:移除此前对 lld 无效的 LONG(0) 写法,.bss 恢复为干净定义。

注意:需完整重编(不能只 --onlyrun)才会生成新的 kernel.elf / kernel.bin。
2026-10-14 15:22:05 +08:00

184 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import shutil
import os
import sys
import subprocess
# 解析参数
clean = '--noclean' not in sys.argv
kernel_only = '--kernel-only' in sys.argv
noapp = '--noapp' in sys.argv
bootloader = '--bootloader' in sys.argv
# --onlyrun:跳过全部编译/打包步骤,只用现有 disk.img 反复启动 QEMU 复现问题。
# 典型用法:python build.py --onlyrun(连跑多次,观察偶发写坏是否复现并留下 [FATAL])
only_run = '--onlyrun' in sys.argv
def BuildKernel():
"""编译内核:链接器一次产出带符号 kernel.elf,再由 objcopy 二次处理成裸镜像 kernel.bin"""
cmd = r'..\..\ViperC\output\viperc.exe --project ./VKernel/project.vpj --clear-cache --rebuild-includes'
if clean:
cmd += ' --clean'
os.system(cmd)
# project.vpj 的 linker 已改为直接产出 ELF(保留符号表,去掉 --oformat binary/--strip-all),
# kernel.bin 由同一份 ELF 二次处理得到 —— 两者布局逐字节一致,日志里的 rip 可直接用
# objdump / nm / readelf 落到函数名,不必再猜地址(此前自行 glob .obj 去链会得到不同布局)。
# --set-section-flags .bss=alloc,load,contents:把 .bss 一并写进裸镜像(ELF 里 .bss 默认
# NOBITS、不占文件空间),这样 bootloader 按“文件大小”预留就能覆盖 .bss。
kernel_elf = "VKernel/__vpcbuild__/output/kernel.elf"
kernel_bin = "VKernel/__vpcbuild__/output/kernel.bin"
rc = os.system(f'llvm-objcopy.exe -O binary --set-section-flags .bss=alloc,load,contents "{kernel_elf}" "{kernel_bin}"')
if rc != 0:
print("[build] 警告: objcopy 生成 kernel.bin 失败")
shutil.copy(kernel_bin, "./disk/sys/kernel/x86_64/kernel.bin")
def BuildApps():
"""编译库与应用,并拷贝到 disk/"""
# --noapp 优先级高于 --kernel-only:只编译内核,不编译所有库和应用
if noapp:
print("[build] --noapp: 跳过所有库与应用编译,复用已有缓存文件")
# 检查必要文件是否存在
required_libs = [
("Libs/SerialLogger/__vpcbuild__/output/slog.so", "./disk/libs/slog.so"),
("Libs/VPUI64/__vpcbuild__/output/vpui64.so", "./disk/libs/vpui64.so"),
]
required_apps = [
("Apps/HelloWorld/__vpcbuild__/output/helloworld.elf", "./disk/apps/hello.elf"),
("Apps/Terminal/__vpcbuild__/output/terminal.elf", "./disk/apps/terminal.elf"),
("Apps/Gargantua/__vpcbuild__/output/gargantua.elf", "./disk/apps/gargantua.elf"),
("Apps/Scene3D/__vpcbuild__/output/scene3d.elf", "./disk/apps/scene3d.elf"),
]
os.makedirs("./disk/apps", exist_ok=True)
os.makedirs("./disk/libs", exist_ok=True)
for src, dst in required_libs + required_apps:
if os.path.exists(src):
shutil.copy(src, dst)
else:
print(f"[build] 警告: {src} 不存在,请先完整构建一次")
elif kernel_only:
# 仅 --kernel-only:只跳过应用,不跳过库(原有逻辑保留)
print("[build] --kernel-only: 跳过其他应用和库的编译")
if not os.path.exists("./disk/apps/hello.elf"):
print("[build] 警告: hello.elf 不存在,请先不带 --kernel-only 完整构建一次")
else:
# 完整编译所有库+应用
so_cmd = r'..\..\ViperC\output\viperc.exe --project Libs/SerialLogger/project.vpj --clear-cache'
if clean:
so_cmd += ' --clean'
os.system(so_cmd)
vpui_cmd = r'..\..\ViperC\output\viperc.exe --project Libs/VPUI64/project.vpj --clear-cache'
if clean:
vpui_cmd += ' --clean'
os.system(vpui_cmd)
app_cmd = r'..\..\ViperC\output\viperc.exe --project Apps/HelloWorld/project.vpj --clear-cache'
if clean:
app_cmd += ' --clean'
os.system(app_cmd)
os.makedirs("./disk/apps", exist_ok=True)
shutil.copy("Apps/HelloWorld/__vpcbuild__/output/helloworld.elf", "./disk/apps/hello.elf")
term_cmd = r'..\..\ViperC\output\viperc.exe --project Apps/Terminal/project.vpj --clear-cache'
if clean:
term_cmd += ' --clean'
os.system(term_cmd)
shutil.copy("Apps/Terminal/__vpcbuild__/output/terminal.elf", "./disk/apps/terminal.elf")
garg_cmd = r'..\..\ViperC\output\viperc.exe --project Apps/Gargantua/project.vpj --clear-cache'
if clean:
garg_cmd += ' --clean'
os.system(garg_cmd)
shutil.copy("Apps/Gargantua/__vpcbuild__/output/gargantua.elf", "./disk/apps/gargantua.elf")
scene3d_cmd = r'..\..\ViperC\output\viperc.exe --project Apps/Scene3D/project.vpj --clear-cache'
if clean:
scene3d_cmd += ' --clean'
os.system(scene3d_cmd)
shutil.copy("Apps/Scene3D/__vpcbuild__/output/scene3d.elf", "./disk/apps/scene3d.elf")
os.makedirs("./disk/libs", exist_ok=True)
so_path = os.path.abspath("Libs/SerialLogger/__vpcbuild__/output/slog.so")
vpui_path = os.path.abspath("Libs/VPUI64/__vpcbuild__/output/vpui64.so")
if os.path.exists(so_path):
shutil.copy(so_path, "./disk/libs/slog.so")
if os.path.exists(vpui_path):
shutil.copy(vpui_path, "./disk/libs/vpui64.so")
def BuildBootloader():
"""编译 bootloader(GNU-EFI:MSYS make → WSL Ubuntu);
bootx64.efi 不由 viperc 产出,容易忘记重编而一直用旧引导器(改了 boot.c / 内核预留逻辑
却仍在用旧件)。显式传 --bootloader 才重编;未传时若 boot.c 比产物新则给出提醒。"""
boot_src = "boot/boot.c"
boot_out = "disk/EFI/BOOT/bootx64.efi"
if bootloader:
print("[build] building bootloader (make -C boot)...")
boot_rc = os.system('make -C boot')
if boot_rc != 0:
print("[build] 警告: bootloader 构建失败(需 MSYS make + WSL Ubuntu + _UNCADK/tools/gnu-efi)")
elif os.path.exists(boot_src) and os.path.exists(boot_out) and os.path.getmtime(boot_src) > os.path.getmtime(boot_out):
print("[build] 警告: boot/boot.c 比 disk/EFI/BOOT/bootx64.efi 新 → bootloader 已过期,请用 --bootloader 重编")
def BuildDisk():
"""把 disk/ 目录内容打包成 disk.img"""
os.system('powershell -ExecutionPolicy Bypass -File ./Scripts/disk.ps1')
def RunQemu():
"""清空 serial.log 并启动 QEMU,退出后打印串口日志"""
serial_log = os.path.abspath("serial.log")
if os.path.exists(serial_log):
os.remove(serial_log)
print(f"[build] CWD: {os.getcwd()}")
print(f"[build] serial_log: {serial_log}")
qemu_cmd = [
'qemu-system-x86_64',
'-pflash', r'D:\msys64\mingw64\share\qemu\edk2-x86_64-code.fd',
'-drive', 'file=disk.img,format=raw,if=ide',
'-m', '256m',
'-vga', 'std',
'-usb',
'-device', 'usb-kbd',
'-device', 'usb-mouse',
'-accel', 'tcg,tb-size=128',
'-serial', f'file:{serial_log}',
'-no-reboot'
]
print(f"[build] QEMU cmd: {' '.join(qemu_cmd)}")
proc = subprocess.Popen(qemu_cmd)
proc.wait()
print(f"[build] serial_log path: {serial_log}")
print(f"[build] file exists: {os.path.exists(serial_log)}")
if os.path.exists(serial_log):
with open(serial_log, "r", encoding="utf-8", errors="replace") as f:
content = f.read()
print(f"[build] serial_log size: {len(content)} bytes")
print(content)
else:
print("[build] serial.log not found!")
if only_run:
print("[build] --onlyrun: 跳过编译/打包,直接用现有 disk.img 启动 QEMU")
else:
BuildKernel()
BuildApps()
BuildBootloader()
BuildDisk()
RunQemu()