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 # --onlyapp:只编译库与应用(跳过内核),用于只改应用/库时的快速迭代。 only_app = '--onlyapp' 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: if only_app: print("[build] --onlyapp: 只编译库与应用,跳过内核") else: BuildKernel() BuildApps() BuildBootloader() BuildDisk() RunQemu()