現在 pc 平台流行的可執行檔案格式主要是 windows 下的 pe(portable executable)和 linux 下的 elf(executable linktable format)。目標檔案就是源**編譯後但未進行鏈結的那些中間檔案(windows 下的 .obj 和 linux 下的 .o),它跟可執行檔案的內容很相似,所以一般跟可執行檔案格式一起採用一種格式儲存。動態鏈結庫(windows 的 .dll 和 linux 的 .so)以及靜態鏈結庫(windows 的 .lib 和 linux 的 .a)檔案都按照可執行檔案格式儲存。
來看乙個簡單的例子:
example_1.c
#include int global_init_var = 14;
int global_uninit_var;
void func1 ( int i )
int main()
elf 檔案中的內容至少有編譯後的機器指令**、資料。沒錯,除了這些內容以外,elf 檔案中還包括了其他的資訊,比如符號表、除錯資訊、字串等。一般 elf 檔案將這些資訊按不同的屬性,以「節」(section)的形式儲存,有時候也叫「段」(segment)。程式源**編譯後的機器指令經常被放在**段中(code section)裡,**段裡常見的檔名有 「.code」 或 「.text」;全域性變數和區域性靜態變數資料通常放在資料段(data section),資料段的一般名字都叫做 「.data」。
一般 c 語言編譯後執行語句都編譯成機器**,儲存在 .text 段;已初始化的全域性變數和區域性靜態變數都儲存在 .data 段;未初始化的全域性變數和區域性靜態變數一般都放在乙個叫 .bss 段裡。我們知道未初始化的全域性變數和區域性靜態變數預設值都為 0,本來它們也可以被放在 .data 段的,但是因為它們都是 0,所以它們在 .data 段分配空間並且存放資料 0 是沒有必要的。程式執行的時候它們的確是要佔記憶體空間的,並且可執行檔案必須記錄所有未初始化的全域性變數和區域性靜態變數的大小總和,記為 .bss 段。所以 .bss 段只是為未初始化的全域性變數和區域性靜態變數預留位置而已。
總體來說,程式源**被編譯後主要分成兩種段:程式指令和程式資料。**段屬於程式指令,而資料段和 .bss 段屬於程式資料。
省去一些 elf 的繁瑣的結構,把最重要的結構提取出來:
elf 檔案結構
elf header
.text
.data
.bss
other sections ……
section header table
string header tables symbol tables ……
elf 目標檔案格式的最前部是 elf 檔案頭,它包含了描述整個檔案的基本屬性,比如 elf 檔案版本、目標機器型號、程式入口位址等。緊接著是 elf 檔案各段。其中 elf 檔案中與段有關的重要結構就是段表(section header table),該錶描述了 elf 檔案包含的所有段的資訊,比如每個段的段名、段的長度、在檔案中的偏移、讀寫許可權及段的其他屬性。
除了 .text、.data、.bss 這三個最常用的段之外,elf 檔案也有可能包含其他段,用來儲存與程式相關的資訊。
常用段名
說明.rodata1
read only data,這種段裡存放的是唯讀資料,比如字串常量,全域性 const 變數。跟 「.rodata」 一樣
.comment
存放的是編譯器版本資訊,比如字串:「gcc:(gnu)4.2.0」
.debug
除錯資訊
.dynamic
動態鏈結資訊
.hash
符號雜湊表
.line
除錯時的行號表,即源**行號與編譯後指令的對應表
.note
額外的編譯器資訊。比如程式的公司名,、發布的版本號等
.strtab
string table 字串表,用於儲存 elf 檔案中用到的各種字串
.symtab
symbol table 符號表
.plt .got
動態鏈結的跳轉和全域性入口表
.init .fini
程式初始化與總結**段
這些段的名字都是由 「.」 作為字首,表示這些表的名字是系統保留的,應用程式也可以使用一些非系統保留的名字作為段名。
elf 檔案頭中定義了 elf 魔數、檔案機器位元組長度、資料儲存方式、版本、執行平台、abi 版本、elf 重定位型別、硬體平台、硬體平台版本、入口位址、程式頭入口和長度、段表的位置和長度及段的數量等等。
elf 檔案頭及相關常數被定義在 「/usr/include/elf.h」 裡。比如說 32 位版本的檔案頭結構 「elf32_ehdr」 的定義如下:
typedef struct
各個成員的含義如下:
成員含義
sh_name
段名sh_type
段的型別
sh_flags
段標誌位
sh_addr
段虛擬位址
sh_offset
段偏移sh_size
段的長度
sh_link 和 sh_info
段鏈結資訊
sh_addralign
段位址對齊
sh_entsize
項的長度
鏈結器在處理目標檔案時,需要對目標檔案中的某些部位進行重定位,即**段和資料段中那些絕對位址的引用的位置。這些重定位資訊都記錄在 elf 檔案的重定位表裡面,對於每個需要重定位的**或者資料段,都會有乙個相應的重定位表。比如 exampl_1.o 中的 「.rel.text」 就是對 「printf」 函式的呼叫;而 「.data」 段則沒有絕對位址的引用,它只包含了幾個常量,所以就沒有針對 「.data」 段的重定位表 「rel.data」。
elf 檔案中用到了很多字串,比如段名、變數名等。因為字串的長度往往是不定的,所以用固定的結構來表示它比較困難。一種很常見做法就是把字串集中起來存放到乙個表,然後使用字串在表中的偏移來引用字串。通過這種方法,在 elf 中引用字串只需給出乙個數字下標即可,不用考慮字串長度的問題。一般字串表在 elf 檔案中也以段的形式儲存,常見的段名為 「.strtab」 或 「.shstrtab」 。這兩個字串表分別為字串表(string table)和段表字串表(section header string table)。顧名思義,字串表用來儲存普通的字串,比如符號的名字;段表字串表用來儲存段表中用到的字串,最常見的就是段名。
鏈結過程的本質就是要把多個不同的目標檔案直接相互「粘」到一起,或者說想玩具積木一樣,可以拼裝形成乙個整體。為了使不同目標檔案之間能夠互相粘合,這些目標檔案之間必須有固定的規則才行,就像積木木塊必須有凹凸部分才能夠拼合。在鏈結中,目標檔案之間相聚拼合實際上是目標檔案之間對位址的引用,即對函式和變數的位址的引用。比如目標檔案 b 要用到了目標檔案 a 中的函式 「foo」,那麼我們就稱目標檔案 a 定義了函式 「foo」,稱目標檔案 b 引用了目標檔案 a 中的函式 「foo」。這兩個概念也同樣適用於變數。每個函式或變數都有自己獨特的名字,才能避免鏈結過程中不同變數和函式之間的混淆。在鏈結中,我們將函式和變數統稱為符號,函式名或變數名就是符號名。
我們可以將符號看作是鏈結中的粘合劑,整個鏈結過程正是基於符號才能夠正確完成。鏈結過程中很關鍵的一部分就是符號的管理,每個目標檔案都會有乙個相應的符號表。這個表裡面記錄了目標檔案中所用到的所有符號。每個定義的符號有乙個對應的值,叫做符號值,對於變數和函式來說,符號值就是它們的位址。除了函式和變數之外,還存在幾種不常用到的符號。我們將符號表中所有的符號進行分類,它們有可能是下面這些型別中的一種:
定義在本目標檔案的全域性符號,可以被其他目標檔案引用。比如 example_1.o 中的 func1、main 和 global_init_var。
在本目標檔案中引用的全域性符號,卻沒有定義在本目標檔案,這一般叫做外部符號,也就是我們前面所講的符號引用。比如 example_1.o 中的 printf。
段名,這種符號往往由編譯器產生,它的值就是該段的起始位址。比如 example_1.o 中的 .text 和 .data。
區域性符號,這類符號只在編譯單元內部可見,比如 example_1.o 裡面的 static_var 和 static_var2 。偵錯程式可以使用這些符號來分析程式或崩潰時的核心轉儲檔案。這些區域性符號對於鏈結過程沒有作用,鏈結器往往也忽略它們。
行號資訊,即目標檔案指令與源**中**行的對應關係,它也是可選的。
對於我們來說,最值得關注的就是全域性符號,即上面分類中的第一類和第二類。因為鏈結過程只關心全域性符號的相互粘合,區域性符號、段名、行號等都是次要的,它們對於其它目標檔案來說時 「不可見的」 ,在鏈結過程中也是無關緊要的。我們可以使用 readelf 、objdump、nm 等來檢視 elf 檔案的符號表。
elf 檔案中的符號表往往是檔案中的乙個段,段名一般叫 「.symtab」。符號表的機構很簡單,它是乙個 elf32_sym 結構的陣列,每個 elf32_sym 結構對應乙個符號。
elf32_sym 的結構定義如下:
typedef struct
{ elf32_wrod st_name;
elf32_addr st_value;
elf32_word st_size;
unsigned char st_info;
unsigned char st_other;
elf32_half st_shndx;
成員定義如下:
成員含義
st_name
符號名。這個成員包含了該符號名在字串表中的下標
st_value
符號相對的值。這個值跟符號有關,可能是乙個絕對值,也可能是乙個位址等,不同的符號,它所對應的值含義不同。
st_size
符號大小。對於包含資料的符號,這個值是該資料型別的大小。比如乙個 double 型的符號它占用 8 個位元組。
st_info
符號型別和繫結資訊
st_other
該成員目前為 0,無用
st_shndx
符號所在的段
《程式設計師的自我修養》
ELF檔案格式
在介紹elf格式之前,先簡單說明一下可執行檔案的生成流程 1 編寫c原始檔,或彙編原始檔 2 準備共享庫格式的目標檔案 shared object file 如數學庫 標準庫 2 用編譯器 compiler 將c編譯成可重定位格式的目標檔案 relocatable object file 用彙編器 ...
ELF檔案格式
1.目標檔案 編譯器和彙編器生成可重定位目標檔案 包括共享目標檔案 鏈結器生成可執行目標檔案。2.可重定位目標檔案和可執行目標檔案的格式 可重定位目標檔案格式 可執行目標檔案格式 3.下面我們開始分析上面 而對於未被初始化的全域性變數和靜態區域性變數,編譯的時候並未被分配空間,而是僅僅在.bss段中...
ELF檔案格式
elf指executable and linking format,不僅包含可執行檔案,也包含庫檔案,包括靜態庫和動態庫。準備的說,也就是三種 這不廢話嗎 可執行檔案 靜態鏈結庫 動態鏈結庫 要觀察elf的具體資訊,可以用以下幾個工具 nm lists symbols from object fil...