32位元支援:使用 GCC 交叉編譯

2019-07-19 05:42:00

使用 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 =&gt; (0x00007ffe0d1dc000)libstdc++.so.6 =&gt; /usr/lib/x86_64-linux-gnu/libstdc++.so.6(0x00007fce8410e000)libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6(0x00007fce83d4f000)libm.so.6 =&gt; /lib/x86_64-linux-gnu/libm.so.6(0x00007fce83a52000)/lib64/ld-linux-x86-64.so.2 (0x00007fce84449000)libgcc_s.so.1 =&gt; /lib/x86_64-linux-gnu/libgcc_s.so.1(0x00007fce8383c000)

從這些測試中,你已經確認了兩件事:你剛剛執行的二進位制檔案是 64 位的,並且它連結的是 64 位庫。

這意味著,為實現 32 位交叉編譯,你必需告訴 g++ 來:

  1. 產生一個 32 位二進位制檔案
  2. 連結 32 位庫,而不是 64 位庫

設定你的開發環境

為編譯成 32 位二進位制,你需要在你的系統上安裝 32 位的庫和標頭檔案。如果你執行一個純 64 位系統,那麼,你沒有 32 位的庫或標頭檔案,並且需要安裝一個基礎集合。最起碼,你需要 C 和 C++ 庫(glibclibstdc++)以及 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 發行版是同時維護這二者的。當為一個完全不同的架構編譯時,你可能不得不窮追你的程式碼所需要的庫。你需要的版本可能不在你的發行版的儲存庫中,因為你的發行版可能不為你的目標系統提供軟體包,或者它不在容易到達的位置提供所有的軟體包。如果你正在編譯的程式碼是你寫的,那麼你可能非常清楚它的依賴關係是什麼,並清楚在哪裡找到它們。如果程式碼是你下載的,並需要編譯,那麼你可能不熟悉它的要求。在這種情況下,研究正確編譯程式碼需要什麼(它們通常被列在 READMEINSTALL 檔案中,當然也出現在原始檔程式碼自身之中),然後收集需要的元件。

例如,如果你需要為 ARM 編譯 C 程式碼,你必須首先在 Fedora 或 RHEL 上安裝 gcc-arm-linux-gnu(32 位)或 gcc-aarch64-linux-gnu(64 位);或者,在 Ubuntu 上安裝 arm-linux-gnueabi-gccbinutils-arm-linux-gnueabi。這提供你需要用來構建(至少)一個簡單的 C 程式的命令和庫。此外,你需要你的程式碼使用的任何庫。你可以在慣常的位置(大多數系統上在 /usr/include)放置標頭檔案,或者,你可以放置它們在一個你選擇的目錄,並使用 -I 選項將 GCC 指向它。

當編譯時,不使用標準的 gccg++ 命令。作為代替,使用你安裝的 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 自動工具做了使你的程式碼可移植的大部分工作。