二. LLVM交叉編譯
前言:交叉編譯最重要的是生成具有與編譯機不同架構的指令,除此之外,編譯過程還需要完整的工具鏈,包括編譯器、鏈接器、庫、頭文件等。
??GCC會針對每個編譯主機和目標架構提供一套完整的套件,包含了二進制、頭文件和庫等。所以一般使用起來比較簡單,下載對應的安裝包,解壓到一個合適的目錄就可以使用了,編譯器會使用自帶的頭文件和庫。
??不過 LLVM 有所不同,LLVM 是復用一套編譯系統負責多個目標架構的編譯任務,通過 -target 選項來區分。這樣的好處是開發者可以維護一套編譯環境即可編譯多個平臺和架構的程序。但是這樣會給開發者帶來使用上的麻煩,因為要不同的目標架構和系統的程序會需要鏈接不同的庫,而編譯器需要足夠的參數被告知去哪里尋找這些庫和頭文件的位置。不僅如此,這些編譯所需的庫通常也需要開發者自己去準備。
1. 準備工作
??如果希望進行交叉編譯,就需要我們的編譯器支持目標架構,即擁有對應的控制臺。LLVM支持哪些目標架構是在其編譯的時候決定的,而前一章在編譯LLVM的時候我們,并沒有指定相關的參數,即 LLVM_TARGETS_TO_BUILD。不過該參數有一個默認值 LLVM_ALL_TARGETS,即所有支持的架構,所以我們編譯的 LLVM 說明已經是支持很多架構的了。
如果想知道我們的LLVM支持哪些架構,可以通過llc -version命令查看

注:ARM是32位的arm架構,AARCH64和ARMV64是64位的arm架構,thumb是16位的arm架構,X86包含了32位和64位,而arm架構系列又分了大端和小端,于是出現了這么多的組合。
查看Target目錄
cd llvm/lib/Target/
ls #查看文件目錄

從目錄結構可以看出,這么多目標架構,實際上總共就這幾個實現,即ARM列的大端和小端,X86系列的32位和64位,實際上是同一套代碼實現的。
ls ARM/進入ARM文件夾查看

可以看出,thumb架構實際上是合用了ARM 32-bit的實現。如果參數LLVM_TARGETS_TO_BUILD只設置ARM,編譯結果支持了ARM和Thumb的大端和小端,即arm 32-bit和16-bit系列共四種架構。
接下來我們測試一下是否可以在X86環境下編譯出arm64的程序。
2. 交叉編譯 X86→ARM64
編寫一個C程序
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
printf("%d + %d = %d\n", a, b, a + b);
return 0;
}
使用 Clang 進行交叉編譯需要用 -target 參數來指定編譯目標,這里我們使用 aarch64-linux-gnu,即所謂的三段式目標表示形式。
clang -target aarch64-linux-gnu cctest.c -o cctest
報錯

錯誤是因為編譯器找不到某些頭文件。我們前面文已經提到過,交叉編譯環境需要目標架構所需的相關庫和頭文件。這部分應該由硬件廠商提供,我們需要自行獲取。那么我們需要獲取哪些庫呢?無非是C/C++標準庫和鏈接器,我們嘗試通過apt的搜索命令apt search arm64來查詢。

根據當前環境的GCC版本,

需要安裝以下庫
sudo apt install binutils-aarch64-linux-gnu
sudo apt install cpp-5-aarch64-linux-gnu
sudo apt install g++-5-aarch64-linux-gnu
sudo apt install linux-libc-dev-arm64-cross
sudo apt install libstdc++-5-dev-arm64-cross
sudo apt install libstdc++6-5-dbg-arm64-cross
sudo apt install libstdc++6-arm64-cross
sudo apt install libgcc-5-dev-arm64-cross
sudo apt install crossbuild-essential-arm64
繼續
clang -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu cctest.c -o cctest
# Clang通過--sysroot=/pat來獲取安裝的庫的位置
報錯鏈接器找不到某個.o文件

通過llvm開發的鏈接器 lld 解決這個問題,首先下載lld,解壓到/llvm/tools,接下來重新編譯llvm,注意此時需要將編譯目錄清空,否則不會把新加的代碼引入編譯

重新編譯之后再回來。這次我們需要用lld需要使用參數 -fuse-ld=lld 來指定
clang -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu -fuse-ld=lld cctest.c -o cctest

用了 lld 似乎完全沒有變化,問題還在。不過我們可以看到,這里說找不到的文件,crtbegin.o 在 /usr/aarch64-linux-gnu 中確實不存在。而相關的庫該裝的都已經裝好了,到底這個文件有沒有?在哪里呢?那我們來搜索看看。

有兩個目錄存在,第一個是 x86 的,另一個才是我們需要的。這個目錄并不在 /usr/aarch64-linux-gnu 里面,或許需要我們手動指定來告訴 lld
clang -target aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu -L/usr/lib/gcc-cross/aarch64-linux-gnu/5 -fuse-ld=lld cctest.c -o cctest

這次問題變少了,雖然 crtbegin.o 文件依然找不到,但后面的一些報錯是沒有了。為了搞清楚問題,我們給編譯命令加上更多的輸出來看看。我們可以通過添加 -v 參數來獲得更詳細的輸出。
我們看最后,從這個參數可以看出,似乎是要求把當前目錄的 crtbegin.o 和 crtend.o 拿來靜態鏈接,而非像一般的系統庫一樣動態鏈接。也就是說這兩個文件在一般系統上是不會提供動態鏈接庫的版本,或許是因為功能的特殊性(在進程啟動之初,動態鏈接庫加載之前使用),要求它們只能靜態鏈接到可執行文件中。
在 llvm 的源碼中搜索這兩個文件,并把這兩個文件拷貝到當前目錄,再編譯試試。

通過 file cctest和 readelf cctest 來查看生成的可執行文件的信息,看起來沒有什么問題。接下來就需要來實際檢驗可執行文件在 arm64 的物理機上的運行效果了。

3. 交叉編譯 X86→ARM32
clang -target arm-linux-gnueabihf cctest.c -o cctest
報錯:

與ARM64相似,也是缺少依賴,通過
apt search armhf
查詢所需依賴:

根據GCC版本安裝以下庫
sudo apt install binutils-arm-linux-gnueabihf
sudo apt install cpp-5-arm-linux-gnueabihf
sudo apt install g++-5-arm-linux-gnueabihf
sudo apt install linux-libc-dev-armhf-cross
sudo apt install libstdc++-5-dev-armhf-cross
sudo apt install libstdc++6-5-dbg-armhf-cross
sudo apt install libstdc++6-armhf-cross
sudo apt install libgcc-5-dev-armhf-cross
sudo apt install crossbuild-essential-armhf
同樣查詢crtbegin.o和crtend.o文件

拷貝至當前目錄,再運行
clang -target arm-linux-gnueabihf --sysroot=/usr/arm-linux-gnueabihf -L/usr/lib/gcc-cross/arm-linux-gnueabihf/5 -fuse-ld=lld cctest.c -o cctest32
生成可執行程序

參考:play_with_llvm/ch03.md at master · tuoxie007/play_with_llvm · GitHub


浙公網安備 33010602011771號