如何为 Arm 交叉编译 Compiler-rt Builtins¶
简介¶
本文档包含关于在 x86_64 Linux 机器上为 Arm 目标构建和测试 compiler-rt 的 builtins 部分的信息。
虽然本文档专注于 Arm 和 Linux,但通用原则应适用于 compiler-rt 支持的其他目标。欢迎对其他目标进行进一步的贡献。
本文档中的说明依赖于 LLVM 外部的库和程序,安装和配置这些依赖项的方法有很多种,因此您可能需要调整此处的说明以适应您自己的本地情况。
先决条件¶
在此用例中,我们将在一个基于 Debian 的 Linux 系统上使用 cmake,从 x86_64 主机交叉编译到硬浮点 Armv7-A 目标。我们将尽可能多地使用 LLVM 工具,但也可以使用 GNU 等效工具。
用于 llvm-tools 和 llvm-config 的 LLVM/clang 构建版本
支持 ARM 目标的 clang 可执行文件
compiler-rt 源代码
qemu-arm 用户模式 模拟器
arm-linux-gnueabihf sysroot
在本例中,我们将使用 ninja。
有关 clang 和 LLVM 依赖项的更多信息,请参阅 https://compiler-rt.llvm.org/。
有关获取 LLVM 和 compiler-rt 源代码的信息,请参阅 https://llvm.net.cn/docs/GettingStarted.html。请注意,入门指南将 compiler-rt 放在 projects 子目录中,但这并非必要,如果您将 BaremetalARM.cmake 缓存用于 v6-M、v7-M 和 v7-EM,则必须将 compiler-rt 放在 runtimes 目录中。
qemu-arm
应该作为您 Linux 发行版的软件包提供。
最复杂的先决条件是满足 arm-linux-gnueabihf sysroot。理论上,可以使用 Linux 发行版的 multiarch 支持来满足构建的依赖项,但不幸的是,由于添加了 /usr/local/include,某些主机包含文件被选中。提供 sysroot 最简单的方法是下载 arm-linux-gnueabihf 工具链。可以在以下位置找到:* https://developer.arm.com/open-source/gnu-toolchain/gnu-a/downloads 适用于 gcc 8 及更高版本 * https://releases.linaro.org/components/toolchain/binaries/ 适用于 gcc 4.9 到 7.3
为 Arm 构建 compiler-rt builtins¶
我们将使用以下 cmake 选项对 compiler-rt 进行独立构建。
path/to/compiler-rt
-G Ninja
-DCMAKE_AR=/path/to/llvm-ar
-DCMAKE_ASM_COMPILER_TARGET="arm-linux-gnueabihf"
-DCMAKE_ASM_FLAGS="build-c-flags"
-DCMAKE_C_COMPILER=/path/to/clang
-DCMAKE_C_COMPILER_TARGET="arm-linux-gnueabihf"
-DCMAKE_C_FLAGS="build-c-flags"
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld"
-DCMAKE_NM=/path/to/llvm-nm
-DCMAKE_RANLIB=/path/to/llvm-ranlib
-DCOMPILER_RT_BUILD_BUILTINS=ON
-DCOMPILER_RT_BUILD_LIBFUZZER=OFF
-DCOMPILER_RT_BUILD_MEMPROF=OFF
-DCOMPILER_RT_BUILD_PROFILE=OFF
-DCOMPILER_RT_BUILD_SANITIZERS=OFF
-DCOMPILER_RT_BUILD_XRAY=OFF
-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON
-DLLVM_CONFIG_PATH=/path/to/llvm-config
build-c-flags
需要足以通过 C-make 编译器检查,编译 compiler-rt,如果您要运行测试,则需要编译和链接测试。当使用 clang 进行交叉编译时,我们需要传递足够的信息来为我们定位的 Arm 架构生成代码。我们需要选择 Arm 目标,选择 Armv7-A 架构,并在使用 Arm 或 Thumb 指令之间进行选择。例如
--target=arm-linux-gnueabihf
-march=armv7a
-mthumb
当使用 GCC arm-linux-gnueabihf 工具链时,需要以下标志来获取包含文件和库
--gcc-toolchain=/path/to/dir/toolchain
--sysroot=/path/to/toolchain/arm-linux-gnueabihf/libc
在本例中,我们将把所有命令行选项添加到 CMAKE_C_FLAGS
和 CMAKE_ASM_FLAGS
中。有一些 cmake 标志可以单独传递其中一些选项,这可以简化 build-c-flags
-DCMAKE_C_COMPILER_TARGET="arm-linux-gnueabihf"
-DCMAKE_ASM_COMPILER_TARGET="arm-linux-gnueabihf"
-DCMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN=/path/to/dir/toolchain
-DCMAKE_SYSROOT=/path/to/dir/toolchain/arm-linux-gnueabihf/libc
一旦 cmake 完成,就可以使用 ninja builtins
构建 builtins
使用 qemu-arm 测试 compiler-rt builtins¶
要测试 builtins 库,我们需要添加一些 cmake 标志来启用测试,并设置测试用例的编译器和标志。我们还必须告诉 cmake 我们希望在 qemu-arm
上运行测试。
-DCOMPILER_RT_EMULATOR="qemu-arm -L /path/to/armhf/sysroot
-DCOMPILER_RT_INCLUDE_TESTS=ON
-DCOMPILER_RT_TEST_COMPILER="/path/to/clang"
-DCOMPILER_RT_TEST_COMPILER_CFLAGS="test-c-flags"
/path/to/armhf/sysroot
应该与 “build-c-flags” 中传递给 --sysroot
的路径相同。
“test-c-flags” 需要包含目标、架构、gcc-toolchain、sysroot 和 arm/thumb 状态。额外的 cmake 定义(例如 CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN
)在构建测试时不适用。如果您已将所有这些都放在 “build-c-flags” 中,则可以重复使用。如果您希望使用 lld 链接测试,则添加 "-fuse-ld=lld
。
一旦 cmake 完成,就可以使用 ninja check-builtins
构建和运行测试
故障排除¶
cmake 尝试编译阶段失败¶
在早期阶段,cmake 将尝试编译和链接一个简单的 C 程序,以测试工具链是否正常工作。
如果未将 --sysroot=
和 --gcc-toolchain=
选项传递给编译器,则此阶段通常会在链接时失败。检查 CMAKE_C_FLAGS
和 CMAKE_C_COMPILER_TARGET
标志。
在 cmake 之外使用您的工具链构建一个简单的示例以确保其正常工作会很有用。例如:clang --target=arm-linux-gnueabi -march=armv7a --gcc-toolchain=/path/to/gcc-toolchain --sysroot=/path/to/gcc-toolchain/arm-linux-gnueabihf/libc helloworld.c
Clang 使用主机头文件¶
在基于 debian 的系统中,可以安装 arm-linux-gnueabi 和 arm-linux-gnueabihf 的 multiarch 支持。在许多情况下,当未提供 --gcc-toolchain=
和 --sysroot=
时,clang 可以成功使用此 multiarch 支持。不幸的是,clang 在 /usr/include/arm-linux-gnueabihf
之前添加了 /usr/local/include
,从而在编译主机头文件时导致错误。
multiarch 支持不足以构建 builtins,您需要使用单独的 arm-linux-gnueabihf 工具链。
未将目标传递给 clang¶
如果未给 clang 指定目标,它通常会使用主机目标,这将无法理解 Arm 汇编语言文件,从而导致错误消息,例如 error: unknown directive .syntax unified
。
您可以检查错误消息中的 clang 调用,以查看是否没有 --target
或是否设置不正确。原因通常是 CMAKE_ASM_FLAGS
未包含 --target
或 CMAKE_ASM_COMPILER_TARGET
不存在。
未给出 Arm 架构¶
--target=arm-linux-gnueabihf
将默认为 arm 架构 v4t,它无法汇编 synch_and_fetch 源文件中使用的 barrier 指令。
原因通常是 CMAKE_ASM_FLAGS
中缺少 -march=armv7a
。
Compiler-rt 构建成功,但测试构建失败¶
用于构建测试的标志与用于构建 builtins 的标志不同。c 标志由 COMPILER_RT_TEST_COMPILE_CFLAGS
提供,并且不应用 CMAKE_C_COMPILER_TARGET
、CMAKE_ASM_COMPILER_TARGET
、CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN
和 CMAKE_SYSROOT
标志。
确保 COMPILER_RT_TEST_COMPILE_CFLAGS
包含所有必要的信息。
针对其他目标的修改¶
Arm 软浮点目标¶
Arm 硬浮点目标的说明可以用于软浮点目标,只需将 sysroot 和目标替换为软浮点等效项即可。要使用的目标是
-DCMAKE_C_COMPILER_TARGET=arm-linux-gnueabi
根据您是否要使用浮点指令,您可能需要额外的 c 标志,例如 -mfloat-abi=softfp
用于使用浮点指令,以及 -mfloat-abi=soft -mfpu=none
用于软件浮点仿真。
您将需要使用 arm-linux-gnueabi GNU 工具链进行软浮点。
AArch64 目标¶
Arm 的说明可以用于 AArch64,只需将 sysroot、模拟器和目标替换为 AArch64 等效项即可。
-DCMAKE_C_COMPILER_TARGET=aarch64-linux-gnu
-DCOMPILER_RT_EMULATOR="qemu-aarch64 -L /path/to/aarch64/sysroot
CMAKE_C_FLAGS 和 COMPILER_RT_TEST_COMPILER_CFLAGS 可能还需要:"--sysroot=/path/to/aarch64/sysroot --gcc-toolchain=/path/to/gcc-toolchain"
Armv6-m、Armv7-m 和 Armv7E-M 目标¶
使用与 Armv7-A 类似的方法构建和测试库是可能的,但更困难。主要问题是
没有用于裸机系统的
qemu-arm
用户模式模拟器。qemu-system-arm
可以使用,但设置起来要困难得多。编译 compiler-rt 的目标具有后缀 -none-eabi。这在 clang 中使用 BareMetal 驱动程序,默认情况下不会找到通过 cmake 编译器检查所需的库。
由于 compiler-rt 的 Armv6-M、Armv7-M 和 Armv7E-M 构建仅使用 Armv7-A 上支持的指令,因此我们仍然可以通过为 Armv7-A 构建和运行测试用例,但使用为 Armv6-M、Armv7-M 或 Armv7E-M 编译的 builtins,从而获得使用与 Armv7-A 相同的 qemu-arm
运行测试的大部分价值。这将测试 builtins 是否可以链接到二进制文件中并正确执行测试,但如果 builtins 使用 Armv7-A 上支持但 Armv6-M、Armv7-M 和 Armv7E-M 上不支持的指令,则不会捕获。
要使 cmake 编译测试通过,您需要通过 CMAKE_CFLAGS
传递成功链接 cmake 测试所需的库。强烈建议您使用 cmake 3.6 或更高版本,以便可以使用 CMAKE_TRY_COMPILE_TARGET=STATIC_LIBRARY
跳过链接步骤。
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY
-DCOMPILER_RT_OS_DIR="baremetal"
-DCOMPILER_RT_BUILD_BUILTINS=ON
-DCOMPILER_RT_BUILD_SANITIZERS=OFF
-DCOMPILER_RT_BUILD_XRAY=OFF
-DCOMPILER_RT_BUILD_LIBFUZZER=OFF
-DCOMPILER_RT_BUILD_PROFILE=OFF
-DCMAKE_C_COMPILER=${host_install_dir}/bin/clang
-DCMAKE_C_COMPILER_TARGET="您的 *-none-eabi 目标"
-DCMAKE_ASM_COMPILER_TARGET="您的 *-none-eabi 目标"
-DCMAKE_AR=/path/to/llvm-ar
-DCMAKE_NM=/path/to/llvm-nm
-DCMAKE_RANLIB=/path/to/llvm-ranlib
-DCOMPILER_RT_BAREMETAL_BUILD=ON
-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON
-DLLVM_CONFIG_PATH=/path/to/llvm-config
-DCMAKE_C_FLAGS="build-c-flags"
-DCMAKE_ASM_FLAGS="build-c-flags"
-DCOMPILER_RT_EMULATOR="qemu-arm -L /path/to/armv7-A/sysroot"
-DCOMPILER_RT_INCLUDE_TESTS=ON
-DCOMPILER_RT_TEST_COMPILER="/path/to/clang"
-DCOMPILER_RT_TEST_COMPILER_CFLAGS="test-c-flags"
Armv6-M builtins 将使用软浮点 ABI。当为 Armv7-A 编译测试时,我们必须在 test-c-flags 中包含 "-mthumb -mfloat-abi=soft -mfpu=none"
。我们必须为 qemu-arm
使用 Armv7-A 软浮点 abi sysroot。
根据用于测试用例的链接器,您可能会遇到来自 compiler-rt 的 M-profile 对象与来自测试的 A-profile 对象之间的 BuildAttribute 不匹配。lld 链接器不检查 profile BuildAttribute,因此可以通过将 -fuse-ld=lld 添加到 COMPILER_RT_TEST_COMPILER_CFLAGS
来使用它链接测试。
使用 cmake 缓存的替代方案¶
如果您希望构建但不测试用于 Armv6-M、Armv7-M 或 Armv7E-M 的 compiler-rt,最简单的方法是使用 clang/cmake/caches 中的 BaremetalARM.cmake 配方。
您将需要一个裸机 sysroot,例如 GNU ARM Embedded 工具链提供的 sysroot。
可以使用以下 cmake 选项构建库
-DBAREMETAL_ARMV6M_SYSROOT=/path/to/bare/metal/toolchain/arm-none-eabi
-DBAREMETAL_ARMV7M_SYSROOT=/path/to/bare/metal/toolchain/arm-none-eabi
-DBAREMETAL_ARMV7EM_SYSROOT=/path/to/bare/metal/toolchain/arm-none-eabi
-C /path/to/llvm/source/tools/clang/cmake/caches/BaremetalARM.cmake
/path/to/llvm
注意,为了使配方生效,必须将 compiler-rt 源代码检出到目录 llvm/runtimes 中。您还需要检出 clang 和 lld。