使用 GCC 在單一的構建機器上來為不同的 CPU 架構交叉編譯二進位制檔案。
如果你是一個開發者,要建立二進位制軟體包,像一個 RPM、DEB、Flatpak 或 Snap 軟體包,你不得不為各種不同的目標平台編譯程式碼。典型的編譯目標包括 32 位和 64 位的 x86 和 ARM。你可以在不同的物理或虛擬機器器上完成你的構建,但這需要你為何幾個系統。作為代替,你可以使用 GNU 編譯器集合 (GCC) 來交叉編譯,在單一的構建機器上為幾個不同的 CPU 架構產生二進位制檔案。
假設你有一個想要交叉編譯的簡單的擲骰子遊戲。在大多數系統上,以 C 語言來編寫這個相對簡單,出於給新增現實的複雜性的目的,我以 C++ 語言寫這個範例,所以程式依賴於一些不在 C 語言中東西 (具體來說就是 iostream
)。
#include <iostream>#include <cstdlib>using namespace std;void lose (int c); void win (int c); void draw (); int main() { int i; do { cout << "Pick a number between 1 and 20: \n"; cin >> i; int c = rand ( ) % 21; if (i > 20) lose (c); else if (i < c ) lose (c); else if (i > c ) win (c); else draw (); } while (1==1); }void lose (int c ) { cout << "You lose! Computer rolled " << c << "\n"; }void win (int c ) { cout << "You win!! Computer rolled " << c << "\n"; }void draw ( ) { cout << "What are the chances. You tied. Try again, I dare you! \n"; }
在你的系統上使用 g++
命令編譯它:
$ g++ dice.cpp -o dice
然後,執行它來確認其工作:
$ ./dicePick a number between 1 and 20:[...]
你可以使用 file
命令來檢視你剛剛生產的二進位制檔案的型別:
$ file ./dicedice: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamicallylinked (uses shared libs), for GNU/Linux 5.1.15, not stripped
同樣重要,使用 ldd
命令來檢視它連結哪些庫:
$ ldd dicelinux-vdso.so.1 => (0x00007ffe0d1dc000)libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6(0x00007fce8410e000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6(0x00007fce83d4f000)libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6(0x00007fce83a52000)/lib64/ld-linux-x86-64.so.2 (0x00007fce84449000)libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1(0x00007fce8383c000)
從這些測試中,你已經確認了兩件事:你剛剛執行的二進位制檔案是 64 位的,並且它連結的是 64 位庫。
這意味著,為實現 32 位交叉編譯,你必需告訴 g++
來:
為編譯成 32 位二進位制,你需要在你的系統上安裝 32 位的庫和標頭檔案。如果你執行一個純 64 位系統,那麼,你沒有 32 位的庫或標頭檔案,並且需要安裝一個基礎集合。最起碼,你需要 C 和 C++ 庫(glibc
和 libstdc++
)以及 GCC 庫(libgcc
)的 32 位版本。這些軟體包的名稱可能在每個發行版中不同。在 Slackware 系統上,一個純 64 位的帶有 32 位相容的發行版,可以從 Alien BOB 提供的 multilib
軟體包中獲得。在 Fedora、CentOS 和 RHEL 系統上:
$ yum install libstdc++-*.i686$ yum install glibc-*.i686$ yum install libgcc.i686
不管你正在使用什麼系統,你同樣必須安裝一些你工程使用的 32 位庫。例如,如果你在你的工程中包含 yaml-cpp
,那麼,在編譯工程前,你必需安裝 yaml-cpp
的 32 位版本,或者,在很多系統上,安裝 yaml-cpp
的開發軟體包(例如,在 Fedora 系統上的 yaml-cpp-devel
)。
一旦這些處理好了,編譯是相當簡單的:
$ g++ -m32 dice.cpp -o dice32 -L /usr/lib -march=i686
-m32
標誌告訴 GCC 以 32 位元型樣編譯。-march=i686
選項進一步定義來使用哪種最佳化型別(參考 info gcc
了解選項列表)。-L
標誌設定你希望 GCC 來連結的庫的路徑。對於 32 位來說通常是 /usr/lib
,不過,這依賴於你的系統是如何設定的,它可以是 /usr/lib32
,甚至 /opt/usr/lib
,或者任何你知道存放你的 32 位庫的地方。
在程式碼編譯後,檢視你的構建的證據:
$ file ./dice32dice: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),dynamically linked (uses shared libs) [...]
接著,當然, ldd ./dice32
也會指向你的 32 位庫。
在 64 位相同的處理器家族上允許 GCC 做出很多關於如何編譯程式碼的假設來編譯 32 位軟體。如果你需要為完全不同的處理器編譯,你必需安裝適當的交叉構建實用程式。安裝哪種實用程式取決於你正在編譯的東西。這個過程比為相同的 CPU 家族編譯更複雜一點。
當你為相同處理器家族交叉編譯時,你可以期待找到與 32 位庫集的相同的 64 位庫集,因為你的 Linux 發行版是同時維護這二者的。當為一個完全不同的架構編譯時,你可能不得不窮追你的程式碼所需要的庫。你需要的版本可能不在你的發行版的儲存庫中,因為你的發行版可能不為你的目標系統提供軟體包,或者它不在容易到達的位置提供所有的軟體包。如果你正在編譯的程式碼是你寫的,那麼你可能非常清楚它的依賴關係是什麼,並清楚在哪裡找到它們。如果程式碼是你下載的,並需要編譯,那麼你可能不熟悉它的要求。在這種情況下,研究正確編譯程式碼需要什麼(它們通常被列在 README
或 INSTALL
檔案中,當然也出現在原始檔程式碼自身之中),然後收集需要的元件。
例如,如果你需要為 ARM 編譯 C 程式碼,你必須首先在 Fedora 或 RHEL 上安裝 gcc-arm-linux-gnu
(32 位)或 gcc-aarch64-linux-gnu
(64 位);或者,在 Ubuntu 上安裝 arm-linux-gnueabi-gcc
和 binutils-arm-linux-gnueabi
。這提供你需要用來構建(至少)一個簡單的 C 程式的命令和庫。此外,你需要你的程式碼使用的任何庫。你可以在慣常的位置(大多數系統上在 /usr/include
)放置標頭檔案,或者,你可以放置它們在一個你選擇的目錄,並使用 -I
選項將 GCC 指向它。
當編譯時,不使用標準的 gcc
或 g++
命令。作為代替,使用你安裝的 GCC 實用程式。例如:
$ arm-linux-gnu-g++ dice.cpp \ -I/home/seth/src/crossbuild/arm/cpp \ -o armdice.bin
驗證你構建的內容:
$ file armdice.binarmdice.bin: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV) [...]
這是一個如何使用交叉編譯的簡單的範例。在真實的生活中,你的原始檔程式碼可能產生的不止於一個二進位制檔案。雖然你可以手動管理,在這裡手動管理可能不是好的正當理由。在我接下來的文章中,我將說明 GNU 自動工具,GNU 自動工具做了使你的程式碼可移植的大部分工作。