android是建立在linux的基礎上,其底層代碼是安裝linux可執行文件——elf的格式來組裝的。本文結合android中的so文件來了解elf格式,資料大多收集於網上;elf格式位於android源碼:elf.h(下面涉及到的結構體和宏定義都可以在此頭文件中找到)。
elf大致可分為三部分:elf頭、程序頭表、節區頭表;當然還有上圖沒標出的動態符號表,
elf頭:
#define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT]; //magic Elf32_Half e_type; //type 1:重定位文件;2:可執行文件;3:共享文件 Elf32_Half e_machine; //cpu結構 Elf32_Word e_version; //版本 Elf32_Addr e_entry; //程序進入點 可執行:main;so:無用 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_shstrndx; //字符串表,在節區中索引 } Elf32_Ehdr;
我們會發現 e_ehsize = e_phoff(為什么?看第一幅圖)。在elf頭中我們很容易發現其實主要分三部分:info相關,程序頭相關,節區頭相關;剛好對應着鏈接器和裝載器所需內容。e_phoff、e_phentsize、e_phnum裝載器必須;e_shoff、e_shentsize、e_shnum、e_shstrndx是鏈接器必須;即section是供給linker使用,而segment是供給loader使用。注意下e_shstrndx是字符串表頭在section頭中的索引,例如e_shstrndx=2,則section頭中的第3個舊市字符串表頭。
phdr頭:
typedef struct elf32_phdr{ Elf32_Word p_type; //segment類型 Elf32_Off p_offset; //該segment在文件的偏移地址 Elf32_Addr p_vaddr; //segment映射到內存中的地址 Elf32_Addr p_paddr; //segment物理地址,現代操作系統基本無法觸及,基本無效 Elf32_Word p_filesz; //segment在文件中所占大小,有些segment在文件中不存在卻占據一定的內存大小,則為0 Elf32_Word p_memsz; //segment在內存中所占的地址空間大小 Elf32_Word p_flags; //segment可操作的讀寫權限 Elf32_Word p_align; //按幾個字節對齊 } Elf32_Phdr;
重點是p_offset和p_filesz,它們是segment的起始地址和大小;p_type為segment類型詳見elf.h中的PT開頭的宏定義;p_flags為segment的可操作權限詳見elf.h中的PF開頭宏定義(跟linux的文件權限rwx是一樣的)。
shdr頭:
typedef struct { Elf32_Word sh_name; //section name:.data、.dynamic、.got、.init...... Elf32_Word sh_type; //section類型 Elf32_Word sh_flags; //section權限 Elf32_Addr sh_addr; //section映射到內存中起始地址 Elf32_Off sh_offset; //該section在文件中的偏移 Elf32_Word sh_size; //section大小 Elf32_Word sh_link; //一般來說是該section所用的string table在section header table中的索引,見參考資料3 Elf32_Word sh_info; // Elf32_Word sh_addralign;//section按幾字節對齊 Elf32_Word sh_entsize; //section內容中表項所占大小,例如.dynamic為8下面解釋 } Elf32_Shdr;
動態符號表(dynamic_symbol_table):
介紹完elf格式的整體框架后,來深入了解內在的聯系和一些section。
.shstrtab:字符串表保存着一系列以NULL結尾的的字符串
.dynstr:該section包含了用於動態鏈接的字符串,通常是符號表項名稱字符串;
.dynamic:該section包含了動態鏈接信息,該section屬性將包含SHF_ALLOC比特位,而SHF_WRITE比特位是否為1取決於處理器(通常.dynamic會獨占一個segment叫dynamic);簡單來說它包含着一連串的dynamic結構
typedef struct dynamic{ Elf32_Sword d_tag; union{ Elf32_Sword d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn;
d_tag控制d_un是d_val還是d_ptr;可通過d_tag來識別是屬於哪個section(elf.h中DT開頭的宏定義),d_un為d_tag在文件中的偏移量(不完全正確!,以后再補充)。例如d_tag為6則是DT_SYMTAB為.dynsym,則d_un為.dynsym為偏移量。值得一提的是在該section中,sh_addralign為4,sh_entsize為8(為什么看dynamic結構體)。這個節區非常重要,android linker就是通過它解析出其他section,看源碼:http://androidxref.com/4.4_r1/xref/bionic/linker/linker.cpp#1339。注意d_tag = NEEDED表示為共享庫,而其共享庫的name是strtab[dun]為首字母的字符串。
.hash:包含了符號hash表,hash表內容的組合形式如下:
Symbol Hash Table nbucket nchain bucket[0] ... bucket[nbucket - 1] / chain[0] ... chain[nchain - 1]
由此可以看出hash表的長度為(nbucket+nchain+2)*4。假設函數hash值為funHash,在.hash中得到值funIndex=bucket[funHash]或chain[funHash]。
.dynsym:該section包含了動態鏈接符號表;其實該section是elf32_sym結構體數組
typedef struct elf32_sym{ Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; //不是很理解,看源碼http://androidxref.com/4.4_r1/xref/bionic/libc/include/sys/exec_elf.h STT_FUNC unsigned char st_other; Elf32_Half st_shndx; } Elf32_Sym;
sh_name值是以.dynstr為基址的索引,st_value為fun指令偏移地址,st_size為fun指令長度。funInfo = sym[funIndex],可得到fun的信息。st_shndx為STN_UNDEF表示該函數為外部引用需要被重定位,即st_value為0。這里提下st_info這個字符,它由類型和綁定屬性組成,可以源碼
.rel:重定位表
typedef struct elf32_rel { Elf32_Addr r_offset; // Elf32_Word r_info; // } Elf32_Rel;
r_offset為需要重定位的內容地址,而r_info分為2部分:elf.h中定義了宏定義,ELF32_R_SYM是在dynsym的索引,ELF32_R_TYPE是重定位的類型:
#define R_ARM_ABS32 2 //外部函數局部指針函數調用方式 位於.rl.dyn
/* 20-31 are reserved for ARM Linux. */ 位於源碼/bionic/libc/arch-arm/include/machine/elf_machdep.h #define R_ARM_COPY 20 #define R_ARM_GLOB_DAT 21 //外部函數全局指針函數調用方式 位於.rl.dyn #define R_ARM_JUMP_SLOT 22 //外部函數直接調用方式 位於.rl.plt #define R_ARM_RELATIVE 23 #define R_ARM_GOTOFF 24 #define R_ARM_GOTPC 25 #define R_ARM_GOT32 26 #define R_ARM_PLT32 27
r_offset是該函數的指令或者數據的值在內存中的指針,比如r_offset = addr1,在地址addr1中存放addr2,則addr2為函數指令地址
Tips:
1 字符串符號表.shstrtab后跟着section_header_table;節區表頭分布在elf文件最后,而字符串符號表往往是在最靠后的內容
2 section name需要在shstr table找;而segment 沒有name只有type,只需比較就能確定類型
3 根據函數名找到函數指令:函數名hash值funHash,在hash表得到索引值funIndex,在dynsym表索引得到funInfo,funIndo.st_name為.dynstr的索引(這可以判斷是否為我們要找到的函數),funInfo.st_value為函數指令偏移地址
4 .dynsym的項數=.hash中nchain
資料: