在 HarmonyOS 上从源码构建 QEMU 11.1.1(qemu-system-aarch64 / x86_64 / qemu-img):完整记录

本文涉及到的全部工作以及本文的撰写全部由 AI 完成。

本文记录在 MateBook Pro S(HarmonyOS 7,aarch64,musl)上,用自建的
clang-22 + Harmonybrew 的 GNU binutils,从 qemu-11.1.1 源码把
qemu-system-aarch64qemu-system-x86_64qemu-imgqemu-io
构建出来,并跑通 --version / -machine help / TCG 启动 / qemu-img
建盘读盘 的全过程。
aarch64-linux-userqemu-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 的 GNU binutils 2.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-softmmux86_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.0 2.88.3、pixman-1 0.46.4、zlib 1.3.1、ncursesw
      zstdcurl 等。QEMU 硬依赖 glib;pixman 做显示;zlib 做压缩。
    • 缺的(SDL/gtk/gnutls/libpng/libjpeg/…) 会被 configure 自动探测并禁用,
      不影响无头(-nographic / -display none)运行。
  • 构建工具:meson 1.12.0、ninja 1.13.2、python3 3.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 包(pycotapqemu.qmpsetuptoolswheel
pip)。其中 pycotap/qemu.qmppython/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.hstruct 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/__forcelinux/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 binutilsclang-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 -lintlHarmonybrew 库不在 SDK lld 默认搜索路径export LIBRARY_PATH=~/.harmonybrew/lib
运行报 Error loading shared library libglib-2.0.so.0Harmonybrew 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 * hashnameBionic keyctl.h 用 __user 但没 include compiler.h-D__user= -D__force=
initializer element is not a compile-time constantclang 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 / dtctarball 只带 .wrap 无源码手动 git clone dtc 到 subprojects/dtc

9. linux-user(qemu-aarch64)暂未建成:musl 兼容补丁清单

system 模式(*-softmmu)顺利建成,但 aarch64-linux-userqemu-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.hsys/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 分层推进。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐