NDK開發最後生成的so文件是基於ELF文件格式。而so是共享目標文件,因此要想對so文件進行加密就必須瞭解ELF文件格式。linux
原文連接:ELF文件格式分析數組
可執行連接格式(Executable and Linking Format)最初是由 UNIX 系統實驗室(UNIX System Laboratories,USL)開發併發布的,做爲應用程序二進制接口(Application Binary Interface,ABI)的一部分。工具接口標準(Tool Interface Standards,TIS)委員會將還 在發展的 ELF 標準選做爲一種可移植的目標文件格式,能夠在 32 位 Intel 體系結構上的 不少操做系統中使用。bash
目標文件有三種類型:數據結構
可重定位文件(Relocatable File) .o)包含適合於與其餘目標文件連接來建立可執行文件或者共享目標文件的代碼和數據。架構
可執行文件(Executable File) .exe) 包含適合於執行的一個程序,此文件規定了exec() 如何建立一個程序的進程映像。併發
共享目標文件(Shared Object File) .so) 包含可在兩種上下文中連接的代碼和數據。首先連接編輯器能夠將它和其它可重定位文件和共享目標文件一塊兒處理, 生成另一個目標文件。其次動態連接器(Dynamic Linker)可能將它與某 個可執行文件以及其它共享目標一塊兒組合,建立進程映像。編輯器
目標文件所有是程序的二進制表示,目的是直接在某種處理器上直接執行。ide
目標文件既要參與程序連接又要參與程序執行。出於方便性和效率考慮,目標文件
格式提供了兩種並行視圖,分別反映了這些活動的不一樣需求。 函數
文件開始處是一個 ELF 頭部(ELF Header),用來描述整個文件的組織。節區部 分包含連接視圖的大量信息:指令、數據、符號表、重定位信息等等。 工具
程序頭部表(Program Header Table),若是存在的話,告訴系統如何建立進程映像。 用來構造進程映像的目標文件必須具備程序頭部表,可重定位文件不須要這個表。
節區頭部表(Section Heade Table)包含了描述文件節區的信息,每一個節區在表中 都有一項,每一項給出諸如節區名稱、節區大小這類信息。用於連接的目標文件必須包 含節區頭部表,其餘目標文件能夠有,也能夠沒有這個表。
注意: 儘管圖中顯示的各個組成部分是有順序的,實際上除了 ELF 頭部表之外, 其餘節區和段都沒有規定的順序
目標文件格式支持 8 位字節/32 位體系結構。不過這種格式是能夠擴展的,好比如今的64位機器,目標文件所以以某些機器獨立的格式表達某些控制數據,使得可以以一種公共的方式來識別和解釋其內容。目標文件中的其它數據使用目標處理器的編碼結構,而無論文件在何種機器上建立。
目標文件中的全部數據結構都聽從「天然」大小和對齊規則。若是必要,數據結構可 以包含顯式的補齊,例如爲了確保 4 字節對象按 4 字節邊界對齊。數據對齊一樣適用於文件內部。
文件的最開始幾個字節給出如何解釋文件的提示信息。這些信息獨立於處理器,也
獨立於文件中的其他內容。ELF Header 部分能夠用如下的數據結構表示:
/* ELF Header */ typedef struct elfhdr { unsigned char e_ident[EI_NIDENT]; /* ELF Identification */ Elf32_Half e_type; /* object file type */ Elf32_Half e_machine; /* machine */ Elf32_Word e_version; /* object file version */ Elf32_Addr e_entry; /* virtual entry point */ Elf32_Off e_phoff; /* program header table offset */ Elf32_Off e_shoff; /* section header table offset */ Elf32_Word e_flags; /* processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size */ Elf32_Half e_phentsize; /* program header entry size */ Elf32_Half e_phnum; /* number of program header entries */ Elf32_Half e_shentsize; /* section header entry size */ Elf32_Half e_shnum; /* number of section header entries */ Elf32_Half e_shstrndx; /* section header table's "section header string table" entry offset */ } Elf32_Ehdr; typedef struct { unsigned char e_ident[EI_NIDENT]; /* Id bytes */ Elf64_Quarter e_type; /* file type */ Elf64_Quarter e_machine; /* machine type */ Elf64_Half e_version; /* version number */ Elf64_Addr e_entry; /* entry point */ Elf64_Off e_phoff; /* Program hdr offset */ Elf64_Off e_shoff; /* Section hdr offset */ Elf64_Half e_flags; /* Processor flags */ Elf64_Quarter e_ehsize; /* sizeof ehdr */ Elf64_Quarter e_phentsize; /* Program header entry size */ Elf64_Quarter e_phnum; /* Number of program headers */ Elf64_Quarter e_shentsize; /* Section header entry size */ Elf64_Quarter e_shnum; /* Number of section headers */ Elf64_Quarter e_shstrndx; /* String table index */ } Elf64_Ehdr;
其中,e_ident 數組給出了 ELF 的一些標識信息,這個數組中不一樣下標的含義如表所示:
這些索引訪問包含如下數值的字節:
e_ident[EI_MAG0]~e_ident[EI_MAG3]即e_ident[0]~e_ident[3]被稱爲魔數(Magic Number),其值通常爲0x7f,'E','L','F'。
e_ident[EI_CLASS](即e_ident[4])識別目標文件運行在目標機器的類別,取值可爲三種值:ELFCLASSNONE(0)非法類別;ELFCLASS32(1)32位目標;ELFCLASS64(2)64位目標。
e_ident[EI_DATA](即e_ident[5]):給出處理器特定數據的數據編碼方式。即大端仍是小端方式。取值可爲3種:ELFDATANONE(0)非法數據編碼;ELFDATA2LSB(1)高位在前;ELFDATA2MSB(2)低位在前。
ELF Header 中各個字段的說明如表:
一個實際可執行文件的頭文件頭部形式以下:
$greadelf -h hello.so ELF 頭: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 類別: ELF32 數據: 2 補碼,小端序 (little endian) 版本: 1 (current) OS/ABI: UNIX - System V ABI 版本: 0 類型: DYN (共享目標文件) 系統架構: ARM 版本: 0x1 入口點地址: 0x0 程序頭起點: 52 (bytes into file) Start of section headers: 61816 (bytes into file) 標誌: 0x5000000, Version5 EABI 本頭的大小: 52 (字節) 程序頭大小: 32 (字節) Number of program headers: 9 節頭大小: 40 (字節) 節頭數量: 24 字符串表索引節頭: 23
注:linux環境下能夠利用命令readelf,mac須要安裝binutils。用brew安裝很方便,命令:brew update && brew install binutils
可執行文件或者共享目標文件的程序頭部是一個結構數組,每一個結構描述了一個段 或者系統準備程序執行所必需的其它信息。目標文件的「段」包含一個或者多個「節區」, 也就是「段內容(Segment Contents)」。程序頭部僅對於可執行文件和共享目標文件 有意義。
可執行目標文件在 ELF 頭部的 e_phentsize 和 e_phnum 成員中給出其自身程序頭部 的大小。程序頭部的數據結構:
/* Program Header */ typedef struct { Elf32_Word p_type; /* segment type */ Elf32_Off p_offset; /* segment offset */ Elf32_Addr p_vaddr; /* virtual address of segment */ Elf32_Addr p_paddr; /* physical address - ignored? */ Elf32_Word p_filesz; /* number of bytes in file for seg. */ Elf32_Word p_memsz; /* number of bytes in mem. for seg. */ Elf32_Word p_flags; /* flags */ Elf32_Word p_align; /* memory alignment */ } Elf32_Phdr; typedef struct { Elf64_Half p_type; /* entry type */ Elf64_Half p_flags; /* flags */ Elf64_Off p_offset; /* offset */ Elf64_Addr p_vaddr; /* virtual address */ Elf64_Addr p_paddr; /* physical address */ Elf64_Xword p_filesz; /* file size */ Elf64_Xword p_memsz; /* memory size */ Elf64_Xword p_align; /* memory & file alignment */ } Elf64_Phdr;
其中各個字段說明:
p_type 此數組元素描述的段的類型,或者如何解釋此數組元素的信息。具體以下圖。
p_offset 此成員給出從文件頭到該段第一個字節的偏移。
p_vaddr 此成員給出段的第一個字節將被放到內存中的虛擬地址。
p_paddr 此成員僅用於與物理地址相關的系統中。由於 System V 忽略全部應用程序的物理地址信息,此字段對與可執行文件和共享目標文件而言具體內容是指定的。
p_filesz 此成員給出段在文件映像中所佔的字節數。能夠爲 0。
p_memsz 此成員給出段在內存映像中佔用的字節數。能夠爲 0。
p_flags 此成員給出與段相關的標誌。
p_align 可加載的進程段的 p_vaddr 和 p_offset 取值必須合適,相對於對頁面大小的取模而言。此成員給出段在文件中和內存中如何 對齊。數值 0 和 1 表示不須要對齊。不然 p_align 應該是個正整數,而且是 2 的冪次數,p_vaddr 和 p_offset 對 p_align 取模後應該相等。
可執行 ELF 目標文件中的段類型:
節區中包含目標文件中的全部信息,除了:ELF 頭部、程序頭部表格、節區頭部
表格。節區知足如下條件:
目標文件中的每一個節區都有對應的節區頭部描述它,反過來,有節區頭部不意 味着有節區。
每一個節區佔用文件中一個連續字節區域(這個區域可能長度爲 0)。
文件中的節區不能重疊,不容許一個字節存在於兩個節區中的狀況發生。
目標文件中可能包含非活動空間(INACTIVE SPACE)。這些區域不屬於任何頭部和節區,其內容指定。
ELF 頭部中,e_shoff 成員給出從文件頭到節區頭部表格的偏移字節數;e_shnum 給出表格中條目數目;e_shentsize 給出每一個項目的字節數。從這些信息中能夠確切地定 位節區的具體位置、長度。
每一個節區頭部數據結構描述:
/* Section Header */ typedef struct { Elf32_Word sh_name; /* name - index into section header string table section */ Elf32_Word sh_type; /* type */ Elf32_Word sh_flags; /* flags */ Elf32_Addr sh_addr; /* address */ Elf32_Off sh_offset; /* file offset */ Elf32_Word sh_size; /* section size */ Elf32_Word sh_link; /* section header table index link */ Elf32_Word sh_info; /* extra information */ Elf32_Word sh_addralign; /* address alignment */ Elf32_Word sh_entsize; /* section entry size */ } Elf32_Shdr; typedef struct { Elf64_Half sh_name; /* section name */ Elf64_Half sh_type; /* section type */ Elf64_Xword sh_flags; /* section flags */ Elf64_Addr sh_addr; /* virtual address */ Elf64_Off sh_offset; /* file offset */ Elf64_Xword sh_size; /* section size */ Elf64_Half sh_link; /* link to another */ Elf64_Half sh_info; /* misc info */ Elf64_Xword sh_addralign; /* memory alignment */ Elf64_Xword sh_entsize; /* table entry size */ } Elf64_Shdr;
對其中各個字段的解釋以下:
索引爲零(SHN_UNDEF)的節區頭部是存在的,儘管此索引標記的是未定義的節區應用。這個節區固定爲:
節區類型定義:
其餘的節區類型是保留的。
sh_flags 字段定義了一個節區中包含的內容是否能夠修改、是否能夠執行等信息。 若是一個標誌位被設置,則該位取值爲 1。 定義的各位都設置爲 0。
其中已經定義了的各位含義以下:
SHF_WRITE: 節區包含進程執行過程當中將可寫的數據。
SHF_ALLOC: 此節區在進程執行過程當中佔用內存。某些控制節區並不出現於目標
文件的內存映像中,對於那些節區,此位應設置爲 0。
SHF_EXECINSTR: 節區包含可執行的機器指令。
SHF_MASKPROC: 全部包含於此掩碼中的四位都用於處理器專用的語義。
根據節區類型的不一樣,sh_link 和 sh_info 的具體含義也有所不一樣:
不少節區中包含了程序和控制信息。下面的表中給出了系統使用的節區,以及它們 的類型和屬性。
在分析這些節區的時候,須要注意以下事項:
以「.」開頭的節區名稱是系統保留的。應用程序可使用沒有前綴的節區名稱,以避 免與系統節區衝突。
目標文件格式容許人們定義不在上述列表中的節區。
目標文件中也能夠包含多個名字相同的節區。
保留給處理器體系結構的節區名稱通常構成爲:處理器體系結構名稱簡寫 + 節區
名稱。
處理器名稱應該與 e_machine 中使用的名稱相同。例如 .FOO.psect 街區是由
FOO 體系結構定義的 psect 節區。
另外,有些編譯器對如上節區進行了擴展,這些已存在的擴展都使用約定俗成的名
稱,如:
.sdata
.tdesc
.sbss
.lit4
.lit8
.reginfo
.gptab
.liblist
.conflict
...
字符串表節區包含以 NULL(ASCII 碼 0)結尾的字符序列,一般稱爲字符串。ELF 目標文件一般使用字符串來表示符號和節區名稱。對字符串的引用一般以字符串在字符 串表中的下標給出。
通常,第一個字節(索引爲 0)定義爲一個空字符串。相似的,字符串表的最後一 個字節也定義爲 NULL,以確保全部的字符串都以 NULL 結尾。索引爲 0 的字符串在 不一樣的上下文中能夠表示無名或者名字爲 NULL 的字符串。
容許存在空的字符串表節區,其節區頭部的 sh_size 成員應該爲 0。對空的字符串 表而言,非 0 的索引值是非法的。
例如:對於各個節區而言,節區頭部的 sh_name 成員包含其對應的節區頭部字符串 表節區的索引,此節區由 ELF 頭的 e_shstrndx 成員給出。下圖給出了包含 25 個字節 的一個字符串表,以及與不一樣索引相關的字符串。
上圖中包含的字符串以下:
在使用、分析字符串表時,要注意如下幾點:
字符串表索引能夠引用節區中任意字節。
字符串能夠出現屢次
能夠存在對子字符串的引用
同一個字符串能夠被引用屢次。
字符串表中也能夠存在未引用的字符串。
目標文件的符號表中包含用來定位、重定位程序中符號定義和引用的信息。符號表 索引是對此數組的索引。索引 0 表示表中的第一表項,同時也做爲 定義符號的索引。
符號表項的格式以下:
/* Symbol Table Entry */ typedef struct elf32_sym { Elf32_Word st_name; /* name - index into string table */ Elf32_Addr st_value; /* symbol value */ Elf32_Word st_size; /* symbol size */ unsigned char st_info; /* type and binding */ unsigned char st_other; /* 0 - no defined meaning */ Elf32_Half st_shndx; /* section header index */ } Elf32_Sym; typedef struct { Elf64_Half st_name; /* Symbol name index in str table */ Elf_Byte st_info; /* type / binding attrs */ Elf_Byte st_other; /* unused */ Elf64_Quarter st_shndx; /* section index of symbol */ Elf64_Xword st_value; /* value of symbol */ Elf64_Xword st_size; /* size of symbol */ } Elf64_Sym;
其中各個字段的含義說明:
st_info 中包含符號類型和綁定信息,操縱方式如:
#define ELF32_ST_BIND(i) ((i)>>4) #define ELF32_ST_TYPE(i) ((i)&0xf) #define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf))
從中能夠看出,st_info 的低四位表示符號綁定,用於肯定連接可見性和行爲。具體的綁定類型如:
全局符號與弱符號之間的區別主要有兩點:
(1). 當連接編輯器組合若干可重定位的目標文件時,不容許對同名的 STB_GLOBAL 符號給出多個定義。 另外一方面若是一個已定義的全局符號已經存在,出現一個同名的弱符號並 不會產生錯誤。連接編輯器盡關心全局符號,忽略弱符號。 相似地,若是一個公共符號(符號的 st_shndx 中包含 SHN_COMMON),那 麼具備相同名稱的弱符號出現也不會致使錯誤。連接編輯器會採納公共定 義,而忽略弱定義。
(2). 當連接編輯器搜索歸檔庫(archive libraries)時,會提取那些包含 定 義全局符號的檔案成員。成員的定義能夠是全局符號,也能夠是弱符號。 鏈接編輯器不會提取檔案成員來知足 定義的弱符號。 能解析的弱符號取值爲 0。
在每一個符號表中,全部具備 STB_LOCAL 綁定的符號都優先於弱符號和全局符 號。符號表節區中的 sh_info 頭部成員包含第一個非局部符號的符號表索引。
符號類型(ELF32_ST_TYPE)定義以下:
在共享目標文件中的函數符號(類型爲 STT_FUNC)具備特別的重要性。當其餘 目標文件引用了來自某個共享目標中的函數時,連接編輯器自動爲所引用的符號建立過 程連接表項。類型不是 STT_FUNC 的共享目標符號不會自動經過過程連接表進行引用。
若是一個符號的取值引用了某個節區中的特定位置,那麼它的節區索引成員 (st_shndx)包含了其在節區頭部表中的索引。當節區在重定位過程當中被移動時,符號 的取值也會隨之變化,對符號的引用始終會「指向」程序中的相同位置。
某些特殊的節區索引具備不一樣的語義:
SHN_ABS: 符號具備絕對取值,不會由於重定位而發 生變化。
SHN_COMMON: 符號標註了一個尚 分配的公共 塊。符號的取值給出了對齊約束,與節區的 sh_addralign 成員相似。就是說,連接編輯器將爲符號分配存儲空間, 地址位於 st_value 的倍數處。符號的大小給出了所須要
的字節數。
SHN_UNDEF: 此節區表索引值意味着符號沒有定義。
當連接編輯器將此目標文件與其餘定義了該符號的目標 文件進行組合時,此文件中對該符號的引用將被連接到實 際定義的位置。
如上所述,符號表中下標爲 0(STN_UNDEF)的表項被保留。其中包含以下數值:
不一樣的目標文件類型中符號表項對 st_value 成員具備不一樣的解釋:
(1). 在可重定位文件中,st_value 中聽從了節區索引爲 SHN_COMMON 的符號的對齊約束。
(2). 在可重定位的文件中,st_value 中包含已定義符號的節區偏移。就是說,st_value 是從 st_shndx 所標識的節區頭部開始計算,到符號位置的偏移。
(3). 在可執行和共享目標文件中,st_value 包含一個虛地址。爲了使得這些 文件的符號對動態連接器更有用,節區偏移(針對文 件的解釋)讓位於 虛擬地址(針對內存的解釋),由於這時與節區號無關。
儘管符號表取值在不一樣的目標文件中具備類似的含義,適當的程序能夠採起高效的數據訪問方式。
重定位是將符號引用與符號定義進行鏈接的過程。例如,當程序調用了一個函數時,
相關的調用指令必須把控制傳輸到適當的目標執行地址。
可重定位文件必須包含如何修改其節區內容的信息,從而容許可執行文件和共享目 標文件保存進程的程序映像的正確信息。重定位表項就是這樣一些數據。
重定位表項的格式:
/* Relocation entry with implicit addend */ typedef struct { Elf32_Addr r_offset; /* offset of relocation */ Elf32_Word r_info; /* symbol table index and type */ } Elf32_Rel; /* Relocation entry with explicit addend */ typedef struct { Elf32_Addr r_offset; /* offset of relocation */ Elf32_Word r_info; /* symbol table index and type */ Elf32_Sword r_addend; } Elf32_Rela; typedef struct { Elf64_Xword r_offset; /* where to do it */ Elf64_Xword r_info; /* index & type of relocation */ } Elf64_Rel; typedef struct { Elf64_Xword r_offset; /* where to do it */ Elf64_Xword r_info; /* index & type of relocation */ Elf64_Sxword r_addend; /* adjustment value */ } Elf64_Rela;
其中,各個字段的說明以下:
如上所述,只有 Elf32_Rela 項目能夠明確包含補齊信息。類型爲 Elf32_Rel 的表 項在將被修改的位置保存隱式的補齊信息。 依賴於處理器體系結構,各類形式均可能 存在,甚至是必需的。所以,對特定機器的實現能夠僅使用一種形式,也能夠根據上下 文使用不一樣的形式。
重定位節區會引用兩個其它節區:符號表、要修改的節區。節區頭部的 sh_info 和 sh_link 成員給出這些關係。不一樣目標文件的重定位表項對 r_offset 成員具備略微不 同的解釋。
(1). 在可重定位文件中,r_offset 中包含節區偏移。就是說重定位節區自身 描述瞭如何修改文件中的其餘節區;重定位偏移 指定了被修改節區中的 一個存儲單元。
(2). 在可執行文件和共享的目標文件中,r_offset 中包含一個虛擬地址。爲 了使得這些文件的重定位表項對動態連接器更爲有用,節區偏移(針對文 件的解釋)讓位於虛地址(針對內存的解釋)。
儘管對 r_offset 的解釋會有少量不一樣,重定位類型的含義始終不變。
重定位表項描述如何修改後面的指令和數據字段。通常,共享目標文件在建立時, 其基 虛擬地址是 0,不過執行地址將隨着動態加載而發生變化。
重定位的過程,按照以下標記:
A 用來計算可重定位字段的取值的補齊。
B 共享目標在執行過程當中被加載到內存中的位置(基地址)。
G 在執行過程當中,重定位項的符號的地址所處的位置 —— 全局偏移
表的索引。
GOT 全局偏移表(GOT)的地址。
L 某個符號的過程連接表項的位置(節區偏移/地址)。過程連接表項
把函數調用重定位到正確的目標位置。連接編輯器構造初始的過程連接表,動態連接器在執行過程當中修改這些項目。
P 存儲單位被重定位(用 r_offset 計算)到的位置(節區偏移或者地
址)。
S 其索引位於重定位項中的符號的取值。
重定位項的 r_offset 取值給定受影響的存儲單位的第一個字節的偏移或者虛擬地 址。重定位類型給出那些位須要修改以及如何計算它們的取值。
SYSTEM V僅使用Elf32_Rel重定位表項,在被重定位的字段中包含補齊量。補
齊量和計算結果始終採用相同的字節順序。
X86 體系結構下常見的重定位類型: