ELF文件詳解
ELF文件分為四個部分:elf header,program header table,section header table,dynamic symbol table
其中節(jié)頭表(section header table) 和 段頭表(program header table) 中用到的數據相同,只是組織方式不同
一、ELF header
每個ELF文件都必須存在一個ELF_Header,這里存放了很多重要的信息用來描述整個文件的組織,如: 版本信息,入口信息,偏移信息等,程序執(zhí)行也必須依靠其提供的信息

image-20200617152243112
Magic 前4字節(jié): \x7F、'E'、'L'、'F' 文件標識
第五字節(jié):文件類別,0(無效類型)、1(32位)、2(64位)
第六字節(jié):數據編碼,0(無效編碼)、1(小端)、2(大端)
第七字節(jié):文件版本,1(當前版本)
第八字節(jié):補齊字節(jié)開始處
之后魔數:留用
Class 類別
Data 2 補碼,小端序(little endian)
Version 版本
OS/ABI 系統(tǒng)
Type EXEC(Executable file 可執(zhí)行文件)
Machine ARM平臺
Version 版本
Entry point address 入口點地址
Start of program headers 程序頭開始地址
Start of section headers 節(jié)區(qū)頭開始地址
Flags 標識
Size of this header 本頭大小
Size of program headers 程序頭大小
Number of program headers 程序頭數量
Size of section headers 節(jié)頭大小
Number of section headers 節(jié)頭數量
Section header string table index 節(jié)頭字符串表索引
數據結構如下
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT]; ##magic魔數
ELF32_Half e_type; ##類別
ELF32_Half e_machine; ##系統(tǒng)架構:3(Intel 80386)
ELF32_Word e_version; ##版本:2(可執(zhí)行文件)
ELF32_Addr e_entry; ##入口點地址
ELF32_Off e_phoff; ##程序頭部表
ELF32_Off e_shoff; ##節(jié)區(qū)頭部表
ELF32_Word e_flags; ##標志
ELF32_Half e_ehsize; ##本頭的大小
ELF32_Half e_phentsize; ##程序頭大小
ELF32_Half e_phnum; ##程序頭數量
ELF32_Half e_shentsize; ##節(jié)頭大小
ELF32_Half e_shnum; ##節(jié)頭數量
ELF32_Half e_shstrndx; ##字符串表索引節(jié)頭
} Elf32_Ehdr;
在系統(tǒng)中是以e_xxx存在的,但是在elfread中,為了看著方便,給我們另一種展示,如下圖
e_xxx 和上面對應表如下圖:

image-20200617231624311

image-20200617231649152
其中數據類型如下
| 名稱 | 長度 | 對齊方式 | 用途 |
|---|---|---|---|
| Elf32_Addr | 4 | 4 | 無符號程序地址 |
| Elf32_Half | 2 | 2 | 無符號半整型 |
| Elf32_Off | 4 | 4 | 無符號文件偏移 |
| Elf32_Sword | 4 | 4 | 有符號大整型 |
| Elf32_Word | 4 | 4 | 無符號大整型 |
| unsigned char | 1 | 1 | 無符號小整型 |
二、Program header table 程序頭表
存儲so文件運行時所需要的信息,這部分信息會直接被linker使用,用于加載so文件,告訴系統(tǒng)如何在內存中創(chuàng)建映像,在圖中也可以看出來,有程序頭部表才有段,有段就必須有程序頭部表,其中存放各個段的基本信息(包括地址指針)

image-20200617155215988
上圖中的程序頭表列出了8個段,這些組成了最終在內存的執(zhí)行程序
PHDR 保存程序頭表
offset 偏移值
VirtAddr 虛擬內存中地址,加載后會被重定向,readelf的時候顯示的是默認值,如上圖的0x00008034
PhysAddr 和上面類似,基本用不到
FileSiz 文件大小
MemSiz 內存中大小
Flg R讀 W寫 X執(zhí)行
Align 對齊 0x4是4字節(jié)對齊
INTERP 程序已經從可執(zhí)行映射到內存后,必須調用解釋器,在這里解釋器并不意味著二進制文件的內存必須由另一個程序解釋,它指的是這樣的一個程序:通過鏈接其他庫,來滿足未解決的引用,在ELF文件中保存的實際為/system/bin/linker
LOAD 表示一個從二進制文件映射到虛擬地址空間的段,其中保存了常量數據(如字符串),程序的目標代碼等
DYNAMIC 保存了其他動態(tài)鏈接器(INTERP中指定的解釋器)使用的信息
GNU_STACK 未定義的,unknow
EXIDX 未定義的,unknow
GNU_RELRO 未定義的,unknow
NOTE 保存了專有信息
節(jié)到段的映射

image-20200617223713107
鏈接視圖是以節(jié)(section)為單位,執(zhí)行視圖是以段(segment)為單位。鏈接視圖就是在鏈接時用到的視圖,而執(zhí)行視圖則是在執(zhí)行時用到的視圖。上圖左側的視角是從鏈接來看的,右側的視角是執(zhí)行來看的
段(Segment): 就是將文件分成一段一段映射到內存中,段中通常包括一個或多個節(jié)區(qū)
那么為什么需要節(jié)和段兩種視圖? 當ELF文件被加載到內存中后,系統(tǒng)會將多個具有相同權限(flg值)section合并一個segment。操作系統(tǒng)往往以頁為基本單位來管理內存分配,一般頁的大小為4096B,即4KB的大小。同時,內存的權限管理的粒度也是以頁為單位,頁內的內存是具有同樣的權限等屬性,并且操作系統(tǒng)對內存的管理往往追求高效和高利用率這樣的目標。ELF文件在被映射時,是以系統(tǒng)的頁長度為單位的,那么每個section在映射時的長度都是系統(tǒng)頁長度的整數倍,如果section的長度不是其整數倍,則導致多余部分也將占用一個頁。而我們從上面的例子中知道,一個ELF文件具有很多的section,那么會導致內存浪費嚴重。這樣可以減少頁面內部的碎片,節(jié)省了空間,顯著提高內存利用率

image-20200617223826822
readelf -S xxx # 用來查看可執(zhí)行文件中有哪些section,如下圖

image-20200617224647502
readelf --segments xxx # 可以查看該文件的執(zhí)行視圖,下圖紅框部分為上圖的節(jié)信息在段中的顯示

image-20200617224748561
最后加載進內存的只有program header table 程序頭表里的load段,其他都只是描述信息,加載過程中用到,但是最后加載進去內存的只有l(wèi)oad段

image-20200617235850414
三、Section header table 節(jié)頭部表
類似與程序頭部表,但與其相對應的是節(jié)區(qū)(Section)
節(jié)區(qū)(Section): 將文件分成一個個節(jié)區(qū),每個節(jié)區(qū)都有其對應的功能,如符號表,哈希表等

image-20200617224647502
.interp 程序解釋器的絕對路徑,類似于linker的絕對路徑
.dynamic 指的是包含當前so文件中的依賴信息
.dynstr 指的是包含了自己寫的函數的函數名,和導入函數的函數名
.hash 表示導入導出函數的函數名稱的hash表
.relname和.relaname: 解釋在下面圖中
.plt 程序鏈接表,具體解釋在下面圖中
.text 已編譯程序的機器代碼
.note 指明編譯器信息
.ARM.extab 標識設備信息
.fini 類似于Android里面的onDestory,用來進入銷毀階段,進行釋放資源等一系列操作
.init_array 初始化函數的地址
.preinit_arry 執(zhí)行時先執(zhí)行init_array,再執(zhí)行的這個
.dynsym 是上面.dynstr的子集,表示導入的函數的函數名
.got 導入函數的地址和一些全局變量的地址,實際是放在data數據段的
.data 數據區(qū)
.bss 保存未初始化數據,當程序運行時,系統(tǒng)用0初始化該區(qū)域,該section不占用系統(tǒng)空間
.comment 版本控制信息
.note.gnu.gold-ve 當前處理器信息
.ARM.attributes 標識設備信息
.shstrtab 存在所有節(jié)名稱的字符串表
.bug 指的是ELF生成中間文件時候產生的debug信息
.line 類似于smali代碼中的.line,smali表示該行代碼在java中的位置,這里就表示該行代碼在.c文件中的位置
.relname和.relaname: 010Editor打開so,展現(xiàn)形式為下圖,.rel.dyn 和 .rel.plt ,是用來重定向dyn和plt的,也就是靜態(tài)情況下,存放偏移值,如果進行動態(tài)調試的時候,就會加上基址變成絕對地址(重定向)
下面第二張圖中,左邊紅框就是偏移值,右邊紅框只要把基址加進來,就是絕對地址,把基址加進來的過程就是重定向的過程

image-20200617233702555

image-20200617234205166
.plt 程序鏈接表,用于做映射關系,拿到依賴so的絕對地址,做重定向的

image-20200617233823702