在linux中,交叉編譯是指在一個平臺上生成另一個平臺上的可執(zhí)行代碼,即編譯源代碼的平臺和執(zhí)行源代碼編譯后程序的平臺是兩個不同的平臺。使用交叉編譯的原因:1、目標系統(tǒng)沒有能力在其上進行本地編譯;2、有能力進行源代碼編譯的平臺與目標平臺不同。
本教程操作環(huán)境:linux5.9.8系統(tǒng)、Dell G3電腦。
交叉編譯
所謂"交叉編譯(Cross_Compile)",是指編譯源代碼的平臺和執(zhí)行源代碼編譯后程序的平臺是兩個不同的平臺。比如,在Intel x86架構(gòu)/Linux(Ubuntu)平臺下、使用交叉編譯工具鏈生成的可執(zhí)行文件,在ARM架構(gòu)/Linux下運行。
簡單地說,就是在一個平臺上生成另一個平臺上的 可執(zhí)行代碼。同一個 體系結(jié)構(gòu)可以運行不同的操作系統(tǒng);同樣,同一個操作系統(tǒng)也可以在不同的體系結(jié)構(gòu)上運行。
交叉編譯是相對復(fù)雜的,必須考慮如下幾個問題:
-
CPU架構(gòu):比如ARM,x86,MIPS等等;
-
字節(jié)序:大端(big-endian)和小端(little-endian);
-
浮點數(shù)的支持;
-
應(yīng)用程序二進制接口(Application Binary Interface,ABI);
為什么要使用交叉編譯呢?主要有兩個原因:
-
交叉編譯的目標系統(tǒng)一般都是內(nèi)存較小、顯示設(shè)備簡陋甚至沒有,沒有能力在其上進行本地編譯;
-
有能力進行源代碼編譯的平臺CPU架構(gòu)或操作系統(tǒng)與目標平臺不同;
交叉編譯工具鏈是進行交叉編譯的必不可少的工具,是嵌入式開發(fā)人員必須熟練掌握的技能。
為什么交叉編譯很難?
便攜式本機編譯很困難。
大多數(shù)程序是在 x86 硬件上開發(fā)的,在本地編譯的。交叉編譯會遇到兩種類型的問題:程序本身的問題和構(gòu)建系統(tǒng)的問題。
第一類問題會影響所有非 x86 目標,包括本機和交叉構(gòu)建。大多數(shù)程序?qū)\行的機器類型做出假設(shè),必須與相關(guān)平臺匹配,否則程序?qū)o法運行。常見的假設(shè)包括:
-
Word size – 將指針復(fù)制到 int 可能會在 64 位平臺上丟失數(shù)據(jù),通過乘以 4 而不是 sizeof(long) ,確定 malloc 的大小不好。整數(shù)溢出導(dǎo)致細微安全漏洞,ala“if (x+y < size) memset(src+x,0,y);”,當 x=1000 時,在 32 位硬件上產(chǎn)生 4 GB 的 memset y=0xFFFFFFF0…
-
Endianness – 不同的系統(tǒng)用不同的方式在內(nèi)部存儲二進制數(shù)據(jù),從磁盤或網(wǎng)絡(luò)中,讀取 int 或 float 數(shù)據(jù)可能需要轉(zhuǎn)換。
-
Alignment – 某些平臺(例如 arm)只能從 4 字節(jié)的偶數(shù)倍的地址,讀取或?qū)懭胝麛?shù),否則出現(xiàn)段錯誤。處理任意alignment的處理,未alignment的數(shù)據(jù)都較慢,編譯器通常會填充結(jié)構(gòu)alignment變量。將結(jié)構(gòu)視為可以發(fā)送到磁盤或通過網(wǎng)絡(luò)發(fā)送的數(shù)據(jù)塊,需要額外的工作確保一致的表示。
-
默認簽名- “char”數(shù)據(jù)類型,默認為有符號或無符號,因平臺而異(從編譯器到編譯器),導(dǎo)致一些非常令人驚訝的錯誤。簡單解決方法是提供一個編譯器參數(shù),如“-funsigned-char”,強制默認值為已知值。
-
NOMMU – 如果目標平臺沒有內(nèi)存管理單元,需要更改幾項內(nèi)容。需要 vfork(),不是 fork(),只有某些類型的 mmap() 工作(共享或只讀,但不能在寫入時復(fù)制),堆棧不會動態(tài)增長。
大多數(shù)包的目標是在本地編譯時可移植,至少會接受補丁,修復(fù)提交到適當?shù)拈_發(fā)郵件列表的任何上述問題(NOMMU 問題除外)。
然后是交叉編譯。
除了本機編譯的問題外,交叉編譯還有其自身的一系列問題:
-
配置問題- 具有單獨配置步驟的包(標準 configure/make/make install 的“./configure”部分),通常會測試字節(jié)順序或頁面大小等內(nèi)容,在本機編譯時可移植。交叉編譯時,這些值在主機系統(tǒng)和目標系統(tǒng)之間不同,在主機系統(tǒng)上運行測試,給出錯誤的答案。當目標沒有該軟件包或版本不兼容時,配置檢測主機上,是否存在軟件包支持。
-
HOSTCC vs TARGETCC -構(gòu)建過程需要編譯在主機系統(tǒng)上運行,如上述配置測試,或生成代碼的程序(如創(chuàng)建 .h 文件的 C 程序,在main構(gòu)建期間 #included )。用目標編譯器替換主機編譯器,破壞在構(gòu)建過程中運行庫。這樣的庫需要訪問主機和目標編譯器,需要說明何時使用。
-
工具鏈泄漏- 配置不當?shù)慕徊婢幾g工具鏈,將主機系統(tǒng)的一些內(nèi)容泄漏到已編譯的程序中,導(dǎo)致通常易于檢測,但難以診斷和糾正的故障。工具鏈可能 #include 錯誤的頭文件,或在鏈接時搜索錯誤的庫路徑。共享庫通常依賴于其它共享庫,可能會潛入對主機系統(tǒng)的意外鏈接時引用。
-
庫- 動態(tài)鏈接的程序必須在編譯時,訪問適當?shù)墓蚕韼?。目標系統(tǒng)的共享庫,需要添加到交叉編譯工具鏈中,以便程序可以鏈接到。
-
測試- 在本機構(gòu)建上,開發(fā)系統(tǒng)提供了方便的測試環(huán)境。交叉編譯時,確認“hello world”構(gòu)建成功,可能需要(至少)配置引導(dǎo)加載程序,內(nèi)核,根文件系統(tǒng)和共享庫。
-
腳注 1:計算機類型之間最顯著的區(qū)別是執(zhí)行程序的處理器,其它差異包括庫 ABI(例如 glibc 與 uClibc),具有可配置字節(jié)序的機器(arm 與 armeb),或不同模式的機器,可以運行 32 位和 64 位代碼(例如 x86 上的 x86-64)。
-
腳注 2:在構(gòu)建編譯器時,第三種類型稱為“加拿大交叉”,一種不在主機系統(tǒng)上運行的交叉編譯器。加拿大交叉構(gòu)建了一個編譯器,該編譯器在一個目標平臺上運行,另一臺目標機器生成代碼。首先創(chuàng)建從主機到第一個目標的臨時交叉編譯器,作為第二個目標構(gòu)建另一個交叉編譯器構(gòu)建這樣的外部編譯器。第一個交叉編譯器的目標成為運行新編譯器的主機,第二個目標是新編譯器生成輸出的平臺。這種技術(shù)通常用于為目標平臺交叉編譯新的本機編譯器。
-
腳注 3:現(xiàn)代桌面系統(tǒng)足夠快,模擬目標在模擬器下進行本地編譯,實際上是一種可行的策略。比交叉編譯慢得多,需要為目標查找或生成本機構(gòu)建環(huán)境(無論如何都必須設(shè)置交叉編譯器),可能會因模擬器和要部署的真實硬件之間的差異崩潰。
-
腳注 4:交叉編譯工具鏈傾向于為其實用程序的名稱加上前綴,ala “armv5l-linux-gcc”。如果簡單地稱為“gcc”,主機和本機編譯器就不能同時在 $PATH 中。