欢迎加入开源鸿蒙PC社区

欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
如有项目源码,可上传至 AtomGit 仓库,并在博文内附上仓库链接。

鸿蒙PC桌面端上的 x86_64-elf-grub 2.12:一次双层交叉工具链适配

这不是在鸿蒙PC上运行 x86_64 GRUB

本文介绍 GNU GRUB 2.12 在 HarmonyOS PC(AArch64)上的工具适配。包内的
x86_64-elf-grub-* 命令运行在鸿蒙PC宿主机,负责生成和检查 x86_64-elf/EFI 目标文件;它们不会在 AArch64 宿主上执行 x86_64 内核。把宿主架构、目标架构和最终验证位置分开,是阅读交叉编译日志的第一步。

配方和测试文件位于 AtomGit 仓库 中,源码来自 GNU GRUB 官方 2.12 发布包,目标工具依赖已经发布的 x86_64-elf-binutils/2.46.1。文章中的代码托管名称统一使用 AtomGit;上游下载地址仍以 GNU 官方发布页为准。

双层工具链如何拼起来

宿主编译器是 HarmonyOS PC 的 Clang/Clang++,用来构建 grub-mkimage、grub-file 等 AArch64 宿主工具。生成 GRUB 模块时,Clang 的 Linux driver 负责选择 GNU x86_64-elf-ld,cc1 再通过 -Xclang -triple x86_64-unknown-unknown-elf 恢复裸机语义。ld、objcopy、strip、nm 和 ranlib 均从 Conan 的 binutils package folder 取得绝对路径,不能依赖宿主 PATH 中恰好存在的同名命令。

这也是普通 CMake 交叉编译不能直接套用的原因:同一个构建同时需要 CC/CXX/BUILD_CC 的宿主工具和 TARGET_CC/TARGET_OBJCOPY/TARGET_STRIP/TARGET_NM/TARGET_RANLIB 的目标工具。配方还显式设置 --target=x86_64-elf、--with-platform=efi、--program-prefix=x86_64-elf-,并关闭 EFI 模拟器、主题、FUSE、device-mapper、ZFS 等与本次工具合同无关的功能。

HarmonyOS PC 上遇到的构建陷阱

GRUB 2.12 的 config.guess 不认识 HarmonyOS 的 uname 输出,config.sub 也不接受 linux-ohos 三元组。适配补丁只扩展脚本的识别和规范化规则,保持普通 Linux 路径不变。HMDFS 还会让 umask 077 创建的 configure 临时目录不可写,因此将两处临时目录权限改为 022,并在任务私有目录中运行 configure。

另一个容易忽略的问题是 noexec 行为。设备可以读取 config.status,但直接执行会返回 126;只改 shebang 没有用。最终规则统一写成 $(SHELL) config.status,并同时更新 Automake 输入和发布包自带的 Makefile.in。如果只改 .am,时间戳会触发设备上不存在的 automake-1.15;如果只改 .in,下一次重新生成又会丢掉修复。因此补丁顺序和生成文件的来源都必须记录。

make install 还有一个独立边界:设置 HELP2MAN=true 只是不生成手册,不会自动清空 man_MANS。本配方在安装阶段显式传入 man_MANS=,否则仍会寻找不存在的 grub-mkimage.1。这些问题属于构建系统和文件系统语义,不应通过伪造空手册页或忽略返回码来“修复”。

可运行的消费者脚本

下面是包内 test_package/test.sh 的可移植版本。它在鸿蒙PC宿主上执行已安装的宿主工具,完整覆盖六个流程和十条语义断言,同时验证生成的对象和 EFI 镜像格式。脚本不尝试启动 x86_64 镜像,因此不会把目标文件生成误报成目标系统启动成功。

#!/bin/sh
set -eu

package_root=${1:?package root is required}
assembler=${2:?x86_64-elf-as is required}
platform_dir="$package_root/lib/grub/x86_64-efi"
assertions=0

pass_assertion() {
    assertions=$((assertions + 1))
    printf '[assert %d/10] %s\n' "$assertions" "$1"
}

fail() {
    printf 'FAIL: %s\n' "$1" >&2
    exit 1
}

test -x "$assembler" || fail "assembler is not executable"
test -f multiboot.S || fail "multiboot.S is missing"
test -f grub.cfg || fail "grub.cfg is missing"
export PATH="$package_root/bin:$package_root/sbin:$PATH"

printf '[flow 1/6] recognize a valid Multiboot object\n'
"$assembler" -o multiboot.o multiboot.S
test -s multiboot.o || fail "assembler did not create multiboot.o"
pass_assertion "assembler creates a non-empty object"
x86_64-elf-grub-file --is-x86-multiboot multiboot.o
pass_assertion "grub-file recognizes the Multiboot object"

printf '[flow 2/6] reject invalid formats\n'
if x86_64-elf-grub-file --is-x86-multiboot grub.cfg; then
    fail "plain grub.cfg was recognized as Multiboot"
fi
pass_assertion "grub-file rejects plain text as Multiboot"
if x86_64-elf-grub-file --is-x86_64-efi multiboot.o; then
    fail "Multiboot object was recognized as EFI"
fi
pass_assertion "grub-file rejects the object as EFI"

printf '[flow 3/6] validate a GRUB configuration\n'
x86_64-elf-grub-script-check grub.cfg
pass_assertion "grub-script-check accepts grub.cfg"

printf '[flow 4/6] generate and identify an EFI image\n'
x86_64-elf-grub-mkimage \
    -d "$platform_dir" -O x86_64-efi -o grub-test.efi \
    -p /boot/grub normal echo
test -s grub-test.efi || fail "grub-mkimage produced an empty EFI image"
efi_size=$(wc -c < grub-test.efi)
printf 'EFI_IMAGE_SIZE=%s bytes\n' "$efi_size"
pass_assertion "grub-mkimage creates a non-empty EFI image"
x86_64-elf-grub-file --is-x86_64-efi grub-test.efi
pass_assertion "grub-file recognizes the EFI image"

printf '[flow 5/6] execute the packaged host binary\n'
version_output=$(x86_64-elf-grub-mkimage --version)
printf '%s\n' "$version_output"
case "$version_output" in
    *'(GRUB) 2.12'*) ;;
    *) fail "unexpected grub-mkimage version: $version_output" ;;
esac
pass_assertion "packaged host binary reports GRUB 2.12"

printf '[flow 6/6] environment block write/read round trip\n'
environment_file="$PWD/grubenv"
x86_64-elf-grub-editenv "$environment_file" create
test -s "$environment_file" || fail "grub-editenv did not create an environment block"
pass_assertion "grub-editenv creates an environment block"
x86_64-elf-grub-editenv "$environment_file" set harmony_pc=verified
environment_output=$(x86_64-elf-grub-editenv "$environment_file" list)
case "$environment_output" in
    *'harmony_pc=verified'*) ;;
    *) fail "grub-editenv did not persist harmony_pc=verified" ;;
esac
pass_assertion "grub-editenv persists and reads the value"

test "$assertions" -eq 10 || fail "expected 10 assertions, got $assertions"
printf 'PASS: 6 test flows, 10 assertions\n'

测试目录中的 multiboot.S 是一个只用于格式识别的对象,不会被脚本跳转执行:

.section .multiboot
.align 4
.long 0x1badb002
.long 0
.long -(0x1badb002 + 0)
.text
.global _start
_start:
    cli
1:
    hlt
    jmp 1b

实际包测试把包根作为第一个参数、binutils 提供的 x86_64-elf-as 作为第二个参数传入,并把 multiboot.S 与 grub.cfg 放在测试工作目录;脚本还会检查负例和 environment block 往返。若在自己的工程中复制脚本,请把两个参数和当前目录改成实际位置。这个版本只使用 POSIX sh 的 set -eu、case 和算术扩展,不依赖 pipefail,可避免把不支持 Bash 选项的 /system/bin/sh 误判为测试失败。

测试结果要分层看

源码中静态清点得到 92 个上游入口:5 个单元测试、81 个 GRUB/QEMU/文件系统集成测试和 6 个汇编检查。完整 92/92 需要 QEMU、文件系统工具和目标运行时,不能用一台 AArch64 鸿蒙PC宿主直接替代。正式配方执行 4 个自包含 C 单测,结果为 4/4;priority_queue_unit_test 因 Clang 15 与内置 gnulib 的 free() 异常规范冲突而没有纳入这组。

安装后消费者包含 6 个流程、10 条语义断言:Multiboot 正负识别、配置校验、EFI 生成和识别、版本输出、environment block 写入读取。归档中的 HarmonyOS PC 真机记录为上游子集 4/4、消费者 6/6、断言 10/10,产物审计 322 个文件且 AUDIT_FAILURES=0。这些数字证明了工具和包边界,不等于 GRUB 已在鸿蒙PC上引导 x86_64 操作系统,也不等于上游 92 项全部通过;它们也不替代任务 2977 对当前 MR 的独立上游测试回读。

目标产物还需要区分 ELF 类型:宿主命令是 AArch64 ELF,最终 .mod 是 x86-64,EFI 镜像是 PE32+ x86-64。审计时应检查宿主文件没有意外 RPATH,目标 kernel.exec 没有宿主解释器、系统 libc loader 或 Conan 缓存绝对路径。任何一项漂移都应停止发布并重新绑定当前候选。

新手适配教程:环境、过程、结论与 FAQ

环境:这是双层工具链问题

构建机需要 Conan 2、Autotools、Make、Python 和 HarmonyOS SDK,host profile 用于生成 AArch64 鸿蒙PC宿主工具;另一个 binutils 包提供 x86_64-elf-* 目标工具。也就是说,同一次构建既要让 Clang 在 HarmonyOS PC 上运行,又要让 ld、objcopy 和 grub-file 处理 x86_64-elf/EFI 文件。目标设备只运行宿主工具,不会在 AArch64 上直接启动 x86_64 内核。把这两个架构层次混淆,是新手最容易犯的错误。

过程:先验证格式,再验证工具闭包

  1. 先用 --build=never 检查精确的 x86_64-elf-binutils/2.46.1,并核对 GNU GRUB 源码摘要。
  2. 配置 --target=x86_64-elf、--with-platform=efi 和目标工具绝对路径,处理 config.guess、HMDFS 临时目录、noexec config.status 与手册安装清单。
  3. 用 multiboot.S 生成对象,验证正负格式识别;再检查 grub.cfg、EFI 镜像和 environment block 往返。
  4. 读取 grub-mkimage --version,确认 (GRUB) 2.12,并检查宿主 AArch64 ELF 与目标 x86-64/PE32+ 文件类型没有串位。
  5. 记录六个流程、十条断言、EFI 大小、包版本和设备桌面截图。不要把生成 EFI 文件理解为已经引导操作系统。

结论:难点是架构、工具和文件系统的交集

这个任务的高难性有明确技术依据:宿主和目标使用两套三元组,GRUB 的生成式构建同时依赖多个绝对路径工具,HarmonyOS PC 又引入 config.status noexec、HMDFS 临时目录和签名边界。PASS: 6 test flows, 10 assertions 说明消费者脚本完成了格式、配置、EFI 和 environment block 合同;它不等于 x86_64 内核已经在 AArch64 鸿蒙PC上启动,也不等于上游 92 个入口全部通过。结论必须按这条边界写,才经得起复核。

FAQ

Q:为什么在 AArch64 鸿蒙PC上适配 x86_64-elf 工具? A:工具运行在 AArch64 宿主上,产出供其他环境使用的 x86_64-elf/EFI 文件,两者不是同一件事。Q:为什么不直接执行 config.status? A:目标文件系统可能拒绝直接执行脚本,应通过 $(SHELL) 调用。Q:6/10 是不是只有六个测试? A:6 是流程数,10 是流程内语义断言,脚本会检查最终断言计数。Q:EFI 生成成功能否证明系统可启动? A:不能,还需要独立的固件、QEMU 或目标引导事务;本文只验证工具和文件格式。

运行截图

以下图片来自同一台 HUAWEI MateBook Pro(HAD-W32)的 HarmonyOS 6.1.0.117 图形会话。工具链身份图先回读 x86_64-elf-grub-mkimage (GRUB) 2.12,并统计包中的 16 个 GRUB 命令和 283 个 x86_64 EFI 平台模块:

GRUB 2.12 工具链身份与模块清点

六流程日志拆成两张图。前半段展示 Multiboot 正向识别、无效格式拒绝和配置语法检查,并继续进入 EFI 生成流程:

GRUB 消费者测试流程 1 至 3

后半段展示 EFI 生成与识别、宿主工具版本、512000 字节镜像、环境块往返以及最终 PASS: 6 test flows, 10 assertions:

GRUB 消费者测试流程 4 至 6

最后一张是六个流程的完整桌面汇总,保留鸿蒙PC设置页、HiShell 和任务栏。

鸿蒙PC桌面端 GRUB 六流程完整运行汇总

这组截图把工具身份、前半流程、后半流程和完整桌面汇总分开,便于读者逐条核对。它证明的是“鸿蒙PC 主机上的跨架构制品工具链”可运行,不等同于已经在该电脑上启动了 x86_64 固件。

结语

x86_64-elf-grub 2.12 的鸿蒙PC桌面端适配,核心是把“宿主工具能运行”和“目标文件格式正确”拆成两个可验证命题,再用 Conan 明确提供目标链接工具。对 config.guess、HMDFS 临时目录、noexec 的 config.status、手册安装清单和双层三元组逐项处理,才能得到可复现的 EFI 生成工具。后续若要增加真实启动测试,应另建具备 QEMU 或目标固件的验证事务,不要把本文的消费者结果扩大解释。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐