交叉編譯和ABI簡介


最近處理一個問題,需要在Ubuntu下使用GCC編譯出多個平臺版本做驗證,發現對交叉編譯這塊有點模糊。導致工作效率略受影響,因此打算學習一下。

交叉編譯

交叉編譯器(Cross Compiler)就是一個可以編譯在別的平臺運行的程序的編譯器。例如在Windows上編譯安卓APK的編譯器就是交叉編譯器。
在交叉編譯中,通常將編譯可執行文件或者庫文件的機器稱之爲構建平臺,而將這些可執行文件或者庫文件運行的平臺稱爲宿主平臺。
導致交叉編譯器出現主要有一下幾個原因:

  1. 某些設備資源有限無法運行編譯器,例如8051單片機,顯然不能指望它自己編譯程序,它的資源有限到甚至系統都沒有;
  2. 需要對一套代碼編譯出在不同平臺運行的版本,比如你想讓你的程序可以運行在Windows,Ubuntu,Mac等不同系統,又不想在每一種上配置一遍編譯環境;
  3. 可以多臺機器聯合編譯,提高效率;
  4. 爲剛出現的機器編譯它的系統和編譯器等。

交叉編譯器,並不是僅僅只有一個編譯器,他還涉及鏈接器、調試器、標準庫等等,這些統稱爲工具鏈。
在GNU中,對交叉編譯器的命名有一個非強制的約定,交叉編譯器的命名形式爲:name-arch-[vendor]-os-[abi] (os = system / kernel-system),例如gcc-arm-linux-gnueabi就是編譯的目標平臺是基於AMR芯片的Linux上,由於這個名字只是個約定,因此並不是大家都遵守。可能順序上有些不同,例如可能vendorh會在arch之前。但是一般情況都會把目標平臺的架構、系統以及ABI等表示清楚。
因此交叉編譯其實要做的就是找到對應的交叉編譯器,最常用的就是確定目標平臺的硬件和系統,如果是比較主流的硬件和軟件產品例如樹梅派,一般廠商都會有提供。特別是基於GCC的交叉的交叉編譯器,例如上面提到的gcc-arm-linux-gnueabi,其實使用上和gcc沒什麼特別的,主要就是它們默認指向的頭文件路徑和生成的二進制文件不同而已。

此外,說到交叉編譯,一個必不可少的知識就是ABI。

Application Binary Interface

ABI,全稱(Application Binary Interface)一套說明程序如何在某個平臺上運行的規則。平臺的開發者定製這套規則,這個平臺可以是硬件平面,也可以是操作系統層面。定製這個規則的主要目的讓編譯器了鏈接器知道如何去編譯鏈接程序,讓他們能夠按照預期在平臺上運行,也使得不同程序之間的交互成爲可能。這裏不同的程序可以是不同語言編寫的程序編譯成的二進制文件,也可以是同種語言編譯成的庫文件,還可以是同一個程序內部不同的函數調用。
ABI主要包含以下幾個部分:

  1. 定義了函數調用標準,這是最核心的一套規則,它規定了如何將你調用一個函數這一行代碼翻譯成機器代碼;
  2. 指導如何表示一個需要暴露的函數,也稱之爲“改名(name mingling)”;
  3. 定義可以數用那些數據類型,以及它們在內存中的佈局。每種數據類型佔用多少個字節,數據是大端格式還是小端,等等。這些都需要一個標準,不然不用說不同語言之間相互不能交互,就連同一種語言不同編譯器編譯出來的二進制都可能不同。
  4. 堆棧的結構和行爲方式等多個方面,比如棧向上還是向下生長等等。

我們知道API是別的(當然也可以是編寫者自己)程序員使用的。ABI的使用者主要就是編譯器了,一般的程序員不可能也沒必要參與到ABI的定製和直接使用ABI。因此一般人沒必要了解ABI的細節。文末附有因特爾 Nios® II 處理器的ABI標準,感興趣可以瞭解一下。這裏只簡單說一下ABI中的程序調用,對ABI有個具體的瞭解。

如果學過彙編,就很容易理解這個函數調用標準是什麼,畢竟我們一般接觸最底層的應該是彙編了,直接寫機器指令的我相信有,但是我是沒機會見過了。在彙編中,我們調用一個函數,主要做下面幾個事情:

  1. 保存現場。計算單元所擁有的寄存器畢竟有限,所以我們要將目前正在執行的命令的地址、寄存器的數據保存起來,使得當從調用的函數返回後,CPU 還能正常工作;
  2. 設置調用函數所需要的參數;
  3. CPU 調轉到函數所在地址繼續執行;
  4. 程序結束後將需要返回給調用者的數據保存、清理;
  5. 恢復現場;
  6. CPU 繼續執行。

我們用高級語言編寫的程序,編譯器最終會幫我們翻譯成彙編代碼,最終翻譯成機器代碼。

感覺大家基本都這麼做的。如果所有人原本就這麼做,那何必多此一舉定一個標準?因爲上面提到的這幾不還很模糊,不具備可操作性。例如,保存現場,是保存在哪裏?參數傳遞是通過特殊寄存器傳遞還是通過棧傳遞?返回值也類似?函數調用完成後誰負責做清理工作,調用者自己清理還是特定人員去清理?這裏的每一條都要說的清清楚楚明明白白。然後編譯器按照對應平臺所設定的標準去生成正確的函數調用的彙編代碼。
當然,上面值是簡單說明了一個程序內部的函數調用的彙編代碼生成,其實還有調用外部函數庫的函數的詳細步驟,也需要一一列舉出來,編譯器和鏈接器才能正確知道如何找到和調用它們。

除了ABI,還有一個EABI。其中的E 表示Embedded,是用於嵌入式系統的ABI,主要目的是提升系嵌入式系統的性能。EABI和ABI的主要區別是EABI去掉了用戶代碼和系統內核之間的抽象,可以讓用戶代碼直接訪問硬件,提高了性能。

公衆號二維碼

本文首發於個人微信公衆號TensorBoy。微信掃描上方二維碼或者微信搜索TensorBoy並關注,及時獲取更多最新文章!

References

[1] https://en.wikipedia.org/wiki/Cross_Compile
[2] https://www.gnu.org/software/libtool/manual/html_node/Cross-compiling.html
[3] How to Survive Embedded Linux: How to Compile
[2] n2cpu_nii51016.pdf

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章