如何為 Arm 交叉編譯 Compiler-rt 內建函式庫

簡介

本文件包含從 x86_64 Linux 機器建置和測試 Arm 目標平台的 Compiler-rt 內建函式庫部分的資訊。

雖然本文件著重於 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.dev.org.tw/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 內建函式庫

我們將使用以下 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 定義(例如 CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN)在構建測試時不適用。如果您已將所有這些內容都放在 “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 安裝多架構支援。在許多情況下,當未提供 --gcc-toolchain=--sysroot= 時,clang 可以成功使用此多架構支援。不幸的是,clang 在 /usr/include/arm-linux-gnueabihf 之前添加了 /usr/local/include,導致在編譯主機標頭檔時出錯。

多架構支援不足以建置內建元件,您需要使用單獨的 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_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-flags,例如使用浮點指令時使用 -mfloat-abi=softfp,以及使用軟體浮點模擬時使用 -mfloat-abi=soft -mfpu=none

您需要使用 arm-linux-gnueabi GNU 工具鏈來處理軟浮點。

AArch64 目標

透過將系統根目錄、模擬器和目標替換為 AArch64 等效項,Arm 的指令可以用於 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 相同的 qemu-arm 來建置和執行 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="您的 *-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 內建函式將使用軟浮點 ABI。在為 Armv7-A 編譯測試時,我們必須在測試 c-flags 中包含 "-mthumb -mfloat-abi=soft -mfpu=none"。我們必須為 qemu-arm 使用 Armv7-A 軟浮點 abi 系統根目錄。

根據用於測試案例的連結器,您可能會在來自 compiler-rt 的 M 配置文件物件和來自測試的 A 配置文件物件之間遇到 BuildAttribute 不匹配。lld 連結器不會檢查配置文件 BuildAttribute,因此可以透過在 COMPILER_RT_TEST_COMPILER_CFLAGS 中添加 -fuse-ld=lld 來連結測試。

使用 cmake 快取的替代方案

如果您希望建置但不想測試 Armv6-M、Armv7-M 或 Armv7E-M 的 compiler-rt,最簡單的方法是使用 clang/cmake/caches 中的 BaremetalARM.cmake 配方。

您將需要一個裸機系統根目錄,例如 GNU ARM 嵌入式工具鏈提供的目錄。

可以使用 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。