ELF文件格式简介

Posted 落樱弥城

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ELF文件格式简介相关的知识,希望对你有一定的参考价值。

  简单了解下ELF文件的格式。

1 简介

  可执行与可链接格式 (Executable and Linkable Format,ELF),常被称为 ELF格式,是一种用于可执行文件、目标代码、共享库和核心转储(core dump)的标准文件格式,一般用于类Unix系统,比如Linux,Macox等。ELF 格式灵活性高、可扩展,并且跨平台。比如它支持不同的字节序和地址范围,所以它不会不兼容某一特别的 CPU 或指令架构。这也使得 ELF 格式能够被运行于众多不同平台的各种操作系统所广泛采纳。
  ELF文件一般由三种类型的文件:

  • 可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。比如编译的中间产物.o文件;
  • 可执行文件:一个可执行文件;
  • 共享目标文件:共享库。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。比如linux下的.so文件。

2 ELF文件格式

  在编译过程中ELF文件格式在链接和程序的运行阶段的格式不同。链接阶段每个.o文件都是一个独立的ELF文件,为了效率和便利性他们的段需要进行合并才能生成对应的可执行文件。

  ELF文件包含一个Header描述文件的基本信息;程序头表告诉徐彤如何构建进程的内存镜像,因此只有可执行文件由程序头表;Sections描述了链接过程中的需要的符号表、数据、指令等信息,而在可执行文件中是Segments,是经过合并的Secitons;节/段头表指明了对应section/segment在文件中的偏移,链接阶段的ELF文件必须包含该表头;而每个节/段头描述了对应的section/segment的大小,入口等基本信息。

  下图是32bit系统下面使用的字段的数大小,64bit系统类似,之后不在赘述。

2.1 ELF Header

  ELF文件头描述了ELF文件的基本类型,地址偏移等信息,分为32bit和64bit两个版本,定义于linux源码的/usr/include/elf.h文件中。

#define EI_NIDENT	16

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;  /* Entry point */
  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;
typedef struct elf64_hdr 
  unsigned char	e_ident[EI_NIDENT];	/* ELF "magic number" */
  Elf64_Half e_type;
  Elf64_Half e_machine;
  Elf64_Word e_version;
  Elf64_Addr e_entry;		/* Entry point virtual address */
  Elf64_Off e_phoff;		/* Program header table file offset */
  Elf64_Off e_shoff;		/* Section header table file offset */
  Elf64_Word e_flags;
  Elf64_Half e_ehsize;
  Elf64_Half e_phentsize;
  Elf64_Half e_phnum;
  Elf64_Half e_shentsize;
  Elf64_Half e_shnum;
  Elf64_Half e_shstrndx;
 Elf64_Ehdr;

  从上面的结构中能够看出32bit和64bit的区别仅仅是字长的区别,字段上没有实际上的差别。每个字段的含义如下:

  • e_ident:ELF文件的描述,是一个16字节的标识,表明当前文件的数据格式,位数等:
    • [0,3]字节为魔数,即e_ident[EI_MAG0-EI_MAG3],取值为固定的0x7f E L F,标记当前文件为一个ELF文件;
    • [4,4]字节为EI_CLASSe_ident[EI_CLASS],表明当前文件的类别:
      • 0:表示非法的类别;
      • 1:表示32bit;
      • 2:表示64bit;
    • [5,5]字节为EI_DATAe_ident[EI_DATA],表明当期那文件的数据排列方式:
      • 0表示非法;
      • 1表示小端;
      • 2表示大端;
    • [6,6]字节为EI_VERSIONe_ident[EI_VERSION],表明当前文件的版本,目前该取值必须为EV_CURRENT即1;
    • [7,7]字节为EI_PADe_ident[EI_PAD]表明e_ident中未使用的字节的起点(值是相对于e_ident[EI_PAD+1]的偏移),未使用的字节会被初始化为0,解析ELF文件时需要忽略对应的字段;

  EI_MAG0,EI_MAG1,EI_MAG2,EI_MAG3,EI_CLASS,EI_DATA,EI_VERSION,EI_OSABI,EI_PAD是linux源码中定义的宏,取值分别为0-7,分别对应各个字段的下标;下面的宏定义将采用类似EI_MAG0(0)的方式,表示EI_MAG0的值为0。

  • e_type:文件的标识字段标识文件的类型;
    • ET_NONE(0):未知的文件格式;
    • ET_REL(1):可重定位文件,比如目标文件;
    • ET_EXEC(2):可执行文件;
    • ET_DYN(3):共享目标文件;
    • ET_CORE(4):Core转储文件,比如程序crash之后的转储文件;
    • ET_LOPROC(0xff00):特定处理器的文件标识;
    • ET_HIPROC(0xffff):特定处理器的文件标识;
    • [ET_LOPROC,ET_HIPROC]之间的值用来表示特定处理器的文件格式;
  • e_machine:目标文件的体系结构(下面列举了少数处理器架构,具体ELF文件支持的架构在对应的文件中查看即可);
    • ET_NONE(0):未知的处理器架构;
    • EM_M32(1):AT&T WE 32100;
    • EM_SPARC(2):SPARC;
    • EM_386(3):Intel 80386;
    • EM_68K(4):Motorola 68000;
    • EM_88K(5):Motorola 88000;
    • EM_860(6):Intel 80860;
    • EM_MIPS(7):MIPS RS3000大端;
    • EM_MIPS_RS4_BE(10):MIPS RS4000大端;
    • 其他,预留;
  • e_version:当前文件的版本;
    • EV_NONE(0):非法的版本;
    • EV_CURRENT(`):当前版本;
  • e_entry:程序的虚拟入口地址,如果文件没有对应的入口可以为0;
  • e_phoff:文件中程序头表的偏移(bytes),如果文件没有该项,则应该为0;
  • e_shoff:文件中段表/节表的偏移(bytes),如果文件没有该项,则应该为0;
  • e_flags:处理器相关的标志位,宏格式为EF_machine_flag比如EF_MIPS_PIC
  • e_ehsize:ELF文件头的大小(bytes);
  • e_phentsize:程序头表中单项的大小,表中每一项的大小相同;
  • e_phnum:程序头表中的项数,也就是说程序头表的实际大小为ephentsize x e_phnum,如果文件中没有程序头表该项为0;
  • e_shentsize:节表中单项的大小,表中每一项的大小相同;
  • e_shnum:节表中项的数量;
  • e_shstrndx:节表中节名的索引,如果文件没有该表则该项为SHN_UNDEF(0)

2.2 程序头表(Program Header Table)

  可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个段 或者系统准备程序执行所必需的其它信息。程序头表描述了ELF文件中Segment在文件中的布局,描述了OS该如何装载可执行文件到内存。程序头表的表项的描述如下,类似于ELF Header也有32和64位两个版本。

typedef struct elf32_phdr 
	Elf32_Word p_type;
	Elf32_Off p_offset;
	Elf32_Addr p_vaddr;
	Elf32_Addr p_paddr;
	Elf32_Word p_filesz;
	Elf32_Word p_memsz;
	Elf32_Word p_flags;
	Elf32_Word p_align;
 Elf32_Phdr;
typedef struct elf64_phdr 
	Elf64_Word p_type;
	Elf64_Word p_flags;
	Elf64_Off p_offset;	/* Segment file offset */
	Elf64_Addr p_vaddr;	/* Segment virtual address */
	Elf64_Addr p_paddr;	/* Segment physical address */
	Elf64_Xword p_filesz;	/* Segment size in file */
	Elf64_Xword p_memsz;	/* Segment size in memory */
	Elf64_Xword p_align;	/* Segment alignment, file & memory */
 Elf64_Phdr;
  • p_type:当前Segment的类型;
    • PT_NULL(0):当前项未使用,项中的成员是未定义的,需要忽略当前项;
    • PT_LOAD(1):当前Segment是一个可装载的Segment,即可以被装载映射到内存中,其大小由p_fileszp_memsz描述。如果p_memsz>p_filesz则剩余的字节被置零,但是p_filesz>p_memsz是非法的。动态库一般包含两个该类型的段:代码段和数据段;
    • PT_DYNAMIC(2):动态段,动态库特有的段,包含了动态链接必须的一些信息,比如需要链接的共享库列表、GOT等等;
    • PT_INTERP(3):当前段用于存储一段以NULL为结尾的字符串,该字符串表明了程序解释器的位置。且当前段仅仅对于可执行文件有实际意义,一个可执行文件中不能出现两个当前段,如果一个文件中包含当前段。比如/lib64/ld-linux-x86-64.so.2
    • PT_NOTE(4):用于保存与特定供应商或者系统相关的附加信息以便于兼容性、一致性检查,但是实际上只保存了操作系统的规范信息;
    • PT_SHLIB(5):保留段;
    • PT_PHDR(6):保存程序头表本身的位置和大小,当前段不能在文件中出现一次以上,且仅仅当程序表头为内存映像的一部分时起作用,它必须在所有加载项目之前;
    • [PT_LPROC(0x70000000),PT_HIPROC(0x7fffffff)]:该范围内的值用作预留;
  • p_offset:当前段相对于文件起始位置的偏移量;
  • p_vaddr:段的第一个字节将被映射到到内存中的虚拟地址;
  • p_paddr:此成员仅用于与物理地址相关的系统中。因为 System V 忽略所有应用程序的物理地址信息,此字段对与可执行文件和共享目标文件而言具体内容是指定的;
  • p_filesz:段在文件映像中所占的字节数,可能为 0;
  • p_memsz:段在内存映像中占用的字节数,可能为 0;
  • p_flags:段相关的标志;
  • p_align:段在文件中和内存中如何对齐。可加载的进程段的p_vaddr和- p_offset取值必须合适,相对于对页面大小的取模而言;
    • 0和1表示不需要对齐;
    • 其他值必须为2的幂次方,且必须 p _ a d d r ∣ p _ a l i g n = = p _ o f f s e t ∣ p a l i g n p\\_addr|p\\_align==p\\_offset| p_align p_addrp_align==p_offsetpalign

2.3 节头表(Section Header Table)

  节头表描述了ELF文件中的节的基本信息。可执行文件不一定由节头表但是一定有节,节头表可利用特殊的方式去除。

  段和节的区别是:

  • 段包含了程序装载可执行的基本信息,段告诉OS如何装载当前段到虚拟内存以及当前段的权限等和执行相关的信息,一个段可以包含0个或多个节;
  • 节包含了程序的代码和数据等内容,链接器会将多个节合并为一个段。
typedef struct elf32_shdr 
  Elf32_Word	sh_name;
  Elf32_Word	sh_type;
  Elf32_Word	sh_flags;
  Elf32_Addr	sh_addr;
  Elf32_Off	sh_offset;
  Elf32_Word	sh_size;
  Elf32_Word	sh_link;
  Elf32_Word	sh_info;
  Elf32_Word	sh_addralign;
  Elf32_Word	sh_entsize;
 Elf32_Shdr;
typedef struct elf64_shdr 
  Elf64_Word sh_name;		/* Section name, index in string tbl */
  Elf64_Word sh_type;		/* Type of section */
  Elf64_Xword sh_flags;		/* Miscellaneous section attributes */
  Elf64_Addr sh_addr;		/* Section virtual addr at execution */
  Elf64_Off sh_offset;		/* Section file offset */
  Elf64_Xword sh_size;		/* Size of section in bytes */
  Elf64_Word sh_link;		/* Index of another section */
  Elf64_Word sh_info;		/* Additional section information */
  Elf64_Xword sh_addralign;	/* Section alignment */
  Elf64_Xword sh_entsize;	/* Entry size if section holds table */
 Elf64_Shdr;
  • sh_name:值是节名称在字符串表中的索引;
  • sh_type:描述节的类型和语义;
    • SHT_NULL(0):当前节是非活跃的,没有一个对应的具体的节内存;
    • SHT_PROGBITS(1):包含了程序的指令信息、数据等程序运行相关的信息;
    • SHT_SYMTAB(2):保存了符号信息,用于重定位;
      • 此种类型节的sh_link存储相关字符串表的节索引,sh_info存储最后一个局部符号的符号表索引+1;
    • SHT_DYNSYM(11):保存共享库导入动态符号信息;
      • 此种类型节的sh_link存储相关字符串表的节索引,sh_info存储最后一个局部符号的符号表索引+1;
    • SHT_STRTAB(3):一个字符串表,保存了每个节的节名称;
    • SHT_RELA(4):存储可重定位表项,可能会有附加内容,目标文件可能有多个可重定位表项;
      • 此种类型节的sh_link存储相关符号表的节索引,sh_info存储重定位所使用节的索引;
    • SHT_HASH(5):存储符号哈希表,所有参与动态链接的目标只能包含一个哈希表,一个目标文件只能包含一个哈希表;
      • 此种类型节的sh_link存储哈希表所使用的符号表的节索引,sh_info为0;
    • SHT_DYAMIC(6):存储包含动态链接的信息,一个目标文件只能包含一个;
      • 此种类型的节的sh_link存储当前节中使用到的字符串表格的节的索引,sh_info为0;
    • SHT_NOTE(7):存储以某种形式标记文件的信息;
    • SHT_NOBITS(8):这种类型的节不占据文件空间,但是成员sh_offset依然会包含对应的偏移;
    • SHT_REL(9):包含可重定位表项,无附加内容,目标文件可能有多个可重定位表项;
      • 此种类型节的sh_link存储相关符号表的节索引,sh_info存储重定位所使用节的索引;
    • SHT_SHLIB(10):保留区,包含此节的程序与ABI不兼容;
    • [SHT_LOPROC(0x70000000),SHT_HIPROC(0x7fffffff)]:留给处理器专用语义;
    • [SHT_LOUSER(0x80000000),SHT_HIUSER(0xffffffff)]:预留;
  • sh_flags:1bit位的标志位;
    • SHF_WRITE(0x1):当前节包含进程执行过程中可写的数据;
    • SHF_ALLOC(0x2):当前节在运行阶段占据内存;
    • SHF_EXECINSTR(0x4):当前节包含可执行的机器指令;
    • SHF_MASKPROC(0xf0000000):所有包含当前掩码都表示预留给特定处理器的;
  • sh_addr:如果当前节需要被装载到内存,则当前项存储当前节映射到内存的首地址,否则应该为0;
  • sh_offset:当前节的首地址相对于文件的偏移;
  • sh_size:节的大小。但是对于类型为SHT_NOBITS的节,当前值可能不为0但是在文件中不占据任何空间;
  • sh_link:存储节投标中的索引,表示当前节依赖于对应的节。对于特定的节有特定的含义,其他为SHN_UNDEF
  • sh_info:节的附加信息。对于特定的节有特定的含义,其他为0
  • sh_addralign:地址约束对齐,值应该为0或者2的幂次方,0和1表示未进行对齐;
  • sh_entsize:某些节是一个数组,对于这类节当前字段给出数组中每个项的字节数,比如符号表。如果节并不包含对应的数组,值应该为0。

2.3 一些特殊的节

  ELF文件中有一些预定义的节来保存程序、数据和一些控制信息,这些节被用来链接或者装载程序。每个操作系统都支持一组链接模式,主要分为两类(也就是常说的动态库和静态库):

  • Static:静态绑定的一组目标文件、系统库和库档案(比如静态库),解析包含的符号引用并创建一个完全自包含的可执行文件;
  • Dynamic:一组目标文件、库、系统共享资源和其他共享库链接在一起创建可执行文件。当加载此可执行文件时必须使系统中其他共享资源和动态库可用,程序才能正常运行。

  库文件无论是动态库还是静态库在其文件中都包含对应的节,一些特殊的节其功能如下:

  • .bss,类型SHT_NOBITS,属性SHF_ALLOC|SHF_WRITE:存储未经初始化的数据。根据定义程序开始执行时,系统会将这些数据初始化为0,且此节不占用文件空间;
  • .comment,类型SHT_PROGBITS,属性none:存储版本控制信息;
  • .data,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_WRITE:存放初始化的数据;
  • .data1,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_WRITE:存放初始化的数据;
  • .debug,类型SHT_PROGBITS,属性none:存放用于符号调试的信息;
  • .dynamic,类型SHT_DYNAMIC,属性SHF_ALLOC,是否有属性SHF_WRITE屈居于处理器:包含动态链接的信息,
  • .hash,类型SHT_HASH,属性SHF_ALLOC
  • .line,类型SHT_PROGBITS,属性none:存储调试的行号信息,描述源代码和机器码之间的对应关系;
  • .note,类型SHT_NOTE,属性none
  • .rodata,类型SHT_PROGBITS,属性SHF_ALLOC:存储只读数据;
  • .rodata1,类型SHT_PROGBITS,属性SHF_ALLOC:存储只读数据;
  • .shstrtab,类型SHT_STRTAB,属性none:存储节的名称;
  • .strtab,类型SHT_STRTAB:存储常见的与符号表关联的字符串。如果文件有一个包含符号字符串表的可加载段,则该段的属性将包括 SHF_ALLOC 位; 否则,该位将关闭;
  • .symtab,类型SHT_SYMTAB,属性``````:存储一个符号表。如果文件具有包含符号表的可加载段,则该节的属性将包括 SHF_ALLOC 位;否则,该位将关闭;
  • .text,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_EXECINSTR:存储程序的代码指令;
  • .dynstr,类型SHT_STRTAB,属性SHF_ALLOC:存储动态链接所需的字符串,最常见的是表示与符号表条目关联的名称的字符串;
  • .dynsym,类型SHT_DYNSYM,属性SHF_ALLOC:存储动态链接符号表;
  • .fini,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_EXECINSTR:存储有助于进程终止代码的可执行指令。 当程序正常退出时,系统执行本节代码;
  • .init,类型SHT_PROGBITS,属性SHF_ALLOC|SHF_EXECINSTR:存储有助于进程初始化代码的可执行指令。 当程序开始运行时,系统会在调用主程序入口点(C 程序称为 main)之前执行本节中的代码;
  • .interp,类型SHT_PROGBITS:保存程序解释器的路径名。 如果文件有一个包含该节的可加载段,则该节的属性将包括 SHF_ALLOC 位; 否则,该位将关闭;
  • .relname,类型SHT_REL:包含重定位信息。如果文件具有包含重定位的可加载段,则这些部分的属性将包括 SHF_ALLOC 位;否则,该位将关闭。通常,名称由 重定位适用的部分。因此.text的重定位部分通常具有名称.rel.text.rela.text
  • .relaname,类型SHT_RELA:同relname
  • 其他:对于C++程序有些版本会有.ctors(有时也会是.init_array,见Can’t find .dtors and .ctors in binary)和dtors两个节存储构造和析构相关的代码。

  带有点 (.) 前缀的部分名称是为系统保留的,但如果它们的现有含义令人满意,应用程序可以使用这些部分。 应用程序可以使用不带前缀的名称以避免与系统部分冲突。 目标文件格式允许定义不在上面列表中的部分。 一个目标文件可能有多个同名的部分。

2.4 字符串表

  字符串表是一个存储字符串的表格,而每个字符串是以NULL也就是\\0为结尾的。字符串表格中索引为0处的字符串被定义为空字符串。符号表中保存的字符串是节名和目标文件中使用到的符号。而需要使用对应字符串时,只需要在需要使用的地方指明对应字符在字符串表中的索引即可,使用的字符串就是索引处到第一个\\0之间的字符串。

2.5 符号表

  目标文件的符号表包含定位和重定位程序的符号定义和引用所需的信息。符号表索引是该数组的下标。索引0既指定表中的第一个条目,又用作未定义的符号索引。

typedef struct elf32_sym
  Elf32_Word	st_name;
  Elf32_Addr	st_value;
  Elf32_Word	st_size;
  unsigned char	st_info;
  unsigned char	st_other;
  Elf32_Half	st_shndx;
 Elf32_Sym;
typedef struct elf64_sym 
  Elf64_Word st_name;		/* Symbol name, index in string tbl */
  unsigned char	st_info;	/* Type and binding attributes */
  unsigned char	st_other;	/* No defined meaning, 0 */
  Elf64_Half st_shndx;		/* Associated section index */
  Elf64_Addr st_value;		/* Value of the symbol */
  Elf64_Xword st_size;		/* Associated symbol size */
 Elf64_Sym;
  • st_name:存储一个指向字符串表的索引来表示对应符号的名称;
  • st_value:存储对应符号的取值,具体值依赖于上下文,可能是一个指针地址,立即数等。另外,不同对象文件类型的符号表条目对 st_value 成员的解释略有不同:
    • 在重定位文件中在可重定位文件中,st_value保存节索引为SHN_COMMON的符号的对齐约束;
    • 在可重定位文件中,st_value保存已定义符号的节偏移量。 也就是说,st_value是从st_shndx标识的部分的开头的偏移量;
    • 在可执行文件和共享对象文件中,st_value保存一个虚拟地址。 为了使这些文件的符号对动态链接器更有用,节偏移(文件解释)让位于与节号无关的虚拟地址(内存解释)。
  • st_size:符号的大小,具体指为sizeof(instance),如果未知则为0;
  • st_info:指定符号的类型和绑定属性。可以用下面的代码分别解析出bind,type,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))
  • BIND
    • STB_LOCAL(0):局部符号在包含其定义的目标文件之外是不可见的。 同名的本地符号可以存在于多个文件中,互不干扰;
    • STB_GLOBAL(1):全局符号对所有正在组合的目标文件都是可见的。 一个文件对全局符号的定义将满足另一个文件对同一全局符号的未定义引用;
    • STB_WEAK(2):弱符号类似于全局符号,但它们的定义具有较低的优先级;
    • [STB_LOPROC(13),STB_HIPROC(15)]:预留位,用于特殊处理器的特定含义;
  • TYPE
    • STT_NOTYPE(0):符号的类型未指定;
    • STT_OBJECT(1):符号与数据对象相关联,例如变量、数组等;
    • STT_FUNC(2):符号与函数或其他可执行代码相关联;
    • STT_SECTION(3):该符号与一个节相关联。 这种类型的符号表条目主要用于重定位,通常具有STB_LOCALBIND属性;
    • STT_FILE(4):一个有STB_LOCAL的BIND属性的文件符号的节索引为SHN_ABS。并且如果存在其他STB_LOCAL属性的符号,则当前符号应该在其之前;
    • [STT_LOPROC(13),STT_HIPROC(15)]:预留位,用于特殊处理器的特定含义;
  • INFO
    • SHN_ABS:符号有一个绝对值,不会因为重定位而改变;
    • SHN_COMMON:该符号标记尚未分配的公共块。 符号的值给出了对齐约束,类似于节的 sh_addralign 成员。 也就是说,链接编辑器将为符号分配存储空间,该地址是 st_value 的倍数。 符号的大小表明需要多少字节;
    • SHN_UNDEF:此节表索引表示该符号未定义。 当链接编辑器将此对象文件与另一个定义指定符号的文件组合时,此文件对符号的引用将链接到实际定义;
  • st_other:该成员当前持有 0 并且没有定义的含义;
  • st_shndx:每个符号都有属于的节,当前成员存储的就是对应节的索引。

3 ELF文件示例

  下面是使用下面的代码编译生成动态库libadd.so作为示例:

//add.h
int add(int a, int b);
static int mult(int a, int b);
//add.c
//编译命令gcc add.c -shared -o libadd.so
extern int extern_value;
static int static_value = 1;
static int static_value1;

int add(int a, int b)
    return 0;


static int mult(int a, int b)
    return 1;

3.1 ELF Header

  使用命令readelf -h <ELF文件名>查看ELF文件的Header。

//readelf -h libadd.so
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4a0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6000 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         7
  Size of section headers:           64 (bytes)
  Number of section headers:         24
  Section header string table index: 23

  从上面的Magic Number中能够看出:当前文件类型为64bit的共享库,小端存储,版本为1,机器架构为x86-64,程序头表项有7项,节头表项有24项。

3.2 Program Header Table

  使用命令readelf -l <ELF文件名>查看程序头表;

//readelf -l libadd.so
Elf file type is DYN (Shared object file)
Entry point 0x4a0
There are 7 program headers, starting at offset 64
Program Headers:
  Type           Offset             VirtAddr           PhysAddr           FileSiz            MemSiz              Flags  Align 
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000674 0x0000000000000674  R E    0x200000
  LOAD           0x0000000000000e80 0x0000000000200e80 0x0000000000200e80 0x00000000000001a4 0x00000000000001b0  RW     0x200000
  DYNAMIC        0x0000000000000e90 0x0000000000200e90 0x0000000000200e90 0x0000000000000150 0x0000000000000150  RW     0x8
  NOTE           0x00000000000001c8 0x00000000000001c8 0x00000000000001c8 0x0000000000000024 0x0000000000000024  R      0x4
  GNU_EH_FRAME   0x00000000000005a8 0x00000000000005a8 0x00000000000005a8 0x000000000000002c 0x000000000000002c  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000000e80 0x0000000000200e80 0x0000000000200e80 0x0000000000000180 0x0000000000000180  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .rela.dyn .init .plt .plt.got .text .fini .eh_frame_hdr .eh_frame
   01     .init_array .fini_array .dynamic .got .got.plt .data .bss
   02     .dynamic
   03     .note.gnu.build-id
   04     .eh_frame_hdr
   05
   06     .init_array .fini_array .dynamic .got

  从上面看出上半部分的内容基本和程序头表项的每个字段基本对应。从下面的Segment Sections可以看出一个Segment是多个Section的集合。

3.3 Section Header Table

  使用命令readelf -S <ELF文件名>查看节头表的内容。

➜  tmp readelf -S libadd.so
There are 24 section headers, starting at offset 0x1770:

Section Headers:
  [Nr] Name              Type             Address           Offset   Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000 0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.build-i NOTE             00000000000001c8  000001c8 0000000000000024  0000000000000000   A       0     0     4
  [ 2] .gnu.hash         GNU_HASH         00000000000001f0  000001f0 000000000000003c  0000000000000000   A       3     0     8
  [ 3] .dynsym           DYNSYM           0000000000000230  00000230 0000000000000108  0000000000000018   A       4     1     8
  [ 4] .dynstr           STRTAB           0000000000000338  00000338 000000000000007d  0000000000000000   A       0     0     1
  [ 5] .rela.dyn         RELA             00000000000003b8  000003b8 00000000000000a8  0000000000000018   A       3     0     8
  [ 6] .init             PROGBITS         0000000000000460  00000460 0000000000000017  0000000000000000  AX       0     0     4
  [ 7] .plt              PROGBITS         0000000000000480  00000480 0000000000000010  0000000000000010  AX       0     0     16
  [ 8] .plt.got          PROGBITS         0000000000000490  00000490 0000000000000008  0000000000000008  AX       0     0     8
  [ 9] .text             PROGBITS         00000000000004a0  000004a0 00000000000000fc  0000000000000000  AX       0     0     16
  [10] .fini             PROGBITS         000000000000059c  0000059c 0000000000000009  0000000000000000  AX       0     0     4
  [11] .eh_frame_hdr     PROGBITS         00000000000005a8  000005a8 000000000000002c  0000000000000000   A       0     0     4
  [12] .eh_frame         PROGBITS         00000000000005d8  000005d8 000000000000009c  0000000000000000   A       0     0     8
  [13] .init_array       INIT_ARRAY       0000000000200e80  00000e80 0000000000000008  0000000000000008  WA       0     0     8
  [14] .fini_array       FINI_ARRAY       0000000000200e88  00000e88 0000000000000008  0000000000000008  WA       0     0     8
  [15] .dynamic          DYNAMIC          0000000000200e90  00000e90 0000000000000150  0000000000000010  WA       4     0     8
  [16] .got              PROGBITS         0000000000200fe0  00000fe0 0000000000000020  0000000000000008  WA       0     0     8
  [17] .got.plt          PROGBITS         0000000000201000  00001000 0000000000000018  0000000000000008  WA       0     0     8
  [18] .data             PROGBITS         0000000000201018  00001018 000000000000000c  0000000000000000  WA       0     0     8
  [19] .bss              NOBITS           0000000000201024  00001024 000000000000000c  0000000000000000  WA       0     0     4
  [以上是关于ELF文件格式简介的主要内容,如果未能解决你的问题,请参考以下文章

ELF文件格式简介

ELF文件格式简介

ELF文件格式简介

ELF文件格式分析

Android 逆向ELF 文件格式 ( ELF 文件头 | ELF 文件头标志 | ELF 文件位数 | ELF 文件大小端格式 )

Android 逆向ELF 文件格式 ( ELF 文件头 | ELF 文件头标志 | ELF 文件位数 | ELF 文件大小端格式 )