Harmony PC 开发者社区

欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper/


欢迎在PC社区平台申请新建项目:

OpenHarmony PC Developer - 开源代码托管,代码协作 - AtomGit

本文记录 LLVM LLD 15.0.4 在 HarmonyOS PC AArch64 上的适配与真机消费者验证。

一、交付结果

LLD 是 LLVM 项目中的高性能链接器。它并非一个独立的小型库,而是 LLVM monorepo 的工具链组件,构建和测试横跨 CMake 平台判断、多个目标后端、lit 测试框架,以及 ELF、COFF、Mach-O、WebAssembly 等测试矩阵。

项目内容
上游项目LLVM Project
上游版本15.0.4
上游地址GitHub - llvm/llvm-project: The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. · GitHub
目标平台HarmonyOS PC / AArch64
交付分支feat/lld-15.0.4
合入 PRMR #9167
交付提交705d16b8e9a2e1854b8a4bdeb049830b839c605c

图 1:LLD 15.0.4 适配 PR 已合入,页面同时展示上游信息、测试统计和评审状态。

本文不把“能编译”当作适配完成。一个链接器制品至少要经过四层验证:源码能在目标工具链下构建、上游测试按平台边界分类、Conan 包能从制品仓取得、包内程序能在真机被下游实际调用。后文的五张图片正好对应这四层证据。

二、从零理解 LLD、LLVM 与 Conan 的关系

初次接触这个项目时,最容易误解的一点是:LLD 并不是单独下载一个小仓库后直接 make 的工具。它位于 llvm-project 单仓中,依赖 LLVM 的公共构建框架和工具组件。可以把本次交付拆为三层:

LLVM monorepo 源码
  -> CMake/Ninja 构建出 LLD 系列二进制
  -> Conan 配方将 bin/ 下程序打包为 lld/15.0.4
  -> 下游在 HarmonyOS PC 下载并调用包内 ld.lld

其中,lld 是通用驱动入口,ld.lld 是 GNU 风格链接器入口;还可能包含 lld-link、ld64.lld、wasm-ld 等不同目标格式的入口。本文真机消费者选用 ld.lld,因为它适合验证 AArch64 ELF 链接链路。

对于初学者,建议先记住下面三个概念:

概念本文中的含义不能混淆的对象
上游源码LLVM 15.0.4 的官方 llvm-project 源码Conan 缓存中的已打包制品
SDK 工具系统已安装的 clang、lld 等工具本次 LLD PR 产生的包内二进制
Conan 制品ohpcd 远端的 lld/15.0.4 二进制包本地源码构建目录中的临时 ld.lld

后续验证始终用路径来区分它们:只有 .conan2/p/.../bin/ld.lld 才是本次从制品仓下载的 LLD。

三、为什么这是高难度适配

这次工作不是把 CC 改成 clang 后重新编译即可完成,主要有四类困难:

上游假设HarmonyOS PC 上的实际差异处理方式
平台识别覆盖既有 Unix 系统aarch64:HarmonyOS 未被完整识别补充 config.guess 和 LLVM CMake 平台分类
测试可使用常规管道行为grep -q 提前退出可能使写端收到 SIGPIPE使用 FileCheck 代替依赖提前关闭的管道检查
Python 脚本保留 Python 2 除法语义Python 3 的 / 返回浮点数将随机边界计算改为 // 整数除法
POSIX 权限位行为稳定HMDFS 的 chmod u-w 语义不同将已证实无法稳定复现的权限用例按边界处理,XPASS 仍保留阻断能力

具体修改包括:

  1. 增加 HarmonyOS AArch64 平台识别,使配置阶段获得正确目标信息。
  2. 在 HandleLLVMOptions 中补充 CMAKE_SYSTEM_NAME=HarmonyOS 分类,使编译和链接选项进入正确的 Unix-like 分支。
  3. 将 Mach-O dead-strip 测试中的 grep -q 管道检查转换为 FileCheck,消除 OHOS 上的管道提前关闭干扰。
  4. 将 random.randint(0, frame_size / 16 - 4) 修正为 random.randint(0, frame_size // 16 - 4),解决 Python 3 的 TypeError。

3.1 平台识别为什么必须优先解决

大型 CMake 项目不会只在一个位置判断操作系统。config.guess 用于识别构建环境,CMake 的 CMAKE_SYSTEM_NAME 又决定编译选项、链接选项和条件测试。若只修改其中一个位置,可能出现“配置成功但编译参数不完整”或“能构建但测试条件走错分支”的假成功。

本次在两个层面补齐 HarmonyOS:首先让 AArch64 HarmonyOS 环境能够被稳定识别;再让 LLVM 的平台选项逻辑把 HarmonyOS 纳入 Unix-like 分类。这样做的目标不是修改上游行为,而是把原本未覆盖的平台显式纳入已有的合理分支。

3.2 为什么一个 Python 除法也会阻塞链接器适配

LLD 自身主要由 C++ 实现,但测试和辅助生成工具会调用 Python。Python 2 中整数相除返回整数,Python 3 中 / 返回浮点数。因此下面代码在 Python 3 中会把 float 传给需要整数范围的 random.randint:

random.randint(0, frame_size / 16 - 4)

正确的修复不是把异常吞掉,也不是临时降低 Python 版本,而是明确表达整数语义:

random.randint(0, frame_size // 16 - 4)

这类问题的价值在于它可复用:任何历史脚本中“数量、索引、对齐、页数、随机边界”一类的除法,都应先检查它是否依赖 Python 2 整数语义。

3.3 HMDFS 权限差异如何处理

某些上游 LTO 用例通过 chmod u-w 让文件变为不可写,再断言链接器或相关工具产生 Permission denied。这实际验证的是底层文件系统的权限语义,而不完全是 LLD 算法。

HarmonyOS PC 的 HMDFS 环境中,权限位行为与常规 Linux 本地文件系统不完全一致。正确处理流程是先做最小权限实验,再将无法稳定复现的用例单独记录为平台边界;不能将其删除,也不能把“没有触发预期权限错误”伪装成 LLD 功能通过。若以后环境改变导致预期失败意外通过,XPASS 仍应作为回归信号保留。

四、HarmonyOS PC 真机环境

真机不是服务器交叉编译截图。下图可见 HarmonyOS PC 桌面、终端窗口、HarmonyOS 内核标识、aarch64 架构和 Conan 版本。

图 2:HarmonyOS PC 真机环境,uname -a、uname -m 和 conan --version 分别确认系统、AArch64 架构和 Conan 2.29.1。

4.1 最小环境检查

开始前应在真机终端检查以下项目,而不是假定开发机、服务器和真机环境一致:

uname -a
uname -m
conan --version
command -v clang
command -v binary-sign-tool

本次交付对应的包设置是 OHOS / armv8 / Release / clang 15 / C++17 / libc++。这组设置不仅描述编译器版本,也决定 Conan 会选择哪个二进制 package ID。只要 C++ 标准或 C++ 运行库不同,Conan 就会把它当作不同的二进制兼容配置。

4.2 构建阶段的关键约束

LLD 15.0.4 的构建入口是 CMake 和 Ninja。适配时只启用本次需要的 LLD 项目,避免把完整 LLVM 工具链、文档和不相关绑定全部带入构建闭包。配置的核心含义如下:

LLVM_ENABLE_PROJECTS=lld           只构建 LLD 项目
LLVM_INCLUDE_TESTS=ON              保留 LLD lit 测试入口
LLVM_BUILD_TESTS=OFF               不额外构建 LLVM C++ 单元测试目标
LLVM_DEFAULT_TARGET_TRIPLE=...     设定 OHOS AArch64 默认目标
LLVM_PARALLEL_LINK_JOBS=2          限制大对象链接时的内存峰值

LLVM_BUILD_TESTS=OFF 并不表示“没有测试”。LLD 的主验证入口是 check-lld 对应的 lit 测试,两者属于不同层次。把它们混为一谈,是解释 LLVM 测试结果时最常见的错误之一。

五、从 ohpcd 制品仓下载 LLD 制品

先在真机下载已经发布的 LLD 制品,而不是运行 SDK 目录中同名的 ld.lld:

conan download "lld/15.0.4:*" -r=ohpcd

下载成功后,Conan 返回完整的 recipe revision、package ID 和 package revision。该制品的目标设置为 os=OHOS、arch=armv8、compiler=clang、compiler.version=15、compiler.cppstd=17、compiler.libcxx=libc++。

conan cache path "lld/15.0.4#<recipe_revision>:<package_id>#<package_revision>"

图 3:真机从 ohpcd 下载 86.1 MB 的 LLD 制品,包安装完成后定位到本地 Conan 缓存。

5.1 如何读懂下载输出

下载输出中有三段标识,建议在问题排查时完整保留:

lld/15.0.4#<recipe_revision>
  :<package_id>
  #<package_revision>
  • recipe_revision 标识配方版本;
  • package_id 由 profile 的设置和选项计算得到;
  • package_revision 标识该二进制包的修订版本。

图 3 中的包 ID 是 6b129b068851896169fae42d399fa922d239ce8a。它不是随意的缓存目录名,而是与 OHOS AArch64、clang 15、C++17、libc++ 配置绑定的二进制身份。

5.2 为什么必须用 download 而不是只看仓库网页

网页只能说明项目存在,不能说明当前 HarmonyOS PC 能否拿到目标架构和 ABI 对应的包。conan download 真实执行了远端获取、包解压和本地安装;图 3 中的 Package installed、下载体积和本地缓存路径共同证明制品可获得。

如果命令报 Recipe not found,说明远端未发布配方;如果显示 Missing binary,说明配方存在但当前 profile 没有匹配包。两类失败的处理方向完全不同,不能混在一起。

六、以消费者运行环境调用制品中的 LLD

下载成功不等于消费者可用。必须使用与制品一致的 host settings 生成 Conan 运行环境,避免默认 profile 选择到不同的 C++ ABI 包:

conan install --requires=lld/15.0.4 -r=ohpcd --build=never \
  -s:h=arch=armv8 \
  -s:h=build_type=Release \
  -s:h=compiler=clang \
  -s:h=compiler.version=15 \
  -s:h=compiler.cppstd=17 \
  -s:h=compiler.libcxx=libc++ \
  -s:h=os=OHOS \
  -s:h=os.version=6.0 \
  -g VirtualRunEnv -of=.

. ./conanrun.sh
command -v ld.lld
ld.lld --version

关键检查是 command -v ld.lld 必须指向 .conan2/p/.../bin/ld.lld,而不是 SDK 的 /data/service/hnp/bin/ld.lld。

图 4:VirtualRunEnv 生成成功,ld.lld 指向 Conan 缓存内的制品并正确输出 LLD 15.0.4。

6.1 这一步曾遇到的真实失败

直接执行 conan install --requires=lld/15.0.4 时,真机的默认 profile 使用了 compiler.cppstd=gnu14 和 compiler.libcxx=libstdc++11。Conan 因此计算出一个不同的 package ID,并提示 No compatible configuration found。

这不是制品仓下载失败,也不是 LLD 不能在鸿蒙上运行,而是消费者请求的 ABI 与已发布制品不一致。显式传入 cppstd=17 与 libc++ 后,Conan 命中图 3 下载的包,生成 conanrun.sh,随后 ld.lld --version 成功输出 LLD 15.0.4。

这个案例说明:遇到 Missing binary 时,先对比“远端包的 settings”和“当前 profile 的 settings”,不要立刻使用 --build=missing。后者会把本应验证远端制品的问题变成一次本地重编译,失去制品消费验证的意义。

6.2 如何确认没有误用 SDK 自带 LLD

设备 SDK 同时提供 /data/service/hnp/bin/ld.lld。一开始若只运行 command -v ld.lld,很容易命中 SDK 程序;该程序还可能因自己的动态库环境而无法加载 libxml2.so.16。这既不能证明本 PR 的制品正确,也不能作为本次 LLD 的失败结论。

正确顺序是:先执行 . ./conanrun.sh,再检查 command -v ld.lld;只有路径落在 Conan 缓存中的包目录,才继续运行版本命令和消费者链接。

七、最小消费者链接、签名和真机运行

为证明制品中的 LLD 确实参与了下游链接,创建最小 C 消费者程序,并将 Conan 包内的 ld.lld 绝对路径显式传给 clang:

LLD_PKG="$(conan cache path "lld/15.0.4#<recipe_revision>:<package_id>#<package_revision>")"
LLD_BIN="$LLD_PKG/bin/ld.lld"

clang -fuse-ld="$LLD_BIN" ./lld_consumer.c -o ./lld_consumer
/data/service/hnp/bin/readelf -h ./lld_consumer | grep -E 'Class:|Machine:'

/data/service/hnp/bin/binary-sign-tool sign \
  -inFile ./lld_consumer \
  -outFile ./lld_consumer \
  -selfSign 1

./lld_consumer
printf 'consumer_exit=%s\n' "$?"

这里有一个容易踩到的环境问题:SDK clang 在仅使用 -fuse-ld=lld 时,可能优先调用 SDK 目录自身的链接器,而不是当前 PATH 中的 Conan 制品。因此本验证使用 -fuse-ld="$LLD_BIN" 显式锁定包内链接器。

图 5:消费者程序由 Conan 制品内的 ld.lld 链接,readelf 确认 ELF64 / AArch64;自签名成功后输出 LLD Conan consumer: PASS,退出码为 0。

7.1 消费者源码为什么要足够小

最小消费者只调用 puts 并返回 0。它不用于证明复杂 C 运行库功能,而是为了把结论收敛到一条可观察链路:C 源码被 clang 编译,clang 把链接阶段交给指定的 Conan ld.lld,最终产物是 AArch64 ELF,经签名后能在真机运行。

#include <stdio.h>

int main(void) {
    puts("LLD Conan consumer: PASS");
    return 0;
}

使用最小输入的好处是,若失败可以立即区分为“编译器/链接器/系统运行时/签名”哪一层的问题,而不会被业务逻辑、第三方库或网络条件干扰。

7.2 为什么传递绝对 linker 路径

clang -fuse-ld=lld 看起来合理,但 SDK clang 的工具查找逻辑可能优先定位 SDK 安装目录中的 lld,即使当前 shell 的 PATH 已经由 Conan 更新。实际验证中这会命中 SDK 自带的、运行时依赖不完整的 linker。

因此使用:

clang -fuse-ld="$LLD_BIN" ./lld_consumer.c -o ./lld_consumer

$LLD_BIN 是通过完整 Conan 包引用计算出的 .../.conan2/p/.../bin/ld.lld。这使链接器来源可审计,也避免同名工具覆盖。

7.3 为什么签名和运行缺一不可

readelf -h 只能证明文件是 ELF64 / AArch64,还不能证明它满足设备的执行要求。HarmonyOS PC 上,ELF 需要通过 binary-sign-tool 进行自签名。图 5 中连续出现“添加 codesign section 成功”“写入签名数据成功”“消费者输出 PASS”“退出码为 0”,因此它覆盖了链接、架构、签名和运行四个阶段。

八、上游测试口径与边界

LLD 的上游测试不是单一平台的简单总数。lld/test 同时包含 Windows、macOS、PPC64 等目标和系统专属测试;OHOS AArch64 的结果不能替代这些平台语义。

MR #9167 的测试台账以独立 lit 用例为中心,源码目录审查口径为 2690 项,其中 Inputs 等夹具文件不是独立断言。文章中应始终区分:上游测试发现数、当前配置实际实例化数、实际运行通过数、平台阻塞项和预期失败项;不能将可执行集合的成功表述为“所有上游平台测试全部通过”。

典型平台边界包括 Windows DIA/PDB、system-windows、macOS xar/system-darwin、PPC64 TOC 指令语义,以及 HMDFS 权限位行为。这些项目应保留首错、适用平台和恢复条件,而不是改写为通过。

8.1 为什么不能只报一个百分比

LLD 的测试目录含有测试输入、夹具、目标格式专属用例和真实断言。下面四个数字表达的不是同一件事:

口径应回答的问题
源码发现数上游目录里有多少可能的测试文件或入口?
独立用例数哪些项目真正拥有独立 RUN 行或断言?
OHOS 实际运行数当前配置在 AArch64/OHOS 上注册并执行了多少?
平台阻塞数哪些用例需要 Windows、macOS、PPC64 或特定工具才能恢复?

MR 台账采用 2690 项上游目录审查口径,并单独记录 2679 项独立实例化用例。文章刻意不把跨平台阻塞项从分母中抹掉,也不把某个子集合成功替换成“上游全量成功”。这是工具链项目比普通库更需要遵守的测试表达纪律。

8.2 测试失败的标准排查顺序

出现 lit 失败时,建议按以下顺序定位:

  1. 先确认测试实际使用的是本次构建产生的工具,而不是系统同名工具;
  2. 保存完整 RUN 命令、首个失败断言、退出码和临时目录;
  3. 判断失败是目标平台差异、缺少外部工具、文件系统语义,还是 LLD 输出本身变化;
  4. 对平台阻塞项写出恢复条件,例如“需要 Windows DIA SDK”或“需要 PPC64 后端语义”;
  5. 只有在根因确定且修复可复现后,才修改补丁或测试期望。

这种顺序比“先把失败跳过”更慢一点,但能防止把真实兼容性缺陷误归类为环境问题。

九、可复用流程清单

对于后续需要发布 Conan 工具型制品的 HarmonyOS PC 适配,可以复用以下流程:

  1. 固定官方源码版本、下载地址和 SHA-256。
  2. 枚举构建入口、测试入口和平台判断文件,先完成平台识别。
  3. 明确区分“构建 LLVM 单元测试”和“运行 LLD lit 测试”。
  4. 以 Conan 配方打包工具,并保留 bin/ 中真实可执行程序。
  5. 在真机执行 conan download,确认远端确实存在目标 ABI 的二进制包。
  6. 用与制品一致的 profile 执行 conan install --build=never,防止隐式本地重建掩盖问题。
  7. 通过 VirtualRunEnv 和绝对路径确认消费者使用的确实是包内工具。
  8. 用最小消费者完成编译、链接、AArch64 ELF 检查、签名和运行。
  9. 在文章中分别呈现 PR、真机、下载、包内执行和消费者运行证据。

十、结论

LLD 15.0.4 的适配覆盖了 HarmonyOS 平台识别、LLVM CMake 分类、Python 3 脚本兼容、HMDFS 权限测试边界和跨平台测试矩阵处理。

真机侧形成了完整的可复核链路:

HarmonyOS PC AArch64
  -> 从 ohpcd 下载 lld/15.0.4 制品
  -> Conan VirtualRunEnv 解析包内 ld.lld
  -> 显式使用该 ld.lld 链接消费者
  -> ELF64/AArch64 校验
  -> binary-sign-tool 自签名
  -> 真机运行并返回 0

十一、FAQ

Q1:为什么不能直接运行 command -v ld.lld?

系统 SDK 也可能提供同名链接器。必须先确认路径指向 .conan2/p/.../bin/ld.lld,否则无法证明运行的是本次下载的 Conan 制品。

Q2:为什么 conan install 会提示没有兼容包?

Conan 二进制包由 OS、架构、编译器版本、C++ 标准和 C++ 运行库等共同确定。本制品为 cppstd=17 与 libc++,消费者 profile 若使用 gnu14 或 libstdc++11,会计算出另一个 package ID,需要显式对齐设置。

Q3:为什么链接后还要签名?

HarmonyOS PC 对 ELF 运行有签名要求。签名成功并在真机运行,证明的不只是“链接器生成了文件”,而是该文件能够作为实际 ELF 交付物运行。

Q4:为什么 clang 要传入 LLD 的绝对路径?

SDK clang 可能优先选择自身安装目录下的 linker。-fuse-ld="$LLD_BIN" 将链接动作固定到 Conan 制品中的 LLD,避免同名 SDK 工具干扰验证结论。

Q5:conan download 成功后,为什么 conan install 仍可能失败?

download 能证明某个包存在并已经被下载;install 会根据当前 profile 重新计算需要的 package ID。若编译器版本、cppstd、libcxx、架构或操作系统版本不同,就可能下载到了 A 包却请求 B 包。应按图 3 的 settings 显式对齐 profile。

Q6:能否使用 --build=missing 解决缺包?

可以用于开发阶段构建源码,但不能用于“验证远端制品仓已发布并可消费”的截图。本文的真机消费环节使用 --build=never,一旦远端没有匹配包就立即报错,结论更可信。

Q7:为什么图 5 中既使用 clang 又使用 LLD?

clang 负责 C 源码编译、sysroot 和启动文件选择;LLD 负责最终链接。-fuse-ld="$LLD_BIN" 保留 clang 驱动的目标平台配置,同时把关键链接步骤明确交给 Conan 包内的 LLD。这是验证链接器制品比手工拼接 crt*.o、动态链接器和 libc 参数更稳妥的方式。

Logo

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

更多推荐