文章目錄
- 前言
- 一、準備工作
- 二、編譯過程
-
- 1. 預處理
- 2. 編譯
- 3. 彙編
- 4. 連結
- 三、分析 ELF 檔案
-
- 1. ELF 檔案的段
- 2. 反彙編 ELF
前言
GCC(GNU C Compiler)是編譯工具。本文所要介紹的将 C/C++語言編寫的程式轉換成為處理器能夠執行的二進制代碼的過程即由編譯器完成。
一、準備工作
由于 GCC 工具鍊主要是在 Linux 環境中進行使用,是以本文也将以 Linux 系統作為工作環 境。為了能夠 示範編譯的整個 過程,先建立一 個工作目test0,然後用文本編輯器生成一個 C 語言編寫的簡單 hello.c 程式為示例,其源代碼如下所示
#include <stdio.h>
//此程式很簡單,僅僅列印一個 Hello World 的字元串。
int main(void)
{
printf("Hello World! \n");
return 0;
}
二、編譯過程
1. 預處理
預處理的過程主要包括以下過程:
(1) 将所有的#define 删除,并且展開所有的宏定義,并且處理所有的條件預編譯指令,比如#if #ifdef #elif #else #endif 等。
(2) 處理#include 預編譯指令,将被包含的檔案插入到該預編譯指令的位置。
(3) 删除所有注釋“//”和“”。
(4) 添加行号和檔案辨別,以便編譯時産生調試用的行号及編譯錯誤警告号。
(5) 保留所有的#pragma 編譯器指令,後續編譯過程需要使用它們。
使用 gcc 進行預處理的指令如下:

将源檔案 hello.c 檔案預處理生成 hello.i,GCC 的選項-E 使 GCC 在進行完預處理後即停止。
2. 編譯
編譯過程就是對預處理完的檔案進行一系列的詞法分析,文法分析,語義分析及優化後生成相應的彙編代碼。
使用 gcc 進行編譯的指令如下:
将預處理生成的 hello.i 檔案編譯生成彙程式設計式 hello.s,GCC 的選項-S 使 GCC 在執行完編譯後停止,生成彙程式設計式
3. 彙編
彙編過程調用對彙編代碼進行處理,生成處理器能識别的指令,儲存在字尾為.o的目标檔案中。由于每一個彙編語句幾乎都對應一條處理器指令,是以,彙編相對于編譯過程比較簡單,通過調用 Binutils 中的彙編器 as 根據彙編指令和處理器指令的對照表一一翻譯即可。
當程式由多個源代碼檔案構成時,每個檔案都要先完成彙編工作,生成.o 目标檔案後,才能進入下一步的連結工作。注意:目标檔案已經是最終程式的某一部分了,但是在連結之前還不能執行。
使用 gcc 進行彙編的指令如下:
将編譯生成的 hello.s 檔案彙編生成目标檔案 hello.o,GCC 的選項-c 使 GCC 在執行完彙編後停止,生成目标檔案。
注意:hello.o 目标檔案為 ELF(Executable and Linkable Format)格式的可重定向檔案。
4. 連結
連結也分為靜态連結和動态連結,其要點如下:
(1) 靜态連結是指在編譯階段直接把靜态庫加入到可執行檔案中去,這樣可執行檔案會比較大。連結器将函數的代碼從其所在地(不同的目标檔案或靜态連結庫中)拷貝到最終的可執行程式中。為建立可執行檔案,連結器必須要完成的主要任務是:符号解析(把目标檔案中符号的定義和引用聯系起來)和重定位(把符号定義和記憶體位址對應起來然後修改所有對符号的引用)。
(2) 動态連結則是指連結階段僅僅隻加入一些描述資訊,而程式執行時再從系統中把相應動态庫加載到記憶體中去。
由于連結動态庫和靜态庫的路徑可能有重合,是以如果在路徑中有同名的靜态庫檔案和動态庫檔案,比如 libtest.a 和 libtest.so,gcc 連結時預設優先選擇動态庫,會連結libtest.so,如果要讓 gcc 選擇連結 libtest.a 則可以指定 gcc 選項-static,該選項會強制使用靜态庫進行連結。以 Hello World 為例:如果使用指令“gcc hello.c -o hello”則會使用動态庫進行連結,生成的 ELF 可執行檔案的大小(使用 Binutils 的 size 指令檢視)和連結的動态庫(使用 Binutils 的 ldd 指令檢視)如下所示
ldd hello 可以看出該可執行檔案連結了很多其他動态庫,主要是 Linux 的 glibc 動态庫
如果使用指令 gcc -static hello.c -o hello 則會使用靜态庫進行連結,生成的 ELF 可執行檔案的大小(使用 Binutils 的 size 指令檢視)和連結的動态庫(使用 Binutils 的 ldd 指令檢視)如下所示
由此可以看出 text 的代碼尺寸變得極大
連結器連結後生成的最終檔案為 ELF 格式可執行檔案,一個 ELF 可執行檔案通常被連結為不同的段,常見的段譬如.text、.data、.rodata、.bss 等段
三、分析 ELF 檔案
1. ELF 檔案的段
ELF 檔案格式如下圖所示,位于 ELF Header 和 Section Header Table 之間的都是段(Section)。一個典型的 ELF 檔案包含下面幾個段:
- text:已編譯程式的指令代碼段。
- rodata:ro 代表 read only,即隻讀資料(譬如常數 const)。
- data:已初始化的 C 程式全局變量和靜态局部變量。
- bss:未初始化的 C 程式全局變量和靜态局部變量。
- debug:調試符号表,調試器用此段的資訊幫助調試。 可以使用 readelf -S 檢視其各個 section 的資訊如下:
Linux系統下GCC常用指令以及EFF檔案格式前言一、準備工作二、編譯過程三、分析 ELF 檔案 Linux系統下GCC常用指令以及EFF檔案格式前言一、準備工作二、編譯過程三、分析 ELF 檔案
2. 反彙編 ELF
由于 ELF 檔案無法被當做普通文本檔案打開,如果希望直接檢視一個 ELF 檔案包含的指令和資料,需要使用反彙編的方法。
使用 objdump -D 對其進行反彙編如下:
使用 objdump -S 将其反彙編并且将其 C 語言源代碼混合顯示出來: