如何为 Arm 交叉编译 Compiler-rt 内建函数¶
简介¶
本文档包含有关从 x86_64 Linux 机器为 Arm 目标构建和测试 compiler-rt 的内建函数部分的信息。
虽然本文档重点关注 Arm 和 Linux,但一般原则应适用于 compiler-rt 支持的其他目标。欢迎为其他目标做出更多贡献。
本文档中的说明依赖于 LLVM 之外的库和程序,安装和配置这些依赖项的方法有很多,因此您可能需要调整此处提供的说明以适应您自己的本地情况。
先决条件¶
在本用例中,我们将在基于 Debian 的 Linux 系统上使用 cmake,从 x86_64 宿主机交叉编译到硬浮点 Armv7-A 目标。我们将尽可能多地使用 LLVM 工具,但也可以使用 GNU 等效工具。
A build of LLVM/clang for the llvm-tools and llvm-config
A clang executable with support for the ARM target
compiler-rt sources
The qemu-arm user mode emulator
An 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 子目录中,但这并不是必需的,如果您正在使用 v6-M、v7-M 和 v7-EM 的 BaremetalARM.cmake 缓存,则必须将 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 内建函数¶
我们将使用以下 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
构建内建函数。
使用 qemu-arm 测试 compiler-rt 内建函数¶
要测试内建函数库,我们需要添加一些额外的 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_C_COMPILER_EXTERNAL_TOOLCHAIN
之类的其他 cmake 定义在构建测试时不适用。如果您已将所有这些内容放入“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 支持不足以构建内建函数,您需要使用单独的 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 源文件中使用的屏障指令。
原因通常是 CMAKE_ASM_FLAGS
中缺少 -march=armv7a
。
Compiler-rt 构建成功,但测试构建失败¶
用于构建测试的标志与用于构建内建函数的标志不同。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 等价物来用于 AArch64,例如 sysroot、模拟器和目标。
-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 编译的内置函数,来获得运行测试的大部分价值。这将测试内置函数是否可以链接到二进制文件并正确执行测试,但它不会捕获内置函数是否使用了 Armv7-A 上支持但在 Armv6-M、Armv7-M 和 Armv7E-M 上不支持的指令。
要使 cmake 编译测试通过,您需要通过 CMAKE_CFLAGS
传递成功链接 cmake 测试所需的库。强烈建议您使用 3.6 或更高版本的 cmake,以便您可以使用 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="your *-none-eabi target"
-DCMAKE_ASM_COMPILER_TARGET="your *-none-eabi target"
-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 内置函数将使用软浮点 ABI。在为 Armv7-A 编译测试时,我们必须在测试 c 标记中包含 "-mthumb -mfloat-abi=soft -mfpu=none"
。我们必须为 qemu-arm
使用 Armv7-A 软浮点 abi sysroot。
根据测试用例使用的链接器,您可能会遇到来自 compiler-rt 的 M 配置文件对象与来自测试的 A 配置文件对象之间的 BuildAttribute 不匹配。lld 链接器不会检查配置文件 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 工具链提供的那个。
库可以使用以下 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。