如何为 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_FLAGSCMAKE_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_FLAGSCMAKE_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 不包含 --targetCMAKE_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_TARGETCMAKE_ASM_COMPILER_TARGETCMAKE_C_COMPILER_EXTERNAL_TOOLCHAINCMAKE_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。