ELF(Executable and Linkable Format)即可執行連接檔案格式,是一種比較復雜的檔案格式,但其應用廣泛。與linux下的其他可執行檔案(a.out,cof)相比,它對節的定義和gnu工具鏈對它的支持使它十分靈活,它保存的足夠了系統相關資訊使它能支持不同平臺上的交叉編譯和交叉鏈接,可移植性很強.同時它在執行中支持動態鏈接共享庫。
通過本文,可以大致了解Linux系統中ELF格式檔案的分類,組成,作用,以及其中包含的內容。另外后面介紹了幾種常用的對elf檔案進行操作的工具,并且對其使用進行簡單舉例,便于對elf檔案有一個比較直觀的理解。
主要內容(目錄):
[描述]
1 ELF檔案簡介
2 ELF檔案格式
3 ELF的特性
[舉例]
1 readelf工具
2 objcopy工具
3 objdump工具
4 nm工具
5 ldd工具
[描述]
1 ELF檔案簡介
ELF(Executable and Linkable Format)即可執行連接檔案格式,是Linux,SVR4和Solaris2.0默認的目標檔案格式,目前標準介面委員會TIS已將ELF標準化為一種可移植的目標檔案格式,運行于32-bit Intel體系微機上,可與多種作業系統兼容。分析elf檔案有助于理解一些重要的系統概念,例如程式的編譯和鏈接,程式的加載和運行等。
(1)ELF檔案型別:
種型別的ELF檔案:
a)可重定位檔案:用戶和其他目標檔案一起創建可執行檔案或者共享目標檔案,例如lib*.a檔案。
b)可執行檔案:用于生成行程映像,載入記憶體執行,例如編譯好的可執行檔案a.out。
c)共享目標檔案:用于和其他共享目標檔案或者可重定位檔案一起生成elf目標檔案或者和執行檔案一起創建行程映像,例如lib*.so檔案。
(2)ELF檔案作用:
ELF檔案參與程式的連接(建立一個程式)和程式的執行(運行一個程式),所以可以從不同的角度來看待elf格式的檔案:
a)如果用于編譯和鏈接(可重定位檔案),則編譯器和聯結器將把elf檔案看作是節頭表描述的節的集合,程式頭表可選。
b)如果用于加載執行(可執行檔案),則加載器則將把elf檔案看作是程式頭表描述的段的集合,一個段可能包含多個節,節頭表可選。
c)如果是共享檔案,則兩者都含有。
(3)ELF檔案總體組成:
elf檔案頭描述elf檔案的總體資訊。包括:
系統相關,型別相關,加載相關,鏈接相關。
系統相關表示:elf檔案標識的魔術數,以及硬體和平臺等相關資訊,增加了elf檔案的移植性,使交叉編譯成為可能。
型別相關就是前面說的那個型別。
加載相關:包括程式頭表相關資訊。
鏈接相關:節頭表相關資訊。
下面對其進行了詳細的介紹。
2 ELF檔案格式
2.1 ELF檔案的型別
ELF檔案主要有三種型別:
(1)可重定位檔案:包含了代碼和資料.可與其它ELF檔案建立一個可執行或共享的檔案:
(2)可執行檔案:是可以直接執行的程式:
(3)共享目標檔案:包括代碼和資料,可以在兩個地方鏈接。第一,連接器可以把它和其它可重定位檔案和共享檔案一起處理以建立另一個ELF檔案;第二,動態聯結器把它和一個可執行檔案和其它共享檔案結合在一起建立一個行程映像。
2.2 ELF檔案的組織
ELF檔案參與程式的連接(建立一個程式)和程式的執行(運行一個程式),編譯器和聯結器將其視為節頭表(section header table)描述的一些節(section)的集合,而加載器則將其視為程式頭表(program header table)描述的段(segment)的集合,通常一個段可以包含多個節。可重定位檔案都包含一個節頭表,可執行檔案都包含一個程式頭表。共享檔案兩者都包含有。為此,ELF檔案格式同時提供了兩種看待檔案內容的方式,反映了不同行為的不同要求。
從鏈接的角度看,ELF檔案從開始到結束,可以看成是如下組成的:
a)ELF檔案頭
b)程式頭表(可選)
c)第1節,第2節,...,第n節,...
d)節頭表
從執行的角度看,ELF檔案從開始到結束,可以看成是如下組成的:
a)ELF檔案頭
b)程式頭表
c)第1段,第2段,...,
d)節頭表(可選)
2.3 檔案頭(Elf header)
Elf頭在程式的開始部位,作為引路表描述整個ELF的檔案結構,其資訊大致分為四部分:一是系統相關資訊,二是目標檔案型別,三是加載相關資訊,四是鏈接相關資訊。
其中系統相關資訊包括elf檔案魔數(標識elf檔案),平臺位數,資料編碼方式,elf頭部版本,硬體平臺e_machine,目標檔案版本 e_version,處理器特定標志e_ftags:這些資訊的引入極大增強了elf檔案的可移植性,使交叉編譯成為可能。目標檔案型別用e_type的值表示,可重定位檔案為1,可執行檔案為2,共享檔案為3;加載相關資訊有:程式進入點e_entry.程式頭表偏移量e_phoff,elf頭部長度 e_ehsize,程式頭表中一個條目的長度e_phentsize,程式頭表條目數目e_phnum;鏈接相關資訊有:節頭表偏移量e_shoff,節頭表中一個條目的長度e_shentsize,節頭表條目個數e_shnum ,節頭表字符索引e shstmdx。可使用命令"readelf -h filename"來察看檔案頭的內容。
檔案頭的資料結構如下:
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;//目標檔案型別
Elf32_Half e_machine;//硬體平臺
Elf32_Word e_version;//elf頭部版本
Elf32_Addr e_entry;//程式進入點
Elf32_Off e_phoff;//程式頭表偏移量
Elf32_Off e_shoff;//節頭表偏移量
Elf32_Word e_flags;/處理器特定標志
Elf32_Half e_ehsize;//elf頭部長度
Elf32_Half e_phentsize;//程式頭表中一個條目的長度
Elf32_Half e_phnum;//程式頭表條目數目
Elf32_Half e_shentsize;//節頭表中一個條目的長度
Elf32_Half e_shnum;//節頭表條目個數
Elf32_Half e_shstrmdx;//節頭表字符索引
}Elf32_Ehdr;
2.4 程式頭表(program header table)
程式頭表告訴系統如何建立一個行程映像.它是從加載執行的角度來看待elf檔案.從它的角度看.elf檔案被分成許多段,elf檔案中的代碼、鏈接資訊和注釋都以段的形式存放。每個段都在程式頭表中有一個表項描述,包含以下屬性:段的型別,段的駐留位置相對于檔案開始處的偏移,段在記憶體中的首位元組地址,段的物理地址,段在檔案映像中的位元組數.段在記憶體映像中的位元組數,段在記憶體和檔案中的對齊標記。可用"readelf -l filename"察看程式頭表中的內容。程式頭表的結構如下:
typedef struct elf32_phdr{
Elf32_Word p_type; //段的型別
Elf32_Off p_offset; //段的位置相對于檔案開始處的偏移
Elf32_Addr p_vaddr; //段在記憶體中的首位元組地址
Elf32_Addr p_paddr;//段的物理地址
Elf32_Word p_filesz;//段在檔案映像中的位元組數
Elf32_Word p_memsz;//段在記憶體映像中的位元組數
Elf32_Word p_flags;//段的標記
Elf32_Word p_align;,/段在記憶體中的對齊標記
)Elf32_Phdr;
2.5節頭表(section header table)
節頭表描述程式節,為編譯器和聯結器服務。它把elf檔案分成了許多節.每個節保存著用于不同目的的資料.這些資料可能被前面的程式頭重復使用,完成一次任務所需的資訊往往被分散到不同的節里。由于節中資料的用途不同,節被分成不同的型別,每種型別的節都有自己組織資料的方式。每一個節在節頭表中都有一個表項描述該節的屬性,節的屬性包括小節名在字符表中的索引,型別,屬性,運行時的虛擬地址,檔案偏移,以位元組為單位的大小,小節的對齊等資訊,可使用"readelf -S filename"來察看節頭表的內容。節頭表的結構如下:
typedef struct{
Elf32_Word sh_name;//小節名在字符表中的索引
E1t32_Word sh_type;//小節的型別
Elf32_Word sh_flags;//小節屬性
Elf32_Addr sh_addr; //小節在運行時的虛擬地址
Elf32_Off sh_offset;//小節的檔案偏移
Elf32_Word sh_size;//小節的大小.以位元組為單位
Elf32_Word sh_link;//鏈接的另外一小節的索引
Elf32 Word sh_info;//附加的小節資訊
Elf32 Word sh_addralign;//小節的對齊
Elf32 Word sh_entsize; //一些sections保存著一張固定大小入口的表。就像符號表
}Elf32_Shdr;
3 ELF的特性
3.1平臺相關
在ELF 檔案頭中包含了足夠的平臺相關資訊,如資料編碼方式,平臺位數,硬體平臺e_machine等,這些平臺相關資訊可在編譯由編譯器決定。例如,與平臺位數的相關的資料結構的定義在elf.h的頭檔案中.在編譯預處理時確定:
#if ELF CLASS==ELFCLASS32
extern Elf32_Dyn_DYNAMIC[];
#define elfhdr elf32_hdr;
#define elf_phdr elf32_phdr;
#define elf_note elf32_note;
#else
extern Elf64_Dyn_DYNAMIC[];
#define elfhdr elf64_hdr;
#define elf_phdr elf64_phdr;
#define elf_note elf64_note;
#endif
linux系統加載ELF可執行檔案時,必須首先做一些簡單的一致性檢查.其代碼如下:
if(memcmp(elf_ex.e_ident,ELFMAG,SELFMAG)!=0)
goto out; //檢查檔案頭開始四個字符是否為ELF魔數'\0177ELF
if(elf_ex.e_type!=ET_EXEC&&elf_ex.e_type!=ET_DYN)
goto out;//檢查檔案型別是否為可執行檔案或共享目標檔案
if(!elf_check_arch(&elf_ex))
goto out;//檢查硬體平臺是否一致
其中的elf_check_arch(x)在不同的硬體平臺上有不同的定義,其由系統的硬體平臺決定。這樣,在硬體平臺相同的系統上,ELF可以不作修改的執行。因此,它可以支持不同平臺上的交叉編譯(cross_compilation)和交叉鏈接(cross_linking)。
3.2 PIC
ELF可以生成一種特殊的代碼——與位置無關的代碼(position-independent code,PIC)。用戶對gcc使用-fPIC指示GNU編譯系統生成PIC代碼。它是實作共享庫或共享可執行代碼的基礎.這種代碼的特殊性在于它可以加載到記憶體地址空間的任何地址執行.這也是加載器可以很方便的在行程中動態鏈接共享庫。
PIC的實作運用了一個事實,就是代碼段中任何指令和資料段中的任何變數之間的距離都是一個與代碼段和資料段的絕對存盤器位置無關的常量。因此,編譯器在資料段開始的地方創建了一個表.叫做全域偏移量表(global offset table.GOT)。GOT包含每個被這個目標模塊參考的全域資料目標的表目。編譯器還為GOT中每個表目生成一個重定位記錄。在加載時,動態聯結器會重定位GOT中的每個表目,使得它包含正確的絕對地址。PIC代碼在代碼中實作通過GOT間接的參考每個全域變數,這樣,代碼中本來簡單的資料參考就變得復雜,必須加入得到GOT適當表目內容的指令。對只讀資料的參考也根據同樣的道理,所以,加上 IC編譯成的代碼比一般的代碼開銷大。
如果一個elf可執行檔案需要呼叫定義在共享庫中的任何函式,那么它就有自己的GOT和PLT(procedure linkage table,程序鏈接表).這兩個節之間的互動可以實作延遲系結(lazy binging),這種方法將程序地址的系結推遲到第一次呼叫該函式。為了實作延遲系結,GOT的頭三條表目是特殊的:GOT[0]包含.dynamic 段的地址,.dynamic段包含了動態聯結器用來系結程序地址的資訊,比如符號的位置和重定位資訊;GOT[1]包含動態聯結器的標識;GOT[2]包含動態聯結器的延遲系結代碼的入口點。GOT的其他表目為本模塊要參考的一個全域變數或函式的地址。PLT是一個以16位元組(32位平臺中)表目的陣列形式出現的代碼序列。其中PLT[0]是一個特殊的表目,它跳轉到動態聯結器中執行;每個定義在共享庫中并被本模塊呼叫的函式在PLT中都有一個表目,從 PLT[1]開始.模塊對函式的呼叫會轉到相應PLT表目中執行,這些表目由三條指令構成。第一條指令是跳轉到相應的GOT存盤的地址值中.第二條指令把函式相應的ID壓入堆疊中,第三條指令跳轉到PLT[O]中呼叫動態聯結器決議函式地址,并把函式真正地址存入相應的GOT表目中。被呼叫函式GOT相應表目中存盤的最初地址為相應PLT表目中第二條指令的地址值,函式第一次被呼叫后.GOT表目中的值就為函式的真正地址。因此,第一次呼叫函式時開銷比較大.但是其后的每次呼叫都只會花費一條指令和一個間接的存盤器參考。
3.3 強大的工具支持
由于gnu有大量的工具支持elf檔案格式.隨著gnu工具的功能的擴展.程式員對ELF檔案的運用也越來越靈活。例如,在C++中全域的建構式和解構式必須非常小心的處理碰到的語言規范問題。建構式必須在main函式之前被呼叫。解構式必須在main函式回傳之后被呼叫。ELF檔案格式中,定義了兩個特殊的節 (section),.init和.fini,.init保存著可執行指令,它構成了行程的初始化代碼。當一個程式開始運行時,在main函式被呼叫之前 (c語言稱為main),系統安排執行這個section的中的代碼。.fini保存著可執行指令,它構成了行程的終止代碼。當一個程式正常退出時.系統安排執行這個section的中的代碼。C++編譯器利用這個特性.構造正確的.init和.fini sections.并結合.ctors(該section保存著程式的全域的建構式的指標陣列)和.dtors(該section保存著程式的全域的解構式的指標陣列)兩個section,完成全域的建構式和解構式的處理。
GCC還有許多擴展的特性.有些對ELF 特別的有用。其中一個就是_attribute_ 。使用_attribute_可以使一個函式放到_CTOR_LIST_或者_DTOR_LIST_里。 _attribute_((constructor))促使函式在進入main之前會被自動呼叫。_attribute_((destructor))促使函式在main回傳或者exit呼叫之后被自動呼叫。這種函式必須是不能帶引數的而且必須是static void型別的函式。在ELF下,這個特性在一般的可執行檔案和共享庫中都能很好的作業。另外一個GCC的特性是 attribute_(section("sectionname")),使用這個,能把一個函式或者是資料結構放到任何的section中。
uj5u.com熱心網友回復:
https://refspecs.linuxfoundation.org/轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/59223.html
標籤:內核源代碼研究區
上一篇:ENVI不能用
下一篇:Ubuntu一段時間后自動斷網
