在 HarmonyOS 上从源码构建 QEMU 11.1.1(qemu-system-aarch64 / x86_64 / qemu-img):完整记录
在 HarmonyOS 上从源码构建 QEMU 11.1.1(qemu-system-aarch64 / x86_64 / qemu-img):完整记录
本文涉及到的全部工作以及本文的撰写全部由 AI 完成。
本文记录在 MateBook Pro S(HarmonyOS 7,aarch64,musl)上,用自建的
clang-22+ Harmonybrew 的 GNUbinutils,从qemu-11.1.1源码把
qemu-system-aarch64、qemu-system-x86_64、qemu-img、qemu-io
构建出来,并跑通--version/-machine help/ TCG 启动 /qemu-img
建盘读盘 的全过程。
(aarch64-linux-user的qemu-aarch64试过,但撞上一串 musl 兼容问题,
见 §9,暂未完成。)与 LLVM / GCC 两份记录一样,绝大多数坑来自「HarmonyOS 看起来像 Linux、
又不完全是」:uname -s返回HarmonyOS、/tmp只读、ELF 必须签名、
musl + Bionic 风格的内核 UAPI 头文件跟主线 Linux 头对不齐。
0. 结论先说
- 构建目录:
qemu-11.1.1/build(out-of-tree,就在源码树里,hmdfs 上) - 宿主编译器:
~/Installed/clang-22/bin/clang{,++}(clang 22.1.8)- 为什么不用 Harmonybrew 自带的 clang 15:见 §4,clang 15 对 C 里
const数组元素用作静态初始化器过严(报initializer element is not a compile-time constant),而 clang 22 接受。此外 clang-22 的 config
文件里已经默认带了--sysroot和-D__MUSL__,省事。
- 为什么不用 Harmonybrew 自带的 clang 15:见 §4,clang 15 对 C 里
- 汇编/归档/反汇编等工具:Harmonybrew 的 GNU
binutils2.47
(~/.harmonybrew/opt/binutils/bin/下的ar/nm/objcopy/strip/
readelf/objdump/ranlib),因为 clang-22 自带的llvm-ar等
没签名、不能执行。 - 链接器:仍是 SDK 的
ld.lld(PATH 里那个会自动--code-sign的 wrapper),
所以编出来的 QEMU 二进制自动自签名。 - 目标:
aarch64-softmmu、x86_64-softmmu(已建成;aarch64-linux-user需更多 musl 补丁,见 §9) - 必须关掉的东西:
vhost_*全家桶(§3) - 必须补的宏:
-D__MUSL__ -D__user= -D__force=(§4) - 必须补的链接路径:
LIBRARY_PATH=~/.harmonybrew/lib+-Wl,-rpath,...
(§5)
1. 前置条件
- 依赖库(Harmonybrew 已装):
glib-2.02.88.3、pixman-10.46.4、zlib1.3.1、ncursesw、
zstd、curl等。QEMU 硬依赖 glib;pixman 做显示;zlib 做压缩。- 缺的(SDL/gtk/gnutls/libpng/libjpeg/…) 会被 configure 自动探测并禁用,
不影响无头(-nographic/-display none)运行。
- 构建工具:
meson1.12.0、ninja1.13.2、python33.14。 - libfdt(dtc):QEMU 的 ARM virt 等机器硬依赖 libfdt,但 release tarball
里只有subprojects/dtc.wrap(指向 gitlab),没有源码。需要手动 clone 到
subprojects/dtc并 checkout 到 wrap 里钉住的 revision:
cd qemu-11.1.1/subprojects
git clone https://gitlab.com/qemu-project/dtc.git dtc
cd dtc
git fetch --depth 1 origin b6910bec11614980a21e46fbccc35934b671bd81
git checkout b6910bec11614980a21e46fbccc35934b671bd81 # v1.6.1
2. configure 阶段的坑:mkvenv 要从 PyPI 装 Python 依赖
QEMU 的 configure 会用 python/scripts/mkvenv.py 建一个「非隔离」venv 并
pip install 若干 Python 包(pycotap、qemu.qmp、setuptools、wheel、
pip)。其中 pycotap/qemu.qmp 在 python/wheels/ 里有 vendored wheel,
但 setuptools 没有 vendored,要现从 PyPI 下。这台机器连 PyPI 时好时坏
(ConnectionResetError: Connection reset by peer),configure 会卡住/失败。
对策(一次性):把缺的包装进系统 Python(非隔离 venv 会继承系统包,
mkvenv 识别为「系统包」后就不再联网安装):
# setuptools 已缓存在 pip cache 里,离线装;没有就先用 pip download 拉到本地
SETW=$(python3 -m pip cache list --format=abspath | grep setuptools | head -1)
python3 -m pip install --break-system-packages --no-index "$SETW"
# 下面两个用 QEMU 自带的 vendored wheel,离线装
W=~/ProjectSources/qemu-11.1.1/python/wheels
python3 -m pip install --break-system-packages --no-index --no-deps \
"$W/qemu_qmp-0.0.6-py3-none-any.whl" "$W/pycotap-1.3.1-py3-none-any.whl"
需要
--break-system-packages是因为 Harmonybrew 的 Python 是 PEP 668
「externally managed」环境。
3. 坑:OHOS sysroot 的 UAPI 头用 Bionic 风格 _UAPI_ 保护宏,vhost 撞车
OHOS sysroot(Bionic 内核头)的 linux/virtio_*.h / linux/vhost_*.h 用
_UAPI_LINUX_* 保护宏,而 QEMU 自带的 standard-headers/ 用主线 Linux 的
_LINUX_* 保护宏。两套都 #include 进来时保护宏对不上 → 结构体重复定义。
libvhost-user / libvduse 子项目,以及 vhost-kernel.c / vhost-vdpa.c /
net/vhost-vdpa.c 等文件,会同时 #include <linux/vhost.h>(sysroot)和
standard-headers/linux/virtio_ring.h(QEMU),于是报:
error: redefinition of 'vring_packed_desc_event'
note: previous definition is here .../sysroot/usr/include/linux/virtio_ring.h
对策:直接禁用 vhost 全家桶。这台机器没有 /dev/kvm、/dev/vhost-*,
vhost-kernel / vhost-vdpa / vhost-net 本来就没用;vhost-user 服务端 / vduse
是冷门后端。禁用不影响 TCG 纯软件模拟、virtio 设备、-machine virt 等:
-Dvhost_user=disabled -Dvhost_kernel=disabled -Dvhost_net=disabled \
-Dvhost_vdpa=disabled -Dvhost_crypto=disabled \
-Dvhost_user_blk_server=disabled -Dlibvduse=disabled -Dvduse_blk_export=disabled
4. 坑:musl 头期望 __MUSL__,Bionic 头漏定义 __user/__force
4.1 -D__MUSL__
musl 的 sys/socket.h 里 struct sockaddr_storage 是用 #ifndef __MUSL__
包起来的——即这个 sysroot 期望编译时已定义 __MUSL__(此时 sockaddr_storage
由 linux/socket.h 提供,避免与 sys/socket.h 重复)。Harmonybrew 的 clang 15
不会自动定义 __MUSL__(自建的 clang-22 的 cfg 里带了)。漏掉它,
net/can/can_socketcan.c 编译时报:
error: redefinition of 'sockaddr_storage' (linux/socket.h vs sys/socket.h)
对策:--extra-cflags="-D__MUSL__ ..."。(其实 clang-22 的 cfg 已带,
这里显式再写一遍更保险。)
4.2 -D__user= -D__force=
Bionic 的 linux/keyctl.h 里用了内核注解 __user,但它只 #include <linux/types.h>,而定义 __user/__force 的 linux/compiler.h 没被带进来
(Bionic 的 types.h 不 include compiler.h)。于是 crypto/secret_keyring.c
编译报:
error: expected ';' at end of declaration list char __user * hashname;
对策:全局补 -D__user= -D__force=(它们在用户态本来就该展开为空)。
4.3 clang 15 过严 → 换 clang-22
Harmonybrew 的 clang 15.0.4 对 C 里「static const 数组元素访问用作静态
初始化器」报错(C 标准里 const 变量本就不是编译期常量,GCC 和较新的 clang
都当扩展接受,clang 15 这里更严):
static const struct { hwaddr addr; ... } memmap[] = { ... };
static const struct { hwaddr addr; ... } t[] = { { memmap[IDX].addr, ... } };
// clang 15: error: initializer element is not a compile-time constant
hw/arm/fsl-imx8mm.c / fsl-imx8mp.c 等会踩中。对策:换用自建的
clang-22(22.1.8,接受该写法),这也是本机已验证过的编译器(之前编过
GCC 16)。换编译器后,ar/nm 等要用 GNU binutils(clang-22 自带的
llvm 工具没签名跑不了)。
5. 坑:QEMU 链接/运行都要能找到 Harmonybrew 的库
QEMU 依赖 Harmonybrew Cellar 里的 libglib-2.0.so / libpixman-1.so /
libintl.so 等。glib 的 pkg-config 输出里带 -lintl,但 libintl 在
gettext 的 Cellar 目录,不在 SDK lld 的默认搜索路径里(直接链接会报
unable to find library -lintl)。而且这些 .so 的所在目录
~/.harmonybrew/lib 也不在 musl loader 的默认搜索路径(/etc/ld-musl-aarch64.path)。
对策两条:
- 链接期:
export LIBRARY_PATH=/storage/Users/currentUser/.harmonybrew/lib
(~/.harmonybrew/lib下有指向各 Cellar 包的软链,clang 会把 LIBRARY_PATH
转成-L)。 - 运行期:
--extra-ldflags="-Wl,-rpath,/storage/Users/currentUser/.harmonybrew/lib"
把该目录写进最终二进制的 RUNPATH。
6. 完整构建命令
cd /storage/Users/currentUser/ProjectSources/qemu-11.1.1
mkdir -p build && cd build
BP=/storage/Users/currentUser/.harmonybrew/opt/binutils/bin
HB=/storage/Users/currentUser/.harmonybrew/lib
export PATH="$BP:$PATH"
export LIBRARY_PATH="$HB"
export CC=/storage/Users/currentUser/Installed/clang-22/bin/clang
export CXX=/storage/Users/currentUser/Installed/clang-22/bin/clang++
export AR="$BP/ar" NM="$BP/nm" OBJCOPY="$BP/objcopy" STRIP="$BP/strip" \
READELF="$BP/readelf" OBJDUMP="$BP/objdump" RANLIB="$BP/ranlib"
../configure \
--target-list=aarch64-softmmu,x86_64-softmmu \
--disable-werror \
--disable-strip \
--disable-docs \
--disable-guest-agent \
--extra-cflags="-D__MUSL__ -D__user= -D__force=" \
--extra-ldflags="-Wl,-rpath,$HB" \
-Dvhost_user=disabled -Dvhost_kernel=disabled -Dvhost_net=disabled \
-Dvhost_vdpa=disabled -Dvhost_crypto=disabled \
-Dvhost_user_blk_server=disabled -Dlibvduse=disabled -Dvduse_blk_export=disabled
ninja -j4
逐参数说明:
| 参数 | 作用 |
|---|---|
CC/CXX=...clang-22 | 用自建 clang 22(避免 clang 15 的 constexpr 过严;cfg 已带 sysroot/-D__MUSL__) |
AR/NM/...=GNU binutils | clang-22 自带的 llvm 工具没签名不能执行,用 GNU binutils |
LIBRARY_PATH=.../harmonybrew/lib | 链接期能找到 -lintl 等 Harmonybrew 库 |
--extra-ldflags="-Wl,-rpath,..." | 运行期能从 RUNPATH 找到 Harmonybrew 的 .so |
--extra-cflags="-D__MUSL__ -D__user= -D__force=" | 对齐 OHOS musl/Bionic 头文件的期望(§4) |
-Dvhost_*=disabled 等 | 避开 Bionic _UAPI_ 头与 QEMU standard-headers 的冲突(§3) |
--disable-werror | 首编不把警告当错误,减少摩擦 |
--disable-strip | 关键:不 strip,避免把链接器自动加上的签名剥掉 |
--disable-docs / --disable-guest-agent | 省掉 sphinx / guest-agent 等额外依赖 |
7. 验证结果
$ ./qemu-system-aarch64 --version
QEMU emulator version 11.1.1
$ ./qemu-system-aarch64 -accel help
Accelerators supported in QEMU binary: nitro kvm tcg
$ ./qemu-system-aarch64 -machine help # 列出 virt 等 ARM 机器
$ ./qemu-system-aarch64 -M virt -cpu cortex-a72 -accel tcg -display none -nographic -S
# TCG 初始化成功,机器保持运行
$ ./qemu-img create -f qcow2 t.qcow2 1M # 建盘
$ ./qemu-img info t.qcow2 # 读回(qcow2/块层正常)
$ ./qemu-io -f qcow2 -c 'read -P 0 512' t.qcow2 # 读 512 字节
二进制签名验证(应为 self-sign,链接器自动签的):
binary-sign-tool display-sign -inFile qemu-system-aarch64
# ... code signature is self-sign
8. 踩坑速查
| 现象 | 原因 | 解法 |
|---|---|---|
unable to find library -lintl | Harmonybrew 库不在 SDK lld 默认搜索路径 | export LIBRARY_PATH=~/.harmonybrew/lib |
运行报 Error loading shared library libglib-2.0.so.0 | Harmonybrew lib 不在 loader 默认路径 | --extra-ldflags="-Wl,-rpath,~/.harmonybrew/lib" |
redefinition of 'vring_packed_desc_event' | Bionic _UAPI_ 头 vs QEMU _LINUX_ 头 | 禁用 vhost_* 全家桶 |
redefinition of 'sockaddr_storage' | 漏 -D__MUSL__,musl sys/socket.h 与 linux/socket.h 都定义 | -D__MUSL__ |
expected ';' ... char __user * hashname | Bionic keyctl.h 用 __user 但没 include compiler.h | -D__user= -D__force= |
initializer element is not a compile-time constant | clang 15 对 const 数组元素作静态初始化过严 | 换 clang-22 |
| configure 卡/死在 mkvenv pip | 要从 PyPI 装 setuptools 等,网络抖 | 把 setuptools/qemu.qmp/pycotap 离线装进系统 Python |
编译产物不能执行(Permission denied) | 签名被 strip 掉 | --disable-strip;或 strip 后 binary-sign-tool sign -selfSign 1 补签 |
| 找不到 libfdt / dtc | tarball 只带 .wrap 无源码 | 手动 git clone dtc 到 subprojects/dtc |
9. linux-user(qemu-aarch64)暂未建成:musl 兼容补丁清单
system 模式(*-softmmu)顺利建成,但 aarch64-linux-user 的 qemu-aarch64
撞上一串 musl 特有兼容问题(linux-user 直接面向 libc 的 syscall 面,在 musl 上
本就不受良好支持)。已修/待修的清单如下,想继续可照着逐个补:
已修(打在源码里):
linux-user/signal.c:musl 没有 glibc 扩展sigorset(),补了一个
#ifndef __GLIBC__的 static inline 实现。linux-user/mmap.c:OHOS 头没有MADV_POPULATE_READ/MADV_POPULATE_WRITE
(Linux 5.14+),补了#define(值 22/23,与 aarch64 generic 一致)。
待修(linux-user/syscall.c 等):
| 现象 | 原因 | 方向 |
|---|---|---|
redefinition of 'sysinfo' | linux/sysinfo.h 与 sys/sysinfo.h 各定义一次(Bionic 头) | 对齐保护宏 / 只 include 一边 |
no member named '_sigev_un' in 'struct sigevent' | musl 的 sigevent 布局与 glibc 不同 | 适配 musl 的字段名(_pad/_sigev_un 差异) |
undeclared function 'vhangup' | vhangup 是 glibc 函数,musl 没有 | 补 stub 或 #ifdef 掉 |
undeclared function 'mq_open/mq_unlink/mq_setattr/mq_getattr' | POSIX 消息队列,OHOS sysroot 的 mqueue.h 没暴露 | 打开对应 feature 宏 / 补声明或 stub |
10. 写在最后
QEMU 的 configure 用 __linux__ 宏(编译探测)而非 uname -s 判断 host OS,
所以 HarmonyOS 直接被当成 Linux 原生构建,这一点比 LLVM 的 config.guess 友好。
真正的坑集中在 OHOS 的 Bionic 风格内核 UAPI 头与主线 Linux 头对不齐
(_UAPI_ vs _LINUX_ 保护宏、__user 漏定义、sockaddr_storage 的 __MUSL__
约定),以及 Harmonybrew 库的链接/运行搜索路径。理顺这几条后,构建本身
反而很顺——一条 configure + 一条 ninja 就出二进制了。
后续如果想继续折腾:把 vhost-user(客户端)用「改 sysroot 保护宏」的方式重新
启用、把 §9 的 linux-user musl 补丁补齐编出 qemu-aarch64 / qemu-x86_64、
或者真机引导一个最小 aarch64 内核 + initramfs,思路相通:先对齐 OHOS
头文件/路径约定,再按 feature 分层推进。
更多推荐
所有评论(0)