ELF文件格式详解-请查收
上一篇文章中主要介紹了ELF文件的基本定義和目標文件的分類,這篇文章中主要介紹下ELF文件格式
ELF文件主要提供了兩個視圖:鏈接視圖及執(zhí)行視圖,分別針對程序運行過程的鏈接過程和執(zhí)行過程。如下圖所示:
單從相貌上來看,二者長得很像,除了肚子。可以看出鏈接視圖采用的節(jié)(section)作為基本單位,而執(zhí)行視圖采用(segment)為基本單位。那么問題來了,這二者有什么區(qū)別?這是個設問句,區(qū)別如下:
Section與segment
在匯編語言中,代碼段、數(shù)據(jù)段就是所謂的段(segment)。在可執(zhí)行文件載入內(nèi)存時,是以segment組織的,每個segment對應ELF文件中的program header中的一個條目,用來建立可執(zhí)行文件映像。
在寫匯編程序時,用.bss .data .txt或者data segment等這些段前指示,都是section,比如.text,告訴匯編器后面的代碼放入.text section中。目標代碼文件中的section和section header table中的條目是一一對應的。Section的信息用于鏈接器對代碼的重定位。
可能還是不太懂,可以看下匯編程序的執(zhí)行過程:
具體過程如下:
寫一個匯編程序或通過編譯器會產(chǎn)生文本文件hello.s。匯編器讀取hello.s時會將其轉(zhuǎn)換為目標文件hello.o,目標文件有若干個section組成,我們在匯編程序中聲明的.section會成為目標文件中的section,此外匯編器還會自動添加一些section(比如符號表)。意思就是不同目標文件中相同的section會被合并成生成文件的一個section。
然后鏈接器把目標文件中的section合并成幾個segment,生成可執(zhí)行文件Hello.o。最后又加載器(loader)根據(jù)可執(zhí)行文件中的segment信息加載運行這個程序。
大概是這樣子的,吧?如果有不太對的地方歡迎指正。
然后針對兩個視圖中的每一部分進行詳細解釋,在此之前,我們熟悉一個命令readelf,其常用的命令格式如下
readelf -h [filename] //查看文件頭 readelf -S [filename] //查看文件節(jié)頭表 readelf -l [filename] //查看程序頭表 readelf -s [filename] //查看符號表 readelf -a [filename] //查看重定位表、符號表等信息 readelf -r [filename] //只查看重定位表信息/*注意這里的filename是elf文件的文件名,常見elf文件后綴有.o、.out等*/1.ELF文件頭解析
采用readelf -h xxx.o查看頭部信息
我這里創(chuàng)建了一個簡單的say hello的hello.o文件,頭部信息如下
我們看一下文件頭在elf.h文件中的結(jié)構(gòu)體定義,會發(fā)現(xiàn)其有14個成員
類似于程序頭表、節(jié)頭表,elf文件頭同時定義了32位及64位結(jié)構(gòu)體,二者的區(qū)別只有地址位數(shù)的不同,其他成員的數(shù)據(jù)長度及類型都是相同的:
如下所示:
/* Type of addresses. */ typedef uint32_t Elf32_Addr; /* 32位 */ typedef uint64_t Elf64_Addr; /* 64位 *//* Type of file offsets. */ typedef uint32_t Elf32_Off; /* 32位 */ typedef uint64_t Elf64_Off; /* 64位 */| Elf32_Addr | 4 | 4 | uint32_t |
| Elf64_Addr | 8 | 8 | uint64_t |
| Elf32_Half/Elf64_Half | 2 | 2 | uint16_t |
| Elf32_Off | 4 | 4 | uint32_t |
| Elf64_Off | 8 | 8 | uint64_t |
| Elf32_Sword/Elf64_Sword | 4 | 4 | int32_t |
| Elf32_Word/Elf64_Word | 4 | 4 | uint32_t |
其14個成員如下圖所示:
2.ELF節(jié)頭表
采用readelf -S xxx.o查看節(jié)頭表信息
查看源文件(以Elf32_Shdr為例)
/* Section header. */ typedef struct { Elf32_Word sh_name; /* Section name (string tbl index) */ Elf32_Word sh_type; /* Section type */ Elf32_Word sh_flags; /* Section flags */ Elf32_Addr sh_addr; /*Section virtual addr at execution */ Elf32_Off sh_offset; /* Section file offset */ Elf32_Word sh_size; /* Section size in bytes */ Elf32_Word sh_link; /* Link to another section */ Elf32_Word sh_info; /* Additional section information */ Elf32_Word sh_addralign; /* Section alignment */ Elf32_Word sh_entsize; /* Entry size if section holds table */ } Elf32_Shdr;在節(jié)頭表中,包含10個成員,如圖所示:
成員說明如下:
| sh_name | 給出節(jié)的名稱,本身不是字符串,而是數(shù)值,代表目標文件字符串表中的一個索引值。 |
| sh_type | 指出節(jié)類型 |
| sh_flags | 標志位 |
| sh_addr | 如果節(jié)將出現(xiàn)在進程的內(nèi)存映像中,此成員指定該節(jié)數(shù)據(jù)在內(nèi)存中的起始地址,否則,如該節(jié)數(shù)據(jù)不需要映射到內(nèi)存中時,此字段為0 |
| sh_offset | 該節(jié)數(shù)據(jù)在目標文件中的偏移量。需要注意的是:該節(jié)類型是SHT_NOBITS(如.bss節(jié)),說明該節(jié)在目標文件中并不占用空間大小,此時,該成員的數(shù)值只是概念上的偏移 |
| sh_size | 節(jié)的大小(字節(jié)數(shù))。當該節(jié)類型不是SHT_NOBITS時,成員數(shù)值就應該是該節(jié)在目標文件的實際大小。 |
| sh_link | 節(jié)頭部表的索引鏈接 |
| sh_info | 附加信息,其會因為節(jié)類型的不同而不同。特別的,類型為SHT_REL或SHT_RELA的節(jié),此時成員代表的是一個節(jié)頭表索引值,指代需要做重定位操作的節(jié) |
| sh_addralign | 地址對齊的約束條件,數(shù)值0、1沒有對其約束 |
| sh_entsize | 如果節(jié)的內(nèi)容是固定大小的表,例如符號表,此時成員sh_entsize給出的是每個表項的大小。如果該成員的數(shù)值為0,說明該節(jié)的數(shù)據(jù)內(nèi)容并不是一張表 |
節(jié)類型(sh_type)說明:
| SHT_NULL | 說明節(jié)頭表表項無效,目標中沒有它對應的節(jié) |
| SHT_PROGBITS | 表明該節(jié)有程序定義的信息,這些信息的格式與意義完全由程序所定義 |
| SHT_SYMTAB/SHT_DYNSYM | 說明節(jié)的數(shù)據(jù)內(nèi)容為符號表,SHT_SYMTAB:為靜態(tài)鏈接器提供符號信息,SHT_DYNSYM:為動態(tài)連接器提供符號信息 |
| SHT_STRTAB | 說明該節(jié)是字符串表 |
| SHT_RELA | 說明該節(jié)的內(nèi)容是重定位表,表項帶Addend |
| SHT_HASH | 說明該節(jié)包含符號哈希表 |
| SHT_DYNAMIC | 說明該節(jié)內(nèi)容是用來提供給動態(tài)鏈接器使用的 |
| SHT_NOTE | 包含系統(tǒng)構(gòu)建人員需要使用的特殊信息 |
| SHT_NOBITS | 在文件中不占空間,其他屬性類似于SHT_PROGBITS |
| SHT_REL | 說明該節(jié)內(nèi)容是一個重定位表,表項不帶Addend |
| SHT_INIT_ARRAY | 說明該節(jié)存儲的是一個函數(shù)指針數(shù)組 |
| and | so on… |
節(jié)類型中的SHT_RELA和SHT_REL的區(qū)別其實不太大,知道他們都是重定位表,應該就可以了。
原文中是這么說的,英語好的同學可以琢磨一下>
SHT_RELA
The section holds relocation entries with explicit addends, such as type Elf32_Relafor the 32-bit class of object files. An object file may have multiple relocation sections.
SHT_REL
The section holds relocation entries without explicit addends, such as type Elf32_Rel for the 32-bit class of object files. An object file may have multiple relocation sections. See ‘‘Relocation’’ below for details.
標志位說明如下:
特殊節(jié)如下所示:
如果節(jié)sh_link的值為1,則其可鏈接到本程序的相關(guān)節(jié)
若sh_info的值為1,則其可鏈接到符號表內(nèi)的索引+1
查看符號表:readelf -s hello.o
學習的時候要笑!接下來笑著>_<看程序頭表
3.程序頭表
采用readelf -l xxx.o查看文件的程序頭表
查看定義
有8個成員
P_type類型描述:
4.符號表
符號:對數(shù)據(jù)、函數(shù)或者代碼(如全局變量或者函數(shù))的引用
符號表:簡單理解就是通過索引對數(shù)據(jù)、函數(shù)、變量的調(diào)用
例如:在使用printf()函數(shù)時,就會調(diào)用動態(tài)符號表.dynsym,因為在符號表.dynsym中存在一個指向該函數(shù)的符號條目。
在大多數(shù)共享庫和可執(zhí)行文件中,存在兩個符號表:.symtab、.dynsym。
.dynsym保存了引用來自外部文件的全局符號,如printf這樣的庫函數(shù)。可以說.dynsym是.symtab的一個子集,因為.symtab中還存有可執(zhí)行文件的本地符號,如全局變量或者代碼定義的本地函數(shù)等,因此.symtab保存了所有的符號,而.dynsym只保存了動態(tài)/全局符號。
但存在一個問題,為什么同時定義包含相同部分的兩個符號表呢?在采用readelf -S 查看可執(zhí)行文件的輸出時,我們發(fā)現(xiàn):.dynsym被標記了ALLOC,即運行時會被內(nèi)存調(diào)用,而.symtab并沒有。可以看出,在程序運行的時候,.symtab并不是必需的。因此不會被裝載到內(nèi)存中。而.dynsym保存的符號只能在運行時被解析,因此是運行時動態(tài)鏈接器所需要的唯一符號。可以說:.dynsym符號表對于動態(tài)鏈接可執(zhí)行文件的執(zhí)行來說是必需的,而.symtab符號表只是用來調(diào)試和鏈接的,有時候為了節(jié)省空間,會將.symtab符號表刪掉
符號表查看,readelf -s hello.o,如下圖所示:
符號類型說明:
符號綁定說明:
5.重定位表
靜態(tài)鏈接的過程分為兩步:第一步是空間與地址的分配,第二步是符號解析與重定位;第二步是鏈接過程的核心。在重定位過程中,重定位表和符號表起著至關(guān)重要的作用,重定位表確定了需要被重定位的地址,符號表決定了其需要被替換成什么值。
可采用readelf -a test.o或readelf -r test.o查看文件重定位表,不同的是,后者只顯示重定位表,如圖所示:
靜態(tài)鏈接中,目標文件中包含有用于重定位的表:代碼段重定位表.rel.text;數(shù)據(jù)段重定位表.rel.data
動態(tài)鏈接中,目標文件的重定位表:.rel.dyn對數(shù)據(jù)引用的修正,修正的位置位于.got和數(shù)據(jù)段;.rel.plt對函數(shù)引用的修正,修正位置位于.got.plt。
如上圖所示,我們可以觀察到幾種重定位入口類型
R_X86_64_RELATIVE, R_X86_64_GLOB_DAT,
R_X86_64_JUMP_SLO
R_X86_64_GLOB_DAT(.rel.dyn中針對.got)和R_X86_64_JUMP_SLO(.rel.plt中針對.got.plt)表示被修正的位置只需要直接將符號的地址填入。
而在重定位表中的Offset表明了當前符號在.got或.got.plt中的偏移,可以根據(jù)該值在GOT表中尋找對應的位置。
總結(jié)
以上是生活随笔為你收集整理的ELF文件格式详解-请查收的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 哈利波特禁林竖琴有什么用
- 下一篇: 流水线问题--计算机体系结构