1. 程式人生 > >交叉編譯工具鏈的構建原理

交叉編譯工具鏈的構建原理

在一種計算機環境(稱為host machine)中執行的編譯程式,能編譯出在另外一種環境(稱為target machine)下執行的程式碼,叫做交叉編譯。實現這個交叉編譯的一系列工具,包括C函式庫,核心檔案,編譯器,連結器,除錯器,二進位制工具……稱為交叉編譯工具鏈
    實際上在進行嵌入式開發時,我們通常都會在主機上(host machine)使用開發板廠商提供的編譯器,偵錯程式。比如在windows上裝環境除錯51,61微控制器,在Linux上用arm-gcc寫arm開發板的程式……
    搭建交叉編譯環境是一個非常繁瑣並細緻的過程。筆者就已嵌入式Linux交叉編譯工具鏈的搭建為例,介紹下交叉編譯工具的建立過程,和原理。

主機(hostmachine),Fedora 9 Linux,  
目標機(target machine),arm,mips,sh,ppc……

一,首先介紹下交叉編譯工具鏈的組成部分。

1,編譯器,彙編
器。
    編譯器是交叉編譯工具鏈中最顯眼的部分,因為平常我們與它打交道,寫程式,編譯成binary,下到開發板上。但是請注意,真正的編譯器並不會把程式直接變成二進位制檔案,而是連結器在做這件事情。

    在Linux中,最常用的編譯器就是傳說中的gcc了,目前gcc已經到了4.4版本。編譯器做的事情是把程式,翻譯成目標機的彙編檔案(.s),然後呼叫二進位制工具的彙編器(as),變成目標檔案(.o)。

    嚴格的說,彙編器as並不屬於編譯器gcc,它和連結器ld屬於二進位制工具(binutils -- binary utilies)。

上一張圖供大家理解(gcc.jpg)。

    什麼是目標檔案(.o)?目標檔案本身是一種可連結的二進位制檔案
,目前在System V系列系統中(Linux,Unix)常用的是ELF格式。簡單的把.o畫一下吧。


2,連結器。
    連結器是把多個目標檔案變成二進位制檔案。這個檔案,可以是可執行檔案,也可以是一個動態連結庫,也可以是一個可重定位的目標檔案(更大的.o)。連結成的檔案也是ELF格式。

    連結的過程非常複雜,但原理卻又簡單,後面可以討論。連結器在Linux中用的是ld,和as一樣,在binutils程式包中。

    ELF定義比較複雜,主要是對於連結來說分了很多的section,對於執行來說邏輯上有一些segment。如果大家想了解一下ELF格式,其實是有利於對整個系統的理解和高階除錯的。需要時進行討論。

3,核心檔案。
    核心檔案是指核心標頭檔案
。用途?實際上在編譯程式(包括我們編譯核心,甚至交叉編譯工具鏈)的時候,通常要使用一些系統呼叫。核心標頭檔案用於宣告這些函式,以便連結器能夠找到相應的程式入口。
    Linux中的核心標頭檔案當然是在Linux Kernal 原始碼包裡了,下載Linux Kernel後,即可安裝標頭檔案到某目錄,供編譯器及C庫使用。請注意的是,核心標頭檔案和二進位制工具沒有依賴關係。
    為什麼要專門提出這一點?首先,有助於我們對交叉編譯工具鏈各個環境的理解。其次,很多教材都是錯的。原因:二進位制工具,實際上是根據target machine的ABI介面對程式進行規範和抽象,並不關心到底程式在做什麼。ABI=application binary interface,是system V系列系統彙編和硬體關係的介面標準。每個系列的處理器都有自己的ABI介面規範,ABI中定義了一些彙編使用規範和介面,如暫存器的使用,棧的組織,函式呼叫入口,資料對齊、格式,異常處理介面、訊號規範等等等等。

4,C函式庫。
     無論是編譯核心,還是日常的程式,甚至編譯一個C++的編譯器,都需要C語言函式庫的支援。比如,我malloc一塊記憶體,我要知道怎麼malloc,Linux Kernel中不會提供一個記憶體管理的工具。其次C庫是可以更換的,無論是靜態連結庫還是動態連結庫。由於C庫的介面標準同意,就可以對庫而不是整個系統進行升級。

     在嵌入式系統中,使用的C語言庫比較多。最強大,也最huge,最完善的莫過於GNU的glibc了,glibc也是標準的C語言庫,目前已經到了version 2.9。另外,由於嵌入式系統的靈活性,不可能將如此大的一個c庫移植到一個系統中,就衍生了很多輕量級的c庫,如uClibc,newlib。

     請注意,由於編譯器是根據C語言庫創建出來的,所以編譯器是依賴於C庫的。比如ARM中的gcc,我們一般看到兩種,一種是arm-linux-*,一種是arm-linux-*。區別就在於,arm-linux-*一般是根據glibc創建出來的,只能夠和glibc的庫使用(你使用glibc 2.8 2.9的動、靜態連線庫沒有關係,而不能使用別的c庫),但不能使用,而arm-elf-*一般使用 uClibc或者使用redhat專門為嵌入式系統的開發的C庫newlib。

     請注意C庫有個C庫的標頭檔案需要安裝,依賴於核心標頭檔案。


5,偵錯程式。
     偵錯程式可以看作是補充的工具,當然,前提是你介於牛A和牛C之間。在程式設計中經常遇到這樣那樣的問題,有個偵錯程式自然是最方便不過的了。目前支援各種環境的偵錯程式最強的莫過於GDB了,GNU的gcc或許還對於某個平臺優化的不好(例如x86環境下gcc編譯出來的程式效率就不如intel自家的c++ complier),GDB在嵌入式上強大到一統江湖……

     但偵錯程式並不只包括GDB,還有二進位制工具中的objdump,address2line,readelf等。GDB依賴於核心標頭檔案和C庫。

    請注意在程式編譯時必須加入一些除錯資訊,才能夠使用偵錯程式。常用的除錯資訊格式有stabs/stabs+, coff/xcoff/xcoff+, dwarf/dwarf+,怎麼把這些資訊加到程式中可以在gcc裡面通過-g選項開啟,一般貌似也用不到太深入的……需要用到時候在討論吧。