GCC編譯器原理(一)------交叉編譯器制作和GCC組件及命令
1.1 交叉編譯器制作
默認安裝的 GCC 編譯系統所產生的代碼適用於本機,即運行 GCC 的機器,但也可將 GCC 安裝成能夠生成其他的機器代碼。安裝一些必須的模塊,就可產生多種目標機器代碼,而且可通過命令行選擇一種希望使用的代碼。
1.1.1 目標機
從網站 http://gcc.gnu.org/install/specific.html 可以得到有可能的最新目標機列表。在此站點中可找到更新過的目標機列表,以及向各種目標機進行移植的最新信息。關於每種可能的目標機都有一個簡短介紹,可以查找說明得到有關移植的一些特殊需求。已知目標的列表非常長,而且也總在加入一些新端口。
1.1.2 創建交叉編譯器---crosstool-ng
當前已經不需要我們從源碼上進行編譯安裝一個交叉編譯器了,我們可以通過工具crosstool-ng 或是 buildroot 工具來進行交叉編譯器的制作
(1)下載
可以在官網上面直接下載
官網:http://crosstool-ng.github.io/
也可以通過git命令直接進行clone下載:
git clone https://github.com/crosstool-ng/crosstool-ng
直接執行git clone命令會在你的工作目錄中建立一個crosstool-ng目錄,裏面存放的是源碼。
(2)安裝
可以進入如下網站查看具體的安裝幫助信息:http://crosstool-ng.github.io/docs/
-
進入crosstool-ng的源碼目錄,執行如下命令進行配置:./configure --prefix=/some/place
- /some/place是自己的主機上的路徑。
- ./configure命令會檢查出自己的主機上缺少哪些軟件環境,檢查出來了逐一安裝即可。
- 執行make命令
-
執行make install命令
- 註意以上兩個命令可能會報權限不足,在命令前面加sudo解決
-
將編譯出來的bin文件加入到臨時環境變量:export PATH="${PATH}:/some/place/bin"
- 此環境變量只在當前終端中有效,關閉或者切換終端後立即失效。
-
若要在主機的任何地方任何終端上使用,必須將環境變量寫入 .bashrc中去:
-
可以通過echo命令將其寫入 .bashrc中,如下所示:
- export PATH=/some/place/bin:$PATH
- 使之生效:source /etc/bash.bashrc
- export PATH=/some/place/bin:$PATH
- 或者執行軟鏈接也可以:sudo ln -s /some/place/bin/ct-ng /usr/local/bin/ct-ng
-
可以通過echo命令將其寫入 .bashrc中,如下所示:
(3)測試
安裝成功後,執行 ct-ng 命令會打印如下信息:
(4)建立S3C2440交叉編譯工具鏈
【1】建立文件夾
建立工作文件夾:
rk3399@rk3399:~/work/tools/toolchain$ mkdir S3C2440_work src S3C2440
- S3C2440_work:為S3C2440交叉編譯工具鏈的工作目錄
- Src:存放下載下來的工具軟件源碼包
- S3C2440:存放交叉編譯工具鏈
【2】查看支持的交叉編譯工具鏈類型
執行命令:ct-ng list-samples
裏面會列出很多的交叉編譯工具樣本。介紹ARM的幾種:
- arm-unknown-eabi:基於裸板的,即無操作系統
- arm-unknown-linux-gnueabi 是基於linux的
- arm-unknown-linux-uclibcgnueabi是為uclinux用的。
- arm-cortex_a8-linux-gnueabi為cortex-a8用的。
- arm-cortexa9_neon-linux-gnueabihf:為cortex-a9帶neon用的,exynos4412就可以用此種
S3C2440選擇arm-unknown-linux-gnueabi 此種編譯器。
所有的支持的編譯器存放在rk3399@rk3399:~/work/tools/crosstool-ng/samples 此目錄下。將其arm-unknown-linux-gnueabi 中的文件拷貝到我們建立的S3C2440_work目錄下。
【3】工程配置
進入S3C2440_work目錄中,將crosstool.config 文件改名為 .config,然後執行 ct-ng menuconfig命令進行配置
-
Paths and misc options:
- Local tarballs directory:源碼包存放位置,修改為 /home/rk3399/work/tools/toolchain/src
- Prefix directory :交叉編譯工具鏈存放位置,home/rk3399/work/tools/toolchain/S3C2440
- Number of parallel jobs:工作線程,根據自己的CPU是多少核來選。
-
Target options:
- Target Architecture:默認是ARM
- Default instruction set mode:指令集模式,默認為ARM
- Architecture level:架構指令集,S3C2440為armv4t
- Emit assembly for CPU:指定目標處理器的名稱,GCC通過這個名字來決定通過匯編代碼來生成哪種指令。填入 arm9tdmi
- Tune for CPU:新版本中填入上面兩個選項就沒有這個選項了,就管了。
- Floating point:浮點類型,可以看芯片手冊的方框圖,看找不找不到對應的浮點數類型,S3C2440沒有硬件浮點,選擇軟件浮點。softfp (FPU)
-
Toolchain options:
- Tuple‘s vendor string:編譯器前綴,設為s3c2440,編譯之後就為arm-s3c2440-linux-gnueabi-
- Tuple‘s alias:給產生的交叉編譯工具起個別名,設置別名,這樣會給每個工具創建一個軟鏈接,比如arm-linux-gcc鏈接到到 arm-unknown-linux-gnueabi-gcc,可以省去自己建立軟鏈接的功夫。設置為
-
Operating System:
- Source of linux:linux源碼,這裏沒有自己需要的源碼包,可以在 .config中修改配置,系統會自動去下載。
- 剩余的其他選項保持默認即可。
架構和處理器名稱,可以在芯片手冊的方框圖中找到,如下:
【4】編譯
執行命令 ct-ng build 即可開始編譯。
制作完成會打印出下面的語句:
(5)制作 exynos 4412 編譯器
基本步驟與上面類似,不同的是在crosstool-ng中編譯器的選擇方面和工程配置上面。
Exynos 4412 選擇的是 arm-cortexa9_neon-linux-gnueabihf 編譯器,exynos 4412本身就是帶neon的,處理器本身就是屬於cortexa9系列。
配置上只是更改下Target options 中的內容:
-
Target options:
- Architecture level:架構指令集為armv7a
- Emit assembly for CPU:指定目標處理器的名稱為cortex-a9
- Floating point:與2440不同的是,a9系列帶應浮點,也就是neon,這裏要選擇硬件浮點
- Use specific FPU:這裏要填上 neon
之後就是保存編譯了。
1.1.3 編譯程序的功能
編譯程序是一個翻譯器。它讀入一種語言格式的指令(通常是文本形式的編程語言),並將它們翻譯成可在計算機上運行的指令集合(通常是二進制硬件指令的集合)。
-
編譯程序可以分為兩部分:前端和後端。
- 前端讀出程序的源代碼,將找到的內容以樹的形式轉換到內存駐留表(memory-resident table)中。一旦構造了該樹,編譯程序的後端就會讀出樹中保存的信息,並將它們轉換成目標機器上的匯編語言。
-
將源文件翻譯成可執行程序的大致步驟:
- 詞法分析是編譯程序前端的最開始部分。它從輸入中讀出字符,確定哪些是在一起的,形成符號、數字和標點符號。
- 語法分析處理會讀入來自詞法瀏覽器的符號流,以及後面跟著的一個規則集合,確定它們之間的關系。 語法分析器的輸出結果是樹結構, 會被傳遞給編譯程序的後端。
- 語法分析樹結構會被翻譯成偽匯編語言(psuedo-assembly language) ,叫做寄存器傳送語言(Register Transfer Language,RTL)。
- 編譯程序的後端由分析 RTL 代碼開始,然後執行一些優化操作。代碼中冗余和未被使用的部分會被去掉。樹中有些部分會被移動到其他位置以防止語句被不必要地多次執行。總的說來,有十個以上的優化操作,而且有些優化操作會多次瀏覽代碼。
- RTL 被翻譯成目標機器上的匯編語言。
- 激活匯編器去將匯編語言翻譯成目標文件。該文件不是可執行格式——它包括可執行的目標代碼,但並不是最終運行的形式。另外,它更可能包括未解析的到其他模塊例程和數據的引用。
- 連接程序將來自匯編器的目標文件(其中有些可能保存在包含目標文件的庫中)組合成可執行程序。
-
所有的命令行選項大致可分為三類:
- 指定語言 GCC 編譯程序有能力編譯多種語言,有些選項只可用於其中的一兩種。例如,-C89 選項只應用於 C 語言,指定適用於 1989 年的標準。
- 指定平臺 GCC 編譯程序可以為多種平臺生成目標代碼,而有些選項只能應用於為某個指定平臺生成代碼。例如,如果輸出平臺是 Intel 386,那麽-fp-ret-in-387 選項可用來指出要將函數調用返回的浮點數保存在硬件的浮點寄存器中。
- 普適 很多選項對所有語言和平臺都適用。例如,-O 選項指示編譯程序要優化輸出代碼。
例子:gcc -ansi -c muxit.c -o muxit.o
- -ansi 即是指定語言
1.2 GCC組件及命令
1.2.1 GCC組件
GCC 是由許多組件組成的,但它們也並不總是出現的。有些部分是和語言相關的,所以如果沒有安裝某種特定語言,系統中就不會出現相關的文件。
組件 |
描述 |
c++ |
gcc 的一個版本,默認語言設置為 C++,而且在連接的時候自動包含標準 C++庫。這和g++一樣 |
cc1 |
實際的 C 編譯程序 |
cc1plus |
實際的 C++編譯程序 |
collect2 |
在不使用 GNU 連接程序的系統上, 有必要運行 collect2 來產生特定的全局初始化代碼 (例如 C++的構造函數和析構函數) |
configure |
GCC 源代碼樹根目錄中的一個腳本。用於設置配置值和創建 GCC 編譯程序必需的 make程序的描述文件 |
crt0.o |
這個初始化和結束代碼是為每個系統定制的,而且也被編譯進該文件,該文件然後會被連接到每個可執行文件中來執行必要的啟動和終止程序 |
cygwin1.dll |
Windows 的共享庫提供的 API,模擬 UNIX 系統調用 |
f77 |
該驅動程序可用於編譯 Fortran |
f771 |
實際的 Fortran 編譯程序 |
g++ |
gcc 的一個版本,默認語言設置為 C++,而且在鏈接的時候自動包含標準 C++庫。這和 c++ 是一樣的 |
gcc |
該驅動程序用於執行編譯程序和連接程序以產生需要的輸出 |
gcj |
該驅動程序用於編譯 Java |
gnat1 |
實際的 Ada 編譯程序 |
gnatbind |
一種工具,用於執行 Ada 語言綁定 |
gnatlink |
一種工具,用於執行 Ada 語言連接 |
jc1 |
實際的 Java 編譯程序 |
libgcc |
該庫包含的例程被作為編譯程序的一部分,是因為它們可被連接到實際的可執行程序中。它們是特殊的例程,連接到可執行程序,來執行基本的任務,例如浮點運算。這些庫中的例程通常都是平臺相關的 |
libgcj |
運行時庫包含所有的核心 Java 類 |
libobjc |
對所有 Objective-C 程序都必須的運行時庫 |
libstdc++ |
運行時庫,包括定義為標準語言一部分的所有的 C++類和函數 |
1.2.2 GCC 編譯器命令
GCC的命令很多,常用的選項如下:
選項 |
描述 |
-c |
只編譯不鏈接。 會明確指示 GCC 去編譯源代碼,在硬盤上留下目標文件,且跳過將目標文件連接到可執行程序這一步。 缺省情況下, GCC通過用`.o‘替換源文件名後綴`.c‘, `.i‘, `.s‘,等產生目標文件名.可以使用-o選項選擇其他名字. GCC忽略-c選項後面任何無法識別的輸入文件(他們不需要編譯或匯編). 如:gcc -c hello.c |
-o file |
指定輸出的文件名 如果沒有使用 `-o‘ 選項,默認的輸出結果是:可執行文件為`a.out‘。如gcc -c hello.c -o hello.o |
-x language |
明確指出後面輸入文件的語言為language (而不是從文件名後綴得到的默認選擇).這個選項應用於後面 所有的輸入文件,直到遇著下一個`-x‘選項. language的可選值有`c‘, `objective-c‘, `c-header‘, `c++‘, `cpp-output‘, `assembler‘,和`assembler-with-cpp‘. |
-x none |
關閉任何對語種的明確說明,因此依據文件名後綴處理後面的文件(就象是從未使用過`-x‘選項). 如果只操作四個階段(預處理,編譯,匯編,連接)中的一部分,可以使用`-x‘選項(或文件名後綴)告訴 gcc從哪裏開始,用`-c‘, `-S‘,或`-E‘選項告訴gcc到 哪裏結束.註意,某些選項組合(例如, `-x cpp-output -E‘)使gcc不作任何事情. |
-S |
指示編譯程序生成匯編語言代碼,然後停止。 缺省情況下, GCC通過用 `.o‘ 替換源文件名後綴 `.c‘、`.i‘ 等等,產生 目標文件名。可以使用-o選項選擇其他名字。 匯編語言的形式依賴於編譯程序的目標平臺。如果編譯多個源文件,會為每個源文件都生成一個匯編語言模塊。 GCC忽略任何不需要編譯的輸入文件。 gcc -S helloworld.c |
-E |
預處理後即停止,不進行編譯。預處理後的代碼送往標準輸出 GCC忽略任何不需要預處理的輸入文件. gcc -E helloworld.c gcc -E helloworld.c -o helloworld.i |
-C |
告訴預處理器不要丟棄註釋,配合 `-E‘ 選項使用。 |
-P |
告訴預處理器不要產生 `#line‘ 命令,配合 `-E‘ 選項使用 |
-v |
(在標準錯誤)顯示執行編譯階段的命令.同時顯示編譯器驅動程序,預處理器,編譯器的版本號. |
-pipe |
在編譯過程的不同階段間使用管道而非臨時文件進行通信.這個選項在某些系統上無法工作,因為那些系統的 匯編器不能從管道讀取數據. GNU的匯編器沒有這個問題. |
-ldl |
表示生成的對象模塊需要用到共享庫:$ gcc say.c -ldl -o say 主要是用到了 dlopen 等函數需要用到此選項 |
-I |
指定頭文件路徑 |
-e name |
指定 name 為程序的入口地址 |
-ffreestanding |
編譯獨立的程序,不會自動鏈接 C 運行庫、啟動文件等;他隱含聲明了 `-fno-builtin‘ 選項,而且對main函數沒有特別要求。 |
-finline-functions -fno-inline-funcitons |
啟用內聯函數 關閉內聯函數 |
-g |
以操作系統的本地格式(stabs,COFF,XCOFF 或 DWARF)產生調試信息,GDB能夠使用這些調試信息。 |
-ggdb |
以本地格式(如果支持)輸出調試信息,盡可能包括GDB擴展。 |
-L <directory> |
指定鏈接時查找路徑,多個路徑之間用冒號隔開 |
-nostartfiles |
不鏈接系統標準啟動文件,比如crtbegin.o、crtend.o,而標準庫文件仍然正常使用。 |
-nostdlib |
不鏈接系統標準啟動文件和標準庫文件,只把指定的文件傳遞給連接器。 |
-static |
在支持動態連接(dynamic linking)的系統上阻止連接共享庫。即只能使用靜態鏈接。 |
-shared |
生成一個共享目標文件,他可以和其他目標文件鏈接產生可執行文件。 |
-O |
優化。對於大函數,優化編譯占用稍微多的時間和相當大的內存。 不使用 `-O‘ 選項時,編譯器的目標是減少編譯的開銷,使編譯結果能夠調試。 語句是獨立的:如果在兩條語句之間用斷點中止程序,可以對任何變量重新賦值,或者在函數體內把程序計數器指到其他語句,以及從源程序中精確地獲取你期待的結果。 不使用 `-O‘ 選項時,只有聲明了register的變量才分配使用寄存器。編譯結果比不用 `-O‘ 選項的GCC要略遜一籌. 使用了 `-O‘ 選項,編譯器會試圖減少目標碼的大小和執行時間。 如果指定了`-O‘選項,,`-fthread-jumps‘ 和 `-fdefer-pop‘ 選項將被打開。 |
-O2 |
多優化一些。 除了涉及空間和速度交換的優化選項,執行幾乎所有的優化工作。例如不進行循環展開(loop unrolling)和函數內嵌(inlining)。 和 -O 選項比較,這個選項既增加了編譯時間,也提高了生成代碼的運行效果。 |
-O3 |
優化的更多。除了打開 -O2所做的一切,它還打開了-finline-functions選項。 |
-O0 |
不優化。如果指定了多個 -O選項,不管帶不帶數字,最後一個選項才是生效的選項。 |
-Wall |
對源代碼中的多數編譯警告進行啟用 |
-fpic |
如果支持這種目標機,編譯器就生成位置無關目標碼。適用於共享庫(shared library)。 |
-fPIC |
如果支持這種目標機,編譯器就輸出位置無關目標碼。適用於動態連接(dynamic linking),即使分支需要大範圍轉移。 |
-fPIE |
使用地址無關代碼模式編譯可執行文件 |
-Xlinker option |
把選項option傳遞給連接器,可以用他傳遞系統特定的連接選項, GNU CC無法識別這些選項。 如果需要傳遞攜帶參數的選項,必須使用兩次 `-Xlinker‘,一次傳遞選項,另一次傳遞他的參數。 例如,如果傳遞 `-assert definitions‘,必須寫成 `-Xlinker -assert -Xlinker definitions‘,而不能寫成 `-Xlinker "-assert definitions"‘,因為這樣會把整個字符串當做一個參數傳遞,顯然這不是連接器期待的。 |
-Wl option |
把選項option傳遞給連接器。如果option中含有逗號,就在逗號處分割成多個選項。 |
-fomit-frame-pointer |
禁止使用 EBP 作為函數幀指針 |
-fno-builtin |
禁止 GCC 編譯器內置函數 |
-ffunction-sections |
將每個函數編譯到獨立的代碼段 |
-fdata-sections |
將全局/靜態變量編譯到獨立的數據段 |
GCC編譯器原理(一)------交叉編譯器制作和GCC組件及命令