elf文件格式
读取elf文件的信息,头部信息的读取和含义。
这是在ndk中头文件的elf头部信息的结构体:
typedef struct elf32_hdr {unsigned char e_ident[EI_NIDENT];Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;Elf32_Addr e_entry;Elf32_Off e_phoff;Elf32_Off e_shoff;Elf32_Word e_flags;Elf32_Half e_ehsize;Elf32_Half e_phentsize;Elf32_Half e_phnum;Elf32_Half e_shentsize;Elf32_Half e_shnum;Elf32_Half e_shstrndx;Elf32_Ehdr;
头部信息e_ident
typedef struct elf32_hdr {unsigned char e_ident[EI_NIDENT];<-----------}
文件类型
e_ident[EI_CLASS] 文件类型
标识文件的类型或容量
| 名称 | 值 | 意义 |
|---|---|---|
| ELFCLASSNONE | 0 | 无效类型 |
| ELFCLASS32 | 1 | 32 位文件 |
| ELFCLASS64 | 2 | 64 位文件 |
读取这个位置的代码:
switch (elf_header.e_ident[EI_CLASS]) {case ELFCLASS32:LOGD("32-bit objects\n");break;case ELFCLASS64:LOGD("e_ident[EI_CLASS] 64-bit objects\n");break;default:LOGD("INVALID CLASS\n");break;}
大小端
ELF(Executable and Linkable Format)文件中的大小端(Endian)特性是因为不同的计算机硬件架构对二进制数据的存储序列和解读方式不同,这种差异称为端序或字节序。
有两种类型的端序:大端序(Big Endian)和小端序(Little Endian)。
大端序:高位字节在前,低位字节在后,这是人类阅读数值的方式,如
0x12345678在内存中的存储方式为12 34 56 78。小端序:低位字节在前,高位字节在后,如
0x12345678在内存中的存储方式为78 56 34 12。
这两种方式各有优势,一些硬件(如大部分ARM,x86和x86_64)选择使用小端序,另一些硬件(如大部分PowerPC和SPARC)选择使用大端序。有些硬件可以在两种模式间切换(如部分ARM,MIPS)。
ELF文件被设计用来在不同的硬件平台上存储二进制代码和数据,所以ELF文件中需要包含端序信息,以便系统知道如何正确地解读文件中的数据。
在ELF文件头部,有一个字段叫做e_ident[EI_DATA],这个字段就是用来标识文件中数据的端序,也正是因为这个设计,使得ELF文件格式具有很好的跨平台特性。
| 名称 | 值 | 意义 |
|---|---|---|
| ELFDATANONE | 0 | 无效数据编码 |
| ELFDATA2LSB | 1 | 小端 |
| ELFDATA2MSB | 2 | 大端 |
switch (elf_header.e_ident[EI_DATA]) {case ELFDATA2LSB:LOGD("2's complement, little endian\n");break;case ELFDATA2MSB:LOGD("2's complement, big endian\n");break;default:LOGD("INVALID Format\n");break;}
文件类型e_type
typedef struct elf32_hdr {unsigned char e_ident[EI_NIDENT];Elf32_Half e_type;<-----------}
详细的类型
| 名称 | 值 | 意义 |
|---|---|---|
| ET_NONE | 0 | 无文件类型 |
| ET_REL | 1 | 可重定位文件 |
| ET_EXEC | 2 | 可执行文件 |
| ET_DYN | 3 | 共享目标文件 |
| ET_CORE | 4 | 核心转储文件 |
| ET_LOPROC | 0xff00 | 处理器指定下限 |
| ET_HIPROC | 0xffff | 处理器指定上限 |
读取代码:
switch (elf_header.e_type) {case ET_NONE:LOGD("N/A (0x0)\n");break;case ET_REL:LOGD("Relocatable\n");break;case ET_EXEC:LOGD("Executable\n");break;case ET_DYN:LOGD("Shared Object\n");break;default:LOGD("Unknown (0x%x)\n", elf_header.e_type);break;}
machine在那些机器上运行
typedef struct elf32_hdr {unsigned char e_ident[EI_NIDENT];Elf32_Half e_type;Elf32_Half e_machine;<-----------}
e_machine 字段存储了目标体系结构的信息,用于表示可执行文件适用的处理器架构类型。
具体来说,e_machine字段指定了可执行文件被设计用来在哪种处理器架构上执行。该字段的取值是一个标识符,代表不同的体系结构。例如,一些常见的取值包括:
0x03:Intel 80386
0x08:Intel 8086
0x14:Sun SPARC
0x28:ARM
0x3E:AMD x86-64
0xB7:AArch64
0xF3:RISC-V
e_machine字段的取值被定义在 ELF 文件格式规范中,并且可以被用来区分不同的体系结构类型,以便操作系统和加载器能够正确地识别和执行可执行文件。
switch (elf_header.e_machine) {case EM_NONE:LOGD("None (0x0)\n");break;case EM_386:LOGD("INTEL x86 (0x%x)\n", EM_386);break;case EM_X86_64:LOGD("AMD x86_64 (0x%x)\n", EM_X86_64);break;case EM_AARCH64:LOGD("AARCH64 (0x%x)\n", EM_AARCH64);break;default:LOGD(" 0x%x\n", elf_header.e_machine);break;}
e_version
typedef struct elf32_hdr {unsigned char e_ident[EI_NIDENT];Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;<-----------}
在 ELF 文件格式中,e_version 字段表示 ELF 文件格式的版本。目前有两个主要的版本:
EV_NONE:这个值表示未指定版本,通常在一些旧的 ELF 文件中会看到。
EV_CURRENT:这个值表示当前的 ELF 文件格式版本,对应于当前所使用的 ELF 文件格式规范版本。
除了这两个主要的版本之外,还有一些其他的取值,但它们都是一些特定的实现或者特殊情况所使用的。在实际的应用中,我们一般只需要关注 EV_NONE 和 EV_CURRENT 这两个版本。
在实际编程中,当创建一个新的 ELF 文件时,我们应该将e_version字段设置为 EV_CURRENT,以表示这个 ELF 文件符合当前的 ELF 文件格式规范版本。
e_entry
typedef struct elf32_hdr {unsigned char e_ident[EI_NIDENT];Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;Elf32_Addr e_entry;<-----------}
elf_header.e_entry 字段指定了 ELF 可执行文件的入口点,也就是程序开始执行的地址。当操作系统加载可执行文件时,它会将控制转移至e_entry字段指定的地址,从而启动程序的执行。
在 ELF 文件中,e_entry字段存储的是一个虚拟地址(Virtual Address),这个地址通常指向程序入口函数(例如C/C++程序的main函数),或者是程序的启动代码(比如汇编程序的入口点)。
操作系统会在加载可执行文件时把e_entry字段指定的地址作为程序计数器(Program Counter,PC)的初始值,从而开始执行程序代码。因此,e_entry字段在 ELF 文件中的作用非常重要,它标识了程序的起始执行位置。
总之,elf_header.e_entry 字段的作用是告诉操作系统加载器和执行器程序应该从哪个地址开始执行,从而启动程序的运行。
readelf读取
readelf -h libc.soELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: DYN (Shared object file)Machine: AArch64Version: 0x1Entry point address: 0x3c000Start of program headers: 64 (bytes into file)Start of section headers: 1011408 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 11Size of section headers: 64 (bytes)Number of section headers: 29Section header string table index: 27
代码读取后:
print_elf_header64 >>>>>>>>>>>>> startStorage class =64-bit objectsData format =complement, little endianOS ABI =UNIX System V ABIFiletype =Shared ObjectMachine =AARCH64 (0xb7)ELF e_version = 1Entry point = 0x0003c000ELF header size = 0x00000040Program Header =Program Header table OFFset 0x00000040Number of program headers 11 entriesSize of program headers 56 bytesSection Header =0x000f6ed0Number of section headers 29 entriesSize of section headers 64 bytes0x0000001b (string table offset) 27File flags = 0x00000000ARM EABI = Version 0
读取出来的情况和工具一样。这是个笔记,其实看完了很快忘记了。如果自己跟着readelf和每一步代码读取的过程来学习,可能会更深刻点。

