在分析Binonic的Linker之前辑奈,我們先介紹Android的可執(zhí)行文件格式进萄,linker本事并不是很復(fù)雜,但是磁玉,如果對(duì)可執(zhí)行文件格式不了解停忿,就不太容易理解程序的邏輯。了解可執(zhí)行文件的另一個(gè)好處是能開(kāi)發(fā)出一些特殊功能的軟件蚊伞。Hook系統(tǒng)API軟件
Android的可執(zhí)行文件和動(dòng)態(tài)庫(kù)就是Linux的ELF文件格式席赂,但是,由于使用了Android自己的linker时迫,因此颅停,和普通的Linux系統(tǒng)不完全兼容。
ELF是executable and Linking Format (可執(zhí)行連接格式)的縮寫(xiě)掠拳,最初由UNIX系統(tǒng)實(shí)驗(yàn)室發(fā)布癞揉,它是應(yīng)用程序二進(jìn)制接口(Application Binary Interface,ABI)的一部分。ELF 標(biāo)準(zhǔn)的目的是為軟件開(kāi)發(fā)人員提供一組二進(jìn)制接口定義烧董,這些接口可以在多種操作系統(tǒng)下生效毁靶,從而減少開(kāi)發(fā)的工作量。
ELF文件以節(jié)(Section)的方式組織在一起逊移,“節(jié)”描述了文件的各項(xiàng)信息预吆,例如代碼,數(shù)據(jù)胳泉,符號(hào)表拐叉,重定位表,全局偏移表等扇商。
可執(zhí)行文件裝載進(jìn)內(nèi)存時(shí)凤瘦,并不是被“完整”的映射進(jìn)內(nèi)存,而是根據(jù)ELF文件中格式的定義案铺,一段一段地裝載進(jìn)去蔬芥。因此,可執(zhí)行文件的格式和內(nèi)存的映象并不完全相同控汉,文件裝載進(jìn)內(nèi)存后是以‘段’的方式來(lái)組織的笔诵,如代碼段,數(shù)據(jù)段姑子,動(dòng)態(tài)段等噪径。
ELF格式的文件有3種:可執(zhí)行文件怪与,動(dòng)態(tài)庫(kù)(.so文件)和重定位(.o文件)逊躁。
這3中文件都有一個(gè)ELF頭屹徘,描述了整個(gè)可執(zhí)行文件的基本信息,如目標(biāo)代碼的格式沐旨,體系結(jié)構(gòu)森逮,各種段或節(jié)的偏移和大小等∠A可執(zhí)行文件和動(dòng)態(tài)庫(kù)中會(huì)有“程序頭部表(porgram Header Table)吊宋、但是,重定位文件中沒(méi)有程序頭部表颜武。此外璃搜。ELF文件中還會(huì)有一個(gè)“節(jié)區(qū)頭部表”(Section Header Table)? ,描述文件中各個(gè) 節(jié)區(qū)的內(nèi)容。這個(gè)表的內(nèi)容和程序頭部表的內(nèi)容有點(diǎn)重復(fù)鳞上,這是因?yàn)閮蓮埍淼挠猛静灰粯诱馕恰T诰幾g和鏈接階段(符號(hào)地址,不執(zhí)行庫(kù)文件)篙议。也就在可執(zhí)行文件的生成階段唾糯,需要使用“節(jié)區(qū)頭部表”怠硼,而可執(zhí)行文件裝載的時(shí)候使用的是程序頭部表。
分析ELF格式文件的目的移怯,是為了了解可執(zhí)行文件的裝載過(guò)程香璃,因此,下面重點(diǎn)介紹舟误,“程序頭部表”及其相關(guān)的數(shù)據(jù)結(jié)構(gòu)葡秒,對(duì)“節(jié)區(qū)頭部表”有興趣的讀取可以了解本節(jié)的內(nèi)容后自行分析。
3.5 圖
XXX.C---->GCC----xxx.ELF(此時(shí)使用節(jié)區(qū)頭部表)-----./(裝載時(shí)使用程序頭部表)------執(zhí)行
本節(jié)的內(nèi)容主要介紹32為可執(zhí)行文件額格式嵌溢,64位的格式除了一些字段的長(zhǎng)度不同外眯牧,文件的組織方式是一樣的。
ELF文件格式的數(shù)據(jù)結(jié)構(gòu)和常量的文件是exec_elf.h,位于目錄bionic/libc/include/sys下赖草。其中ELF頭定義如下:
typeof struct {
unsigned char e_ident[ELF_NIDENT] //目標(biāo)文件標(biāo)示
Elf32_Half e_type ; //目標(biāo)文件類(lèi)型
Elf32_Half e_machine学少; //目標(biāo)運(yùn)行平臺(tái)的體系結(jié)構(gòu)
Elf32_Word e_version; //目標(biāo)文件版本
Elf32_Addr e_entry; //程序的入口地址
Elf32_Off e_phoff; //程序頭部表的偏移量
Elf32_Off e_shoff; //節(jié)區(qū)頭部表的偏移量
Elf32_world e_flags; //文件相關(guān)的,特定于處理器的標(biāo)志秧骑。
Elf32_Half? e_ehsize; //ELF頭部的字節(jié)大小
Elf32_Half e_phentsize; //程序頭部表的表項(xiàng)的字節(jié)大小
Elf32_Half e_shentsize; //程序頭部表的表項(xiàng)數(shù)目
Elf32_Half e_shnum; //節(jié)區(qū)頭部表的表項(xiàng)的字節(jié)大小
Elf32_Half e_shstrndxl //節(jié)區(qū)頭部表的表項(xiàng)數(shù)目
} Elf32_Ehdr;
在程序頭部表里版确,最重要的是記錄“程序頭部表”和“節(jié)區(qū)頭部表”的位置,表示表項(xiàng)數(shù)目和表項(xiàng)大小的字段乎折。余下的字段中阀坏。
e_ident的16個(gè)字節(jié)標(biāo)明了ELF文件的標(biāo)志(7F+'E'+'L'+'F');
e_type表示文件類(lèi)型,2表示可執(zhí)行文件笆檀。
e_machine 表示機(jī)器類(lèi)別,3表示386機(jī)器盒至、8表示mips機(jī)器
e_entry表示程序的入口地址酗洒。
查看頭程序的文件readelf和objdump這兩個(gè)工具在prebuild目錄下有多份,分別對(duì)應(yīng)不同的平臺(tái)枷遂。在Android5.0的源碼里樱衷,可以使用的是目錄prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin下的工具
。
arm-eabi-readelf 有很多參數(shù)酒唉,其中-h參數(shù)能查看ELF文件的頭部信息矩桂,
程序頭部表
程序頭部表的作用是記錄文件中各種段的地址,大小等信息痪伦。在程序裝在和連接時(shí)都需要它侄榴。
程序頭部表示一個(gè)結(jié)構(gòu)Elf32_Phdr的數(shù)組,每個(gè)結(jié)構(gòu)中記錄了裝入內(nèi)存中的各個(gè)段的信息网沾,包括類(lèi)型癞蚕,地址,大小等辉哥。結(jié)構(gòu)Elf32_Phdr的定義如下:
typedef? struct{
Elf32_Word p_tpye; //段的類(lèi)型
Elf32_Off p_offset; //段的文件中的偏移
Elf32_Addr p_vaddr; //段裝入內(nèi)存后的虛擬地址
Elf32_Addr p_paddr; //端裝入內(nèi)存后的物理地址
Elf32_Word p_filesz; //段在文件中的大小
Elf32_Word p_memsz; //段裝入內(nèi)存后的大小
Elf32_Word p_flags; //段的標(biāo)志
Elf32_Word p_align; //內(nèi)存的對(duì)齊方式
} Elf32_Phdr;
同樣桦山,可以使用工具arm-eabi-readelf 來(lái)查看程序頭部表的信息攒射,這次需要使用的參數(shù)是"-l"
雖然“程序頭部表”可能包含多個(gè)段,但是恒水,只有類(lèi)型為"PT_LOAD"的“段”才會(huì)從文件映射到內(nèi)存中会放。其余類(lèi)型的"段"如果有實(shí)際的節(jié)區(qū),這些“節(jié)區(qū)”也會(huì)出現(xiàn)在"PT_LOAD"類(lèi)型的“段”中钉凌。
圖3.5中上半部分是程序頭部表咧最,可以看到它有8個(gè)“段”,圖3.5的下半部分是這些“段”包含的“節(jié)區(qū)”甩骏。這些段中只有兩個(gè)段的類(lèi)型是“PT_LOAD”,因此窗市,裝載這個(gè)文件時(shí),實(shí)際mmap進(jìn)內(nèi)存的也只有這兩個(gè)“段”饮笛。它們就是所謂的代碼段和數(shù)據(jù)段咨察,從它們的屬性也可以看出一個(gè)是“只讀”,另一個(gè)是“讀寫(xiě)”。
這兩個(gè)“PT_LOAD段"在圖3.5下半部分的對(duì)應(yīng)關(guān)系位于第02項(xiàng)和第03項(xiàng)福青,從圖3.5中可以看到他們包含了可執(zhí)行文件的所有"節(jié)區(qū)"摄狱。而DYNAMIC段(第04項(xiàng))只"包含"了一個(gè)".dynamic節(jié)區(qū)",這個(gè)"節(jié)區(qū)"和第03項(xiàng)中的".dynamic"是同一個(gè)无午。只不過(guò)".dynamic節(jié)區(qū)"的起始地址和大小等數(shù)據(jù)保存在“Dynamic段”中媒役,只能通過(guò)“DYNAMIC”段來(lái)找到“.dynamic”節(jié)區(qū),從而再找到“.plt”宪迟、“.dynsym”酣衷、“.got”等“節(jié)區(qū)”。相反次泽,雖然“PT_LOAD”類(lèi)型的“段”的地址空間范圍覆蓋了".dynmic節(jié)區(qū)"
但是無(wú)法通過(guò)它來(lái)找到“.dynamic節(jié)區(qū)”穿仪。這樣設(shè)計(jì)的目的是,當(dāng)系統(tǒng)裝在可執(zhí)行文件時(shí)只需要將"PT_LOAD"類(lèi)型的"段"完整的映射進(jìn)內(nèi)存就完成了意荤,而訪問(wèn)各個(gè)“節(jié)區(qū)”還是通過(guò)相應(yīng)的段所記錄的地址來(lái)完成啊片。
與重定位相關(guān)的"節(jié)區(qū)"的信息------DYNAMIC段
"DYNAMIC段"描述的是與重定位相關(guān)的"節(jié)區(qū)"的信息。"DYNAMIC段"也是個(gè)數(shù)組玖像,每項(xiàng)描述了"節(jié)區(qū)"的一些信息紫谷,一些復(fù)雜的"節(jié)區(qū)"需要好幾項(xiàng)來(lái)共同說(shuō)明,如".plt節(jié)區(qū)"就用了3項(xiàng)分別來(lái)描述"節(jié)區(qū)"的地址捐寥、大小笤昨、和"節(jié)區(qū)條目"的大小
“DYNAMIC段”的項(xiàng)數(shù)不是在文件的某個(gè)地方指定的,而是通過(guò)數(shù)組將最后一項(xiàng)的數(shù)據(jù)置為NULL來(lái)表示數(shù)組的結(jié)尾握恳。
typedef struct{
Elf32_Word d_tag;
union {
Elf32_Addr d_ptr;
Elf32_Word d_val;
}d_un;
} Elf32_Dyn;
其中咬腋,d_tag 表示每項(xiàng)的類(lèi)型。d_un是個(gè)聯(lián)合睡互,根據(jù)類(lèi)型使用根竿,器字段的作用是陵像。
d_val 表示一個(gè)整數(shù)值,根據(jù)d_tag的類(lèi)型不同有多種解釋寇壳,如偏移醒颖、尺寸等。
d_ptr 表示"節(jié)對(duì)象"的虛擬地址