日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ELF 文件格式

發布時間:2024/4/11 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ELF 文件格式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

<elf.h> 頭文件定義了 ELF 可執行二進制文件的格式。這些文件包括普通的可執行文件,即可以直接執行的應用程序文件;可重定位目標文件,即 *.o 文件;核心轉儲 core 文件;和共享目標文件,即共享庫 *.so 文件。

使用 ELF 文件格式的可執行文件的組成是這樣的:一個 ELF 文件頭,后面是一個程序頭表,或者是一個節(即 section,后文也用節指代 section,用段指代 segment)頭表,或兩者都有。ELF 文件頭總是位于文件中偏移量為 0 的位置。程序頭表和節頭表在文件中的偏移量則由 ELF 文件頭定義。這兩個表描述了整個 ELF 文件其余部分的細節。

這個頭文件以 C 結構體的形式描述了上面提到的那些頭,它也包含動態節,重定位節,和符號表的結構體。

基本類型

N-bit 架構(N=32,64,ElfN 代表 Elf32 或 Elf64,uintN_t 代表 uint32_t 或 uint64_t)使用了下面這些數據類型:

數據類型說明
ElfN_Addr無符號程序地址,uintN_t
ElfN_Off無符號文件偏移量,uintN_t
ElfN_Section無符號節索引,uint16_t
ElfN_Versym無符號版本符號信息,uint16_t
Elf_Byteunsigned char
ElfN_Halfuint16_t
ElfN_Swordint32_t
ElfN_Worduint32_t
ElfN_Sxwordint64_t
ElfN_Xworduint64_t

(注意:BSD 的術語有點不一樣。Elf64_Half 是 Elf32_Half 大小的兩倍大,Elf64Quarter 用作 uint16_t。為了避免混淆,在下文中這些類型由顯式的替換。)

文件格式定義的所有數據結構遵循相關類型的 “自然” 大小及對齊規則。如果有需要,4 字節對象的數據結構可以包含顯式的填充以確保 4 字節對齊,來強制結構體大小為 4 的整數倍,等等。

ELF 文件頭(Ehdr)

ELF 文件頭由類型 Elf32_Ehdr 或 Elf64_Ehdr 描述:

#define EI_NIDENT 16typedef struct {unsigned char e_ident[EI_NIDENT];uint16_t e_type;uint16_t e_machine;uint32_t e_version;ElfN_Addr e_entry;ElfN_Off e_phoff;ElfN_Off e_shoff;uint32_t e_flags;uint16_t e_ehsize;uint16_t e_phentsize;uint16_t e_phnum;uint16_t e_shentsize;uint16_t e_shnum;uint16_t e_shstrndx;} ElfN_Ehdr;

這些字段的含義如下:

e_ident 這個字節數組描述了如何來解釋這個文件,其依賴的處理器或文件其余部分的內容。這個數組中的每一樣東西都由以 EI_ 為前綴的宏命名,且可能包含以 ELF 為前綴的宏值。這些宏有如下這些:

  • EI_MAG0 Magic number 的第一個字節。它的值必須是 ELFMAG0。(0: 0x7f)
  • EI_MAG1 Magic number 的第二個字節。它的值必須是 ELFMAG1。(1: ‘E’)
  • EI_MAG2 Magic number 的第三個字節。它的值必須是 ELFMAG2。(2: ‘L’)
  • EI_MAG3 Magic number 的第四個字節。它的值必須是 ELFMAG3。(3: ‘F’)
  • EI_CLASS 第五個字節描述了這個文件的架構:
    • ELFCLASSNONE 無效類別。
    • ELFCLASS32 這個值定義了 32 位架構。它支持文件和虛擬地址空間最多 4 Gigabytes 的機器。
    • ELFCLASS64 這個值定義了 64 位架構。
  • EI_DATA 第六個字節描述了文件中處理器特有數據的編碼方式。目前支持的編碼方式有如下這些:
    • ELFDATANONE 未知數據格式。
    • ELFDATA2LSB 二進制補碼,小尾端。
    • ELFDATA2MSB 二進制補碼,大尾端。
  • EI_VERSION 第七個字節是 ELF 規范的版本號:
    • EV_NONE 無效版本。
    • EV_CURRENT 目前的版本。
  • EI_OSABI 第八個字節描述了這個目標文件的目標操作系統和 ABI。其它的 ELF 結構中的一些字段有一些對于特定的平臺有意義的標記和值;對于那些字段的解釋由這個字節的值決定。比如:
ABI 值含義
ELFOSABI_NONE與 ELFOSABI_SYSV 相同
ELFOSABI_SYSVUNIX System V ABI
ELFOSABI_HPUXHP-UX ABI
ELFOSABI_NETBSDNetBSD ABI
ELFOSABI_LINUXLinux ABI
ELFOSABI_SOLARISSolaris ABI
ELFOSABI_IRIXIRIX ABI
ELFOSABI_FREEBSDFreeBSD ABI
ELFOSABI_TRU64TRU64 UNIX ABI
ELFOSABI_ARMARM 架構 ABI
ELFOSABI_STANDALONEStand-alone (embedded) ABI
  • EI_ABIVERSION 第九個字節描述了目標文件的目標 ABI 版本。該字段用于區分 ABI 的不兼容版本。這個版本號的解釋依賴于由 EI_OSABI 字段描述的 ABI。符合本規范的應用程序使用值 0。
  • EI_PAD 填充值的開始。這些字節保留且被設置為 0。讀取它們的程序應該忽略它們。如果目前未使用的字節被賦予了意義,則 EI_PAD 的值在未來將會改變。
  • EI_NIDENT e_ident 數組的大小。

e_type 該結構體的這個成員描述了目標文件的類型:

類型值含義
ET_NONE一種未知類型
ET_REL可重定位文件,即 *.o 文件
ET_EXEC可執行文件
ET_DYN共享目標文件,即 *.so 文件
ET_COREcore 文件,即 crash 時的核心轉儲文件

e_machine 該成員為單個文件指定所需的體系結構。比如:

機器值含義
EM_NONE未知機器類型
EM_M32AT&T WE 32100
EM_SPARCSun Microsystems SPARC
EM_386Intel 80386
EM_68KMotorola 68000
EM_88KMotorola 88000
EM_860Intel 80860
EM_MIPSMIPS RS3000 (big-endian only)
EM_PARISCHP/PA
EM_SPARC32PLUSSPARC with enhanced instruction set
EM_PPCPowerPC
EM_PPC64PowerPC 64-bit
EM_S390IBM S/390
EM_ARMAdvanced RISC Machines
EM_SHRenesas SuperH
EM_SPARCV9SPARC v9 64-bit
EM_IA_64Intel Itanium
EM_X86_64AMD x86-64
EM_VAXDEC Vax

e_entry 這個成員給出了系統首次控制轉移的目標虛擬地址,這將啟動進程。如果文件沒有關聯的入口點,則這個成員的值為 0。
e_phoff 這個成員是程序頭表(program header table)在文件中的字節偏移量。如果文件沒有程序頭表,則這個成員的值為 0。
e_shoff 這個成員是節頭表(section header table)在文件中的字節偏移量。如果文件沒有節頭表,則這個成員的值為 0。
e_flags 這個成員為與文件關聯的處理器特有標記。標記名的形式為 EF_machine_flag。目前,還沒有定義任何標記。
e_ehsize 這個成員是 ELF 頭的字節大小。
e_phentsize 這個成員是文件的程序頭表中一個項的字節大小;所有的項具有相同大小。
e_phnum 這個成員是程序頭表中的程序頭個數。這樣 e_phentsize 和 e_phnum 的乘積給出了這個表的字節大小。如果文件沒有程序頭,則 e_phnum 的值為 0。
如果程序頭表中項的個數大于等于 PN_XNUM (0xffff),則這個成員的值為 PN_XNUM (0xffff),而真實的程序頭表中的項數由節頭表中的初始項的 sh_info 成員給出。否則初始項的 sh_info 成員的值為 0。

  • PN_XNUM 這個值被定義為 0xffff,它是 e_phnum 能具有的最大值,給出了實際的程序頭個數的位置。
    e_shentsize 這個成員為節頭的字節大小。節頭是節頭表中的一項;所有項的大小都相同。
    e_shnum 這個成員為接頭表中的項數。這樣 e_shentsize 和 e_shnum 的乘積給出了節頭表的字節大小。如果一個文件沒有節頭表,則 e_shnum 的值為 0。
    如果節頭表中的項數大于等于 SHN_LORESERVE (0xff00),則 e_shnum 的值為 0,且節頭表中的項數的真實值位于節頭表的初始項的 sh_size 成員中。否則節頭表的初始項的 sh_size 成員的值為 0。
    e_shstrndx 這個字段為與節名稱字符串表關聯的節的節頭在節頭表中的索引。如果文件沒有節名稱字符串表,則這個成員的值為 SHN_UNDEF 。
    如果節名稱字符串表的節的索引大于等于 SHN_LORESERVE (0xff00),則這個字段的值為 SHN_XINDEX (0xffff),而節名稱字符串表節的真正索引位于節頭表初始項的 sh_link 成員中。否則,節頭表中的初始項的 sh_link 成員值為 0。

程序頭(Phdr)

可執行文件或共享目標文件的程序頭表是一個結構體的數組,其中的每一個都描述了一個段或系統用于為執行做準備的其它信息。一個目標文件的段包含一個或多個節。程序頭只對可執行文件和共享目標文件有意義。文件用 ELF 文件頭的 e_phentsize 和 e_phnum 成員描述它自己的程序頭大小。根據具體的架構,ELF 程序頭用類型 Elf32_Phdr 或 Elf64_Phdr 描述:

typedef struct {uint32_t p_type;Elf32_Off p_offset;Elf32_Addr p_vaddr;Elf32_Addr p_paddr;uint32_t p_filesz;uint32_t p_memsz;uint32_t p_flags;uint32_t p_align;} Elf32_Phdr;typedef struct {uint32_t p_type;uint32_t p_flags;Elf64_Off p_offset;Elf64_Addr p_vaddr;Elf64_Addr p_paddr;uint64_t p_filesz;uint64_t p_memsz;uint64_t p_align;} Elf64_Phdr;

32 位和 64 位程序頭的主要區別在于 p_flags 成員在結構體中的位置。

p_type 結構體的這個成員表示這個數組元素描述的是何種類型的段,或如何解釋數組元素的信息。

  • PT_NULL 該數組元素是未使用的,且其它成員的值是未定義的。這使得該程序頭被忽略。
  • PT_LOAD 該數組元素表示一個可加載段,由 p_filesz 和 p_memsz 描述。文件中這部分的字節被映射到內存段的開始位置。如果段的內存大小 p_memsz 大于文件大小 p_filesz,“額外的”自己被定義為值 0,且跟在段的已初始化部分后面。段的文件大小可以不大于內存大小。程序頭表中的可加載段項以升序出現,按 p_vaddr 成員的值排序。
  • PT_DYNAMIC 該數組元素表示動態鏈接信息。
  • PT_INTERP 該數組元素表示一個以 null 終止的將被調用以作為解釋器的路徑名的位置和大小。這個段類型只對可執行文件(盡管它可以出現在共享目標文件中)有意義。然而,它在一個文件中不會出現多次。如果出現,它必須出現在任何可加載段項的前面。
  • PT_NOTE 該數組元素表示 notes 的位置(ElfN_Nhdr)。
  • PT_SHLIB 這個段類型保留,但語義不明。包含此類型數組元素的程序不符合ABI。
  • PT_PHDR 該數組元素,如果出現,表示程序頭表自身的位置和大小,在程序的文件和內存鏡像中都是。這個段類型在一個文件中不會出現多次。此外,如果程序頭表是程序的內存鏡像的一部分時,它可能出現。如果出現,它必須出現在任何可加載段項的前面。
  • PT_LOPROC, PT_HIPROC [PT_LOPROC, PT_HIPROC] 范圍內的值被保留用于處理器特有的語義。
  • PT_GNU_STACK GNU 擴展,Linux 內核會使用它來通過 p_flags 成員設置的標記控制棧的狀態。

p_offset 這個成員表示這個段的第一個字節從文件開始位置處的偏移量。
p_vaddr 這個成員表示這個段的第一個字節在內存中的虛擬地址。
p_paddr 在物理地址是相對尋址的系統上,這個成員保留用作段的物理地址。在 BSD 下這個成員未使用,且必須是 0。
p_filesz 這個成員表示段在文件鏡像中的字節大小。它可能是 0。
p_memsz 這個成員表示段在內存鏡像中的字節大小。它可能是 0。
p_flags 這個成員表示與段相關的標記的位掩碼:

  • PF_X 可執行段。
  • PF_W 可讀段。
  • PF_R 可寫段。

文本段通常具有標記 PF_X 和 PF_R。數據段通常具有標記 PF_X,PF_W 和 PF_R。

p_align 這個字段的值是段在內存和文件中的對齊方式。可加載的進程段對 p_vaddr 和 p_offset 必須具有一致的值,為頁大小的模。值為 0 和 1 表示不需要對齊。否則,p_align 應該是個正值,2 的指數,且 p_vaddr 應該等于 p_offset,模 p_align。

節頭(Shdr)

文件的節頭表讓我們可以定位文件所有的節。節頭表是 Elf32_Shdr 或 Elf64_Shdr 結構體的數組。ELF 文件頭的 e_shoff 成員給出了節頭表到文件開始位置處的字節偏移量。
e_shnum 成員為節頭表包含的項數。
e_shentsize 的值為每個項的字節大小。

節頭表索引是這個數組的下標。一些接頭表索引被保留:初始項和 SHN_LORESERVE 及 SHN_HIRESERVE 之間的索引。初始項用于 e_phnum,e_shnum 和 e_strndx 的 ELF擴展,在其它情況下,初始項中的每個字段被設置為 0。目標文件不具有如下這些特殊索引的節:

SHN_UNDEF 這個值標記一個未定義的,丟失的,不相關的,或其它無意義的節參考。
SHN_LORESERVE 這個值表示保留的索引的下界。
SHN_LOPROC, SHN_HIPROC 大于 [SHN_LOPROC, SHN_HIPROC] 范圍的值被保留用于處理器特有語義。
SHN_ABS 這個值指定了對應引用的絕對值。比如,一個符號相對于節號 SHN_ABS 定義則具有絕對值,且不受重定位影響。
SHN_COMMON 相對于這個節定義的符號是通用符號,比如 FORTRAN COMMON 或未分配的 C 外部變量。
SHN_HIRESERVE 這個值表示保留的索引范圍的上界。系統保留 SHN_LORESERVE 和 SHN_HIRESERVE 之間的范圍,包括。節頭表不包含這些保留索引的項。

節頭的結構如下:

typedef struct {uint32_t sh_name;uint32_t sh_type;uint32_t sh_flags;Elf32_Addr sh_addr;Elf32_Off sh_offset;uint32_t sh_size;uint32_t sh_link;uint32_t sh_info;uint32_t sh_addralign;uint32_t sh_entsize;} Elf32_Shdr;typedef struct {uint32_t sh_name;uint32_t sh_type;uint64_t sh_flags;Elf64_Addr sh_addr;Elf64_Off sh_offset;uint64_t sh_size;uint32_t sh_link;uint32_t sh_info;uint64_t sh_addralign;uint64_t sh_entsize;} Elf64_Shdr;

32 位和 64 位節頭沒有真正的差異。

sh_name 這個成員表示節的名稱。它的值是到節頭字符串表節的索引,給出了一個以 null 結尾的字符串的位置。
sh_type 這個成員給節的內容和語義做了分類。

  • SHT_NULL 這個值把節頭標記為 inactive。它沒有與之關聯的節。該節頭的其它成員具有未定義值。

  • SHT_PROGBITS 該節持有由程序定義的信息,其格式和含義完全由程序決定。

  • SHT_SYMTAB 這個節是符號表。典型地,SHT_SYMTAB 為鏈接編輯提供了符號,盡管它可能也用于動態鏈接。
    作為一個完整的符號表,它可能包含許多不需要動態鏈接的符號。目標文件也可以包含一個 SHT_DYNSYM 節。

  • SHT_STRTAB 這個節是一個字符串表。一個目標文件可以有多個字符串表節。

  • SHT_RELA 這個節包含具有顯式的附加的重定位項,例如,目標文件的 32 位類的類型Elf32_Rela。一個目標文件可以有多個重定位節。

  • SHT_HASH 這個節是符號哈希表。參與動態鏈接的目標文件必須包含一個符號哈希表。一個目標文件可能只有一個哈希表。

  • SHT_DYNAMIC 這個節包含用于動態鏈接的信息。一個目標文件可以只有一個動態節。

  • SHT_NOTE 這個節包含 notes(ElfN_Nhdr)。

  • SHT_NOBITS 這種類型的節在文件中不占用空間,但它與 SHT_PROGBITS 類似。盡管這種節不包含數據,但其 sh_offset 成員包含概念上的文件偏移量。

  • SHT_REL 這個節包含不具有顯式的附加的重定位偏移,例如,目標文件的 32 位類的類型 Elf32_Rel。一個目標文件可以有多個重定位節。

  • SHT_SHLIB 該節是保留的,但具有未指定的語義。

  • SHT_DYNSYM 該節包含動態鏈接符號的最小集合。一個目標文件也可以包含一個 SHT_DYNSYM 節。

  • SHT_LOPROC, SHT_HIPROC [SHT_LOPROC, SHT_HIPROC] 范圍內的值是為處理器特有語義保留的。

  • SHT_LOUSER 這個值是為應用程序保留的索引范圍的下界。

  • SHT_HIUSER 這個值是為應用程序保留的索引范圍的上界。SHT_LOUSER 和 SHT_HIUSER 之間的節類型可以用于應用程序,而不會與當前或未來系統定義的節類型沖突。
    sh_flags 節支持描述雜項屬性的一位標志。如果給 sh_flags 設置了一個標記位,則這個節的屬性為 “on”。否則,屬性為 “off” 或不應用。未定義的屬性設置為 0。

  • SHF_WRITE 該節包含的數據在進程執行期間應該可寫。

  • SHF_ALLOC 該節在進程執行期間占用內存。有些控制節不駐留在目標文件的內存映像中。對于那些節來說這個屬性為 off。

  • SHF_EXECINSTR 該節包含可執行的機器指令。

  • SHF_MASKPROC 這個掩碼中包含的所有位為處理器特有語義保留。

sh_addr 如果該節出現在進程的內存鏡像中,則這個成員為該節的第一個字節應該所處的地址。否則,這個成員包含 0。
sh_offset 這個成員的值為這個節中的第一個字節到文件開始位置處的字節偏移量。一種節類型,SHT_NOBITS,在文件中不占用空間,但它的 sh_offset 成員定位了在文件中概念上的位置。
sh_size 這個成員的值為該節的字節大小。除非節的類型為 SHT_NOBITS,則該節在文件中占有 sh_size 字節。SHT_NOBITS 類型的節可以具有非 0 的大小,但它在文件中不占用空間。
sh_link 該成員為節頭表索引鏈接,其解釋依賴于節類型。
sh_info 這個成員包含額外的信息,其解釋依賴于節類型。
sh_addralign 一些節具有地址對齊限制。如果一個節包含雙字,則系統必須為整個節確保雙字對齊。即,sh_addr 的值必須全等于 0,模 sh_addralign 的值。只能是 0 和 2 的正整數冪。值為 0 或 1 意味著該節沒有對齊限制。
sh_entsize 一些節包含固定大小的項的表,比如符號表。對于這樣的節,該成員給出了每一項的字節大小。如果該節不包含固定大小的項的表則該成員包含 0。

各種各樣的節包含有程序和控制信息:

.bss 本節包含未初始化但會占用程序的內存鏡像空間的數據。根據定義,系統在程序開始運行時以0 初始化數據。
本節的類型為 SHT_NOBITS。屬性類型為 SHF_ALLOCSHF_WRITE

.comment 這個節包含版本控制信息。這個節的類型為 SHT_PROGBITS。沒有屬性類型。

.ctors 本節包含指向 C++ 構造函數的已初始化指針。本節的類型為 SHT_PROGBITS。屬性類型為 SHF_ALLOCSHF_WRITE

.data 本節包含用于程序內存鏡像的已初始化數據。本節的類型為 SHT_PROGBITS。屬性類型為 SHF_ALLOCSHF_WRITE

.data1 本節包含用于程序內存鏡像的已初始化數據。本節的類型為 SHT_PROGBITS。屬性類型為 SHF_ALLOCSHF_WRITE

.debug 本節包含符號調試的信息。內容未指定。本節的類型為 SHT_PROGBITS。不使用任何屬性類型。

.dtors 本節包含指向 C++ 析構函數的已初始化指針。本節的類型為 SHT_PROGBITS。屬性類型為 SHF_ALLOCSHF_WRITE

.dynamic 本節包含動態鏈接信息。本節的屬性將包含 SHF_ALLOC 位。SHF_WRITE 位是否被置位依賴于處理器。本節的類型為 SHT_DYNAMIC。參考上面的屬性。

.dynstr 本節包含動態鏈接所需的字符串,最常見的是與符號表項關聯的表示名字的字符串。本節的類型為 SHT_STRTAB。用到的屬性為 SHF_ALLOC

.dynsym 本節包含動態鏈接符號表。本節的類型為 SHT_DYNSYM。用到的屬性為 SHF_ALLOC

.fini 本節包含用于進程終止代碼的可執行指令。當程序正常退出時,系統安排執行本節的代碼。本節的類型為 SHT_PROGBITS。用到的屬性為 SHF_ALLOCSHF_EXECINSTR

.gnu.version 本節包含版本符號表,一個 ElfN_Half 元素的數組。本節的類型為 SHT_GNU_versym。用到的屬性類型為 SHF_ALLOC

.gnu.version_d 本節包含版本符號定義,一個 ElfN_Verdef 結構的表。本節的類型為 SHT_GNU_verdef。用到的屬性類型為 SHF_ALLOC

.gnu.version_r 本節包含元素需要的版本符號,一個 ElfN_Verneed 結構的表。本節的類型為 SHT_GNU_versym。用到的屬性類型為 SHF_ALLOC

.got 本節包含全局偏移表。本節的類型為 SHT_PROGBITS。屬性是特定于處理器的。

.hash 本節包含符號哈希表。本節的類型為 SHT_HASH。用到的屬性為 SHF_ALLOC

.init 本節包含用于進程初始化代碼的可執行指令。當程序開始運行時,系統在調用主程序的入口點之前安排執行本節的代碼。本節的類型為 SHT_PROGBITS。用到的屬性為 SHF_ALLOCSHF_EXECINSTR

.interp 本節包含程序解釋器的路徑名。如果程序具有包含該節的段,則該節的屬性將包含 SHF_ALLOC 位。否則,SHF_ALLOC 位將為 off。本節的類型為 SHT_PROGBITS

.line 本節包含符號調試的行號信息,其描述了程序源碼和機器碼之間的對應關系。內容未指定。本節的類型為 SHT_PROGBITS。沒有用到任何屬性類型。

.note 本節包含各種 notes。本節的類型為 SHT_NOTE。沒有用到任何屬性類型。

.note.ABI-tag 本節用于聲明期望的 ELF 鏡像的運行時 ABI。它包含操作系統名和它的運行時版本。本節的類型為 SHT_NOTE。只用到了 SHF_ALLOC 屬性。

.note.gnu.build-id 本節用于包含唯一標識 ELF 鏡像內容的 ID。具有相同 build ID 的不同文件應該包含相同的可執行內容。參考 GNU 鏈接器 (ld (1)) 的 –build-id 選項來了解更多細節。本節的類型為 SHT_NOTE。只用到了 SHF_ALLOC 屬性。

.note.GNU-stack 本節用于聲明棧屬性的 Linux 目標文件中。本節的類型為 SHT_PROGBITS。唯一使用的屬性是 SHF_EXECINSTR。本節向 GNU 鏈接器表示目標文件需要一個可執行棧。

.note.openbsd.ident OpenBSD 本地可執行文件通常包含這個節來標識它們自己,以使內核在加載文件時可以繞開任何兼容性 ELF 二進制仿真測試。

.plt 本節包含過程鏈接表。本節的類型為 SHT_PROGBITS。屬性是特定于處理器的。

.relNAME 本節包含如下面所述的重定位信息。如果文件具有包含重定位的可加載段,本節的屬性將包含 SHF_ALLOC 位。否則,該位為 off。按照慣例,“NAME” 由重定位應用的節提供。這樣 .text 的重定位節通常的名字將為 .rel.text。本節的類型為 SHT_REL

.relaNAME 本節包含如下面所述的重定位信息。如果文件具有包含重定位的可加載段,本節的屬性將包含 SHF_ALLOC 位。否則,該位為 off。按照慣例,“NAME” 由重定位應用的節提供。這樣 .text 的重定位節通常的名字將為 .rela.text。本節的類型為 SHT_RELA

.rodata 本節包含只讀數據,典型地用于進程鏡像的非可寫段。本節的類型為 SHT_PROGBITS。用到的屬性為 SHF_ALLOC

.rodata1 本節包含只讀數據,典型地用于進程鏡像的非可寫段。本節的類型為 SHT_PROGBITS。用到的屬性為 SHF_ALLOC

.shstrtab 本節包含節名。本節的類型為 SHT_STRTAB。不使用任何屬性類型。

.strtab 本節包含字符串,最常見的是與符號表項關聯的表示名字的字符串。如果文件具有一個包含符號字符串表的可加載段,本節的屬性將包含 SHF_ALLOC 位。否則,此位將是 off 的。本節的類型為 SHT_STRTAB

.symtab 本節包含符號表。如果文件具有一個包含符號表的可加載段,本節的屬性將包含 SHF_ALLOC 位。否則,此位將是 off 的。本節的類型為 SHT_SYMTAB

.text 本節包含 “text”,或程序的可執行指令。本節的類型為 SHT_PROGBITS。用到的屬性為 SHF_ALLOCSHF_EXECINSTR

字符串和符號表

字符串表包含以 null 結尾的字符序列(復數),通常稱為字符串(復數)。目標文件使用這些字符串表示符號和節名。使用字符串的地方通過字符串在字符串表節內的索引來引用。第一個字節,其索引為 0,被定義為包含一個 null 字節 (’\0’)。類似地,字符串表的最后一個字節被定義為包含一個 null 字節,以確保所有的字符串均以 null 終止。

目標文件的符號表包含定位和重定位一個程序的符號定義和引用的信息。符號表索引是到這個數組的下標。

typedef struct {uint32_t st_name;Elf32_Addr st_value;uint32_t st_size;unsigned char st_info;unsigned char st_other;uint16_t st_shndx;} Elf32_Sym;typedef struct {uint32_t st_name;unsigned char st_info;unsigned char st_other;uint16_t st_shndx;Elf64_Addr st_value;uint64_t st_size;} Elf64_Sym;

32 位和 64 位版本具有相同的成員,只是順序不同。

st_name 這個成員包含到目標文件的符號字符串表的索引,其包含表示符號名稱的字符。如果該值非 0,則它表示給出符號名稱的字符串表索引。否則,符號沒有名稱。

st_value 這個成員給出了與符號關聯的值。

st_size 許多符號具有關聯的大小。如果符號不具有大小則該成員包含 0,否則是一個未知大小。

st_info 這個成員指定了符號的類型和綁定屬性:

  • STT_NOTYPE 符號的類型未定義。
  • STT_OBJECT 符號與一個數據對象關聯。
  • STT_FUNC 符號與一個函數或其它可執行代碼關聯。
  • STT_SECTION 符號與一個節關聯。具有這種類型的符號表項主要用于重定位,且通常具有 STB_LOCAL 綁定類型。
  • STT_FILE 按照慣例,該符號的名稱給出了與目標文件關聯的源文件的名稱。文件符號具有 STB_LOCAL 綁定,它的節索引是 SHN_ABS,且如果出現的話,它出現在文件中的其它 STB_LOCAL 符號的前面。
  • STT_LOPROC, STT_HIPROC 在 [STT_LOPROC, STT_HIPROC] 范圍內的值是為特定于處理器的語義保留的。
  • STB_LOCAL 本地符號在包含它們的定義的目標文件之外不可見。出現在多個文件中的具有相同名稱的本地符號彼此之間互不干擾。
  • STB_GLOBAL 全局符號對于被合并的所有目標文件都可見。全局符號的一個文件的定義將滿足另一個文件對相同符號的未定義引用。
  • STB_WEAK 弱符號與全局符號類似,但它們的定義具有更低優先級。
  • STB_LOPROC, STB_HIPROC 在 [STB_LOPROC, STB_HIPROC] 范圍內的值是為特定于處理器的語義保留的。

有一些宏可以用于打包和解包綁定和類型字段:

  • ELF32_ST_BIND( info )ELF64_ST_BIND( info ) 從 st_info 值中提取綁定。
  • ELF32_ST_TYPE( info )ELF64_ST_TYPE( info ) 從 st_info 值中提取類型。
  • ELF32_ST_INFO( bind,type )ELF64_ST_INFO( bind,type ) 將綁定和類型轉換為 st_info 值。

st_other 這個成員定義了符號可見性。

  • STV_DEFAULT 默認符號可見性規則。全局的和弱符號對于其它模塊可用:本地模塊的引用可以由其它模塊的定義插入。
  • STV_INTERNAL 特定于處理器的隱藏類型。
  • STV_HIDDEN 符號對其它模塊不可用:本地模塊中的引用總是解析到本地的符號(比如,符號無法由其它模塊的定義插入)。
  • STV_PROTECTED 符號對其它模塊可用,但本地模塊中的引用總是解析到本地的符號。

這些宏用于提取可見性類型:
ELF32_ST_VISIBILITY( other )ELF64_ST_VISIBILITY( other )

st_shndx 每個符號表項均是根據某些節 “定義” 的。該成員包含相關的節頭表索引。

重定位項(Rel & Rela)

重定位是把符號引用和符號定義連接起來的過程。可重定位文件必須具有描述如何修改它們的節內容的信息,這樣使得可執行文件和共享目標文件可以為進程的程序鏡像包含正確的信息。重定位項就是這些數據。

不需要附加的重定位項:

typedef struct {Elf32_Addr r_offset;uint32_t r_info;} Elf32_Rel;typedef struct {Elf64_Addr r_offset;uint64_t r_info;} Elf64_Rel;

需要附加的重定位項:

typedef struct {Elf32_Addr r_offset;uint32_t r_info;int32_t r_addend;} Elf32_Rela;typedef struct {Elf64_Addr r_offset;uint64_t r_info;int64_t r_addend;} Elf64_Rela;

r_offset 這個成員給出了采取重定位行動的位置。對于一個可重定位文件,該值是從節的開始位置處到被重定位影響到的存儲單元的字節偏移量。對于可執行文件或共享目標文件,該值是被重定位影響到的存儲單元的虛擬地址。

r_info 該成員給出了必須執行重定位的符號表索引和應用的重定位的類型。重定位類型是特定于處理器的。當 text 引用了一個重定位項的重定位類型或符號表索引,它意味著應用 ELF[32|64]_R_TYPEELF[32|64]_R_SYM,分別地,到項的 r_info 成員的結果。

r_addend 這個成員指定了一個常量附加用于計算將被存儲進可重定位字段中的值。

動態標簽(Dyn)

.dynamic 節包含了一系列包含與動態鏈接相關的信息的結構體。d_tag 成員控制 d_un 的解釋。

typedef struct {Elf32_Sword d_tag;union {Elf32_Word d_val;Elf32_Addr d_ptr;} d_un;} Elf32_Dyn;extern Elf32_Dyn _DYNAMIC[];typedef struct {Elf64_Sxword d_tag;union {Elf64_Xword d_val;Elf64_Addr d_ptr;} d_un;} Elf64_Dyn;extern Elf64_Dyn _DYNAMIC[];

d_tag 這個成員可以具有下面的任何值:

  • DT_NULL 標記動態節的結束。
  • DT_NEEDED 所需的庫的名稱的字符串表索引
  • DT_PLTRELSZ PLT 重定位項的字節大小
  • DT_PLTGOT PLT 和/或 GOT 的地址
  • DT_HASH 符號哈希表的地址
  • DT_STRTAB 字符串表的地址
  • DT_SYMTAB 符號表的地址
  • DT_RELA Rela 重定位表的地址
  • DT_RELASZ Rela 重定位表的字節大小
  • DT_RELAENT 重定位表項的字節大小
  • DT_STRSZ 字符串表的字節大小
  • DT_SYMENT 符號表項的字節大小
  • DT_INIT 初始函數的地址
  • DT_FINI 終止函數的地址
  • DT_SONAME 共享目標文件名稱的字符串表偏移量
  • DT_RPATH 庫搜索路徑的字符串表偏移量(廢棄)
  • DT_SYMBOLIC 通知鏈接器在為可執行文件搜索符號之前先搜索該共享目標文件。
  • DT_REL Rel 重定位表的地址。
  • DT_RELSZ Rel 重定位表的字節大小。
  • DT_RELENT Rel 重定位表項的字節大小。
  • DT_PLTREL PLT 引用的重定位項的類型(Rela or Rel)
  • DT_DEBUG 用于調試的未定義內容
  • DT_TEXTREL 缺失這個項表示不應該為一個非可寫段應用重定位項。
  • DT_JMPREL 僅與 PLT 相關聯的重定位表項的地址
  • DT_BIND_NOW 指示鏈接器在將控制權轉移給可執行程序前處理所有的重定位
  • DT_RUNPATH 庫搜索路徑的字符串表偏移量
  • DT_LOPROC, DT_HIPROC [DT_LOPROC, DT_HIPROC] 范圍內的值為特定于處理器的語義保留。

d_val 該成員表示具有各種解釋的整數值。

d_ptr 該成員表示程序虛擬地址。當解釋這些地址時,實際的地址應當基于原始文件值和內存基地址計算得出。文件不包含修正這些地址的重定位項。

_DYNAMIC 包含 .dynamic 節中的所有動態數據結構的數組。它是由鏈接器自動填充的。

Notes(Nhdr)

ELF notes 允許附加任意的信息給系統使用。它們主要用于核心轉儲文件(e_type 為 ET_CORE),但許多項目定義了它們自己的擴展集合。比如,GNU 工具鏈使用 ELF notes 從鏈接器向 C 庫傳遞信息。

Note 節包含一系列 notes(參考下面的 struct 定義)。每個 note 后跟名稱字段(其長度由 n_namesz 定義),然后是描述符字段(其長度由 n_descsz 定義),且其開始地址 4 字節對齊。由于它們的任意長度,兩個字段都沒有在 note 結構中定義。

解析兩個連續的 notes 應該闡明它們在內存中的布局的例子:

void *memory, *name, *desc;Elf64_Nhdr *note, *next_note;/* The buffer is pointing to the start of the section/segment */note = memory;/* If the name is defined, it follows the note */name = note->n_namesz == 0 ? NULL : memory + sizeof(*note);/* If the descriptor is defined, it follows the name(with alignment) */desc = note->n_descsz == 0 ? NULL :memory + sizeof(*note) + ALIGN_UP(note->n_namesz, 4);/* The next note follows both (with alignment) */next_note = memory + sizeof(*note) +ALIGN_UP(note->n_namesz, 4) +ALIGN_UP(note->n_descsz, 4);

記住 n_type 的解釋依賴于由 n_namesz 字段定義的命名空間。如果 n_namesz 字段沒有設置(比如,為 0),則有兩個 notes 集合:一個用于核心轉儲文件,另一個用于所有其它 ELF 類型。如果命名空間未知,則工具通常也將 fallback 到這些 notes 集合。

typedef struct {Elf32_Word n_namesz;Elf32_Word n_descsz;Elf32_Word n_type;} Elf32_Nhdr;typedef struct {Elf64_Word n_namesz;Elf64_Word n_descsz;Elf64_Word n_type;} Elf64_Nhdr;

n_namesz 名稱字段的字節長度。在內存中內容將緊跟在這個 note 后面。名稱以 null 終止。比如,如果名稱是 “GNU”,則 n_namesz 將被設置為 4。

n_descsz 描述符字段的字節長度。在內存中內容將緊跟在名稱字段后面。

n_type 依賴于名稱字段的值,這個成員可以具有下面的任何值:

  • 核心轉儲文件 (e_type = ET_CORE)
    Notes 被所有核心轉儲文件使用。這些是與操作系統和架構高度相關的,且通常需要與內核,C 庫,和調試器緊密配合的。當命名空間為默認值時(例如,n_namesz 將被設置為0),或者當命名空間未知時使用回退。

    • NT_PRSTATUS prstatus struct
    • NT_FPREGSET fpregset struct
    • NT_PRPSINFO prpsinfo struct
    • NT_PRXREG prxregset struct
    • NT_TASKSTRUCT task structure
    • NT_PLATFORM 來自于 sysinfo(SI_PLATFORM) 的字符串
    • NT_AUXV auxv array
    • NT_GWINDOWS gwindows struct
    • NT_ASRS asrset struct
    • NT_PSTATUS pstatus struct
    • NT_PSINFO psinfo struct
    • NT_PRCRED prcred struct
    • NT_UTSNAME utsname struct
    • NT_LWPSTATUS lwpstatus struct
    • NT_LWPSINFO lwpinfo struct
    • NT_PRFPXREG fprxregset struct
    • NT_SIGINFO siginfo_t (size might increase over time)
    • NT_FILE 包含映射文件的信息
    • NT_PRXFPREG user_fxsr_struct
    • NT_PPC_VMX PowerPC Altivec/VMX 寄存器
    • NT_PPC_SPE PowerPC SPE/EVR 寄存器
    • NT_PPC_VSX PowerPC VSX 寄存器
    • NT_386_TLS i386 TLS 槽 (struct user_desc)
    • NT_386_IOPERM x86 io permission bitmap (1=deny)
    • NT_X86_XSTATE x86 extended state using xsave
    • NT_S390_HIGH_GPRS s390 upper register halves
    • NT_S390_TIMER s390 timer register
    • NT_S390_TODCMP s390 time-of-day (TOD) clock comparator register
    • NT_S390_TODPREG s390 time-of-day (TOD) programmable register
    • NT_S390_CTRS s390 control registers
    • NT_S390_PREFIX s390 prefix register
    • NT_S390_LAST_BREAK s390 breaking event address
    • NT_S390_SYSTEM_CALL s390 system call restart data
    • NT_S390_TDB s390 transaction diagnostic block
    • NT_ARM_VFP ARM VFP/NEON registers
    • NT_ARM_TLS ARM TLS register
    • NT_ARM_HW_BREAK ARM hardware breakpoint registers
    • NT_ARM_HW_WATCH ARM hardware watchpoint registers
    • NT_ARM_SYSTEM_CALL ARM system call number
  • n_name = GNU
    GNU 工具鏈使用的擴展。

    • NT_GNU_ABI_TAG 操作系統 ABI 信息。desc 字段將有 4 個字:
      • word 0:OS 描述符(ELF_NOTE_OS_LINUXELF_NOTE_OS_GNU,等等)
      • word 1:ABI 主版本號
      • word 2:ABI 小版本號
      • word 3:ABI的次級版本號
    • NT_GNU_HWCAP 合成 hwcap 信息。desc 字段以 2 個字開頭:
      • word 0:項的個數
      • word 1:啟用的項的位掩碼
        然后是可變長度的項,一個字節后跟一個以空結尾的 hwcap 名稱字符串。該字節給出了如果啟用的話,要測試的位數,(1U << bit) & bit mask。
    • NT_GNU_BUILD_ID 由 GNU ld(1) –build-id 生成的惟一的 build ID。desc 包含任何非零字節數。
    • NT_GNU_GOLD_VERSION desc 包含使用的 GNU Gold 鏈接器版本。
  • 默認的/未知命名空間(e_type != ET_CORE)
    當命名空間為默認(比如,n_namesz 將被設置為0)時,或當命名空間未知而回退時使用。

    • NT_VERSION 一些排序的版本字符串。
    • NT_ARCH 架構信息。

https://man7.org/linux/man-pages/man5/elf.5.html

可重定位目標文件的結構及其鏈接過程

動態鏈接過程

可執行文件加載及其執行過程

一個簡單的 ELF 文件解析器:

#include <elf.h> #include <fcntl.h> #include <iostream> #include <map> #include <memory> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h>using namespace std;template <typename Ehdr, typename Shdr> int parse_section_header_table(Ehdr *ehdr, Shdr *shdr,const char *string_table) {/** Print each section header name and address. Notice we get the index into* the string table that contains each section header name with shdr.sh_name* member*/printf("Section header list:\n");for (int i = 0; i < ehdr->e_shnum; ++i) {printf("%d-%s: %p\n", i, &string_table[shdr[i].sh_name],reinterpret_cast<void *>(shdr[i].sh_addr));}return 0; }template<typename FlagType> const char* getFlagDescription(FlagType type) {static std::map<FlagType, const char*> sFlagToDescMap = {{ PF_R, "R" },{ PF_W, " W" },{ PF_X, " X" },{ PF_R | PF_W, "RW" },{ PF_R | PF_X, "R X" },{ PF_W | PF_X, " WX" },{ PF_R | PF_W | PF_X, "RWX" },};if (sFlagToDescMap.find(type) != sFlagToDescMap.end()) {return sFlagToDescMap[type];}return ""; }template<typename PType> const char* getPTypeDescription(PType type) {static std::map<PType, const char*> sPTypeToDescMap = {{ PT_LOAD, "LOAD" },{ PT_INTERP, "INTERP" },{ PT_NOTE, "NOTE" },{ PT_DYNAMIC, "DYNAMIC" },{ PT_PHDR, "PHDR" },{ PT_GNU_EH_FRAME, "GNU_EH_FRAME" },{ PT_GNU_STACK, "GNU_STACK" },{ PT_GNU_RELRO, "GNU_RELRO" },};if (sPTypeToDescMap.find(type) != sPTypeToDescMap.end()) {return sPTypeToDescMap[type];}return ""; }template <typename Ehdr, typename Phdr> int parse_program_header_table(Ehdr *ehdr, Phdr *phdr) {/** Print out each segment name, and address. Except for PT_INTERP we print the* path to the dynamic linker (Interpreter).*/printf("Program header list:\n");printf(" %-15s%-18s %-18s %-18s %-18s %-18s %-6s %-5s\n", "Type", "Offset", "VirtAddr","PhysAddr", "FileSiz", "MemSiz", "Flags", "Align");const char *segment_format_str = " %-15s0x%016x 0x%016x 0x%016x 0x%016x 0x%016x %-6s 0x%-x\n";for (int i = 0; i < ehdr->e_phnum; ++i) {printf(segment_format_str, getPTypeDescription(phdr[i].p_type),static_cast<int>(phdr[i].p_offset), static_cast<int>(phdr[i].p_vaddr),static_cast<int>(phdr[i].p_paddr), static_cast<int>(phdr[i].p_filesz),static_cast<int>(phdr[i].p_memsz), getFlagDescription(phdr[i].p_flags),phdr[i].p_align);}printf("\n");return 0; }template<typename EType> const char* getFileTypeDescription(EType type) {static std::map<EType, const char*> sTypeToDescMap = {{ ET_NONE, "NONE (None)" },{ ET_REL, "REL (Relocatable file)" },{ ET_EXEC, "EXEC (Executable file)" },{ ET_DYN, "DYN (Shared object file)" },{ ET_CORE, "CORE (Core file)" },};if (sTypeToDescMap.find(type) != sTypeToDescMap.end()) {return sTypeToDescMap[type];}return ""; }template <typename Ehdr, typename Phdr, typename Shdr> int elf_parse(void *mem, const char *filepath) {Ehdr *ehdr = reinterpret_cast<Ehdr *>(mem);/** We are only parsing executables with this code.* So ET_EXEC marks an executable.*/printf("\nELF file type is: %s.\n", getFileTypeDescription(ehdr->e_type));if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) {return -1;}printf("Program entry point: %p\n", reinterpret_cast<void *>(ehdr->e_entry));printf("Size of ELF file header: 0x%x.\n\n", static_cast<int>(sizeof(*ehdr)));/** The shdr table and phdr table offsets are given by e_shoff and e_phoff* member of the Elf32_Ehdr*/Phdr *phdr = reinterpret_cast<Phdr *>(((uint8_t *)mem) + ehdr->e_phoff);parse_program_header_table(ehdr, phdr);Shdr *shdr = reinterpret_cast<Shdr *>(((uint8_t *)mem) + ehdr->e_shoff);/** We find the string table for the section header names with e_shstrndx which* gives the index of which section holds the string table.*/char *string_table = ((char *)mem) + shdr[ehdr->e_shstrndx].sh_offset;printf("sh_offset: %u, string_table: %p\n",(uint32_t)shdr[ehdr->e_shstrndx].sh_offset, string_table);parse_section_header_table(ehdr, shdr, string_table);return 0; }class MmapObject {public:~MmapObject();void *GetMemBase();size_t GetMemSize();public:static std::shared_ptr<MmapObject> Create(int fd);private:MmapObject(void *mem_base, size_t length);private:void *mem_base_;size_t length_; };MmapObject::MmapObject(void *mem_base, size_t length): mem_base_(mem_base), length_(length) {}MmapObject::~MmapObject() {if (mem_base_) {if (munmap(mem_base_, length_) < 0) {printf("Memory unmap failed.\n");}mem_base_ = nullptr;length_ = 0;} }void *MmapObject::GetMemBase() {return mem_base_; }size_t MmapObject::GetMemSize() {return length_; }std::shared_ptr<MmapObject> MmapObject::Create(int fd) {if (fd <= 0) {return nullptr;}struct stat file_stat;if (fstat(fd, &file_stat) < 0) {perror("fstat");return nullptr;}/* Map the executable into memory */void *mem = mmap(nullptr, file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (mem == MAP_FAILED) {perror("mmap");return nullptr;}auto mem_object = std::shared_ptr<MmapObject>(new MmapObject(mem, file_stat.st_size));return mem_object; }int main(int argc, const char *argv[]) {if (argc < 2) {printf("Usage: %s <executable>\n", argv[0]);return 0;}int fd = -1;if ((fd = open(argv[1], O_RDONLY)) < 0) {perror("open");return -1;}struct stat file_stat;if (fstat(fd, &file_stat) < 0) {perror("fstat");return -1;}auto mem_object = MmapObject::Create(fd);if (!mem_object) {return -1;}void *mem = mem_object->GetMemBase();char *ident = reinterpret_cast<char *>(mem);/** Check to see if the ELF magic (The first 4 bytes) match up as 0x7f E L F*/if (strncmp(ident, ELFMAG, 4) != 0) {fprintf(stderr, "%s is not an ELF file.\n", argv[1]);return -1;}if (ident[EI_CLASS] == ELFCLASS32) {elf_parse<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr>(mem, argv[1]);} else if (ident[EI_CLASS] == ELFCLASS64) {elf_parse<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr>(mem, argv[1]);}getchar();return 0; }

總結

以上是生活随笔為你收集整理的ELF 文件格式的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。