ELF 文件详解

ELF 文件详解

Segment 和 Section 的意义

在ELF文件中,会有很多的Section(比如.text、.bss),那么为什么还要有 Segment 这个概念,以下是一些原因:

  1. 区分加载和非加载部分:
    • Section 包含了所有类型的内容,包括代码段、数据段、符号表、调试信息等。
    • Segment 主要用于指示操作系统如何加载程序的不同部分到内存中,因此只包含那些需要加载到内存中的 section(例如 .text、.data 和 .bss 段),而不会包含调试信息、符号表等不需要加载的部分。
  2. 简化加载过程:
    • Segment 提供了一种简单的方式,告诉操作系统如何一次性加载多个相关的 section。
    • 例如,多个 section 可以被归类到同一个 segment 中,以减少加载过程中需要的 I/O 操作次数。
  3. 权限和属性管理:
    • 不同的 segment 可以有不同的权限和属性(例如可读、可写、可执行)。
    • 通过将需要相同权限和属性的 section 归类到同一个 segment,可以更容易地设置和管理内存权限。

ELF Header

ElfN_Ehdr结构体

typedef 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;

ElfN_Ehdr结构体字段含义:

e_ident

这个数组里面有一些关于这个ELF文件的信息

有一些宏用来索引这些信息:

#define EI_MAG0		0		/* File identification byte 0 index */

#define EI_MAG1		1		/* File identification byte 1 index */

#define EI_MAG2		2		/* File identification byte 2 index */

#define EI_MAG3		3		/* File identification byte 3 index */

#define EI_CLASS	4		/* File class byte index */

#define EI_DATA		5		/* Data encoding byte index */

#define EI_VERSION	6		/* File version byte index */

#define EI_OSABI	7		/* OS ABI identification */

#define EI_ABIVERSION	8		/* ABI version */

#define EI_PAD		9		/* Byte index of padding bytes */
EI_MAG0 && EI_MAG1 && EI_MAG2 && EI_MAG3

上面这四个宏是用来索引e_ident数组的前四个字节的,这四个字节是ELF文件最开头的四个字节,而且这四个字节中的内容在不同的ELF文件中都是一样的,连起来就是"\177ELF",有什么用呢,其实就是相当于这个文件的标识符一样,当操作系统看到一开头的四个字节是"\177ELF",它就认为这是一个合法的ELF文件

EI_CLASS

可以通过这个宏索引e_ident数组的第五个字节

这个字节存放的数据只会是下面这些宏:

#define ELFCLASSNONE	0		/* Invalid class */
#define ELFCLASS32	1		/* 32-bit objects */
#define ELFCLASS64	2		/* 64-bit objects */

通过这个字节可以知道这个ELF文件是什么类型,这里的类型指的是这个程序是运行在32位机上还是64位机上,如果这是一份有问题的ELF文件,则会是ELFCLASSNONE

EI_DATA

可以通过这个宏索引e_ident数组的第六个字节

这个字节存放的数据只会是下面这些宏:

#define ELFDATANONE	0		/* Invalid data encoding */
#define ELFDATA2LSB	1		/* 2's complement, little endian */
#define ELFDATA2MSB	2		/* 2's complement, big endian */

通过这个字节可以知道这个ELF文件是的数据编码是大端序还是小端序,如果这份ELF文件的数据编码是有问题的,则会是ELFDATANONE

EI_VERSION

可以通过这个宏索引e_ident数组的第七个字节

这个字节存放的数据是这个ELF文件的版本,我还在想一个文件有什么版本之分,问了一下AI,这是它的解释:

随着 ELF 格式的演变,可能会引入新的字段、结构或约定。版本信息可以帮助工具(如链接器、加载器、调试器等)判断一个特定的 ELF 文件是使用哪个版本的格式生成的,从而采取适当的处理方式。

EI_OSABI

可以通过这个宏索引e_ident数组的第八个字节

这个字节存放的数据只会是下面这些宏:

#define ELFOSABI_NONE		0	/* UNIX System V ABI */
#define ELFOSABI_SYSV		0	/* Alias.  */
#define ELFOSABI_HPUX		1	/* HP-UX */
#define ELFOSABI_NETBSD		2	/* NetBSD.  */
#define ELFOSABI_GNU		3	/* Object uses GNU ELF extensions.  */
#define ELFOSABI_LINUX		ELFOSABI_GNU /* Compatibility alias.  */
#define ELFOSABI_SOLARIS	6	/* Sun Solaris.  */
#define ELFOSABI_AIX		7	/* IBM AIX.  */
#define ELFOSABI_IRIX		8	/* SGI Irix.  */
#define ELFOSABI_FREEBSD	9	/* FreeBSD.  */
#define ELFOSABI_TRU64		10	/* Compaq TRU64 UNIX.  */
#define ELFOSABI_MODESTO	11	/* Novell Modesto.  */
#define ELFOSABI_OPENBSD	12	/* OpenBSD.  */
#define ELFOSABI_ARM_AEABI	64	/* ARM EABI */
#define ELFOSABI_ARM		97	/* ARM */
#define ELFOSABI_STANDALONE	255	/* Standalone (embedded) application */

通过这个字节可以知道这个ELF文件是运行在什么操作系统上的

EI_ABIVERSION

可以通过这个宏索引e_ident数组的第九个字节

这个字节标识了目标对象所针对的 ABI 版本。该字段用于区分不兼容版本的 ABI。此版本号的解释取决于 EI_OSABI 字段所标识的 ABI。符合此规范的应用程序使用值 0。(说实话,不是很懂zzz)

EI_PAD

可以通过这个宏索引e_ident数组的第十个字节

这个字节就是用告诉你从这里开始直到这个数组最后一个字节都是用来填充的,是被保留的,现在没啥用,未来可能有用

e_type

这个字段用来标识目标文件类型

这个字段存放的数据只会是下面这些宏:

#define ET_NONE		0		/* No file type */
#define ET_REL		1		/* Relocatable file */
#define ET_EXEC		2		/* Executable file */
#define ET_DYN		3		/* Shared object file */

注释写的很清楚了,不再赘述

e_machine

这个字段用来指定所需的架构

这个字段存放的数据只会是下面这些宏(太多了,随便列了一些):

#define EM_NONE		 0	/* No machine */
#define EM_M32		 1	/* AT&T WE 32100 */
#define EM_SPARC	 2	/* SUN SPARC */
#define EM_386		 3	/* Intel 80386 */
#define EM_68K		 4	/* Motorola m68k family */
#define EM_88K		 5	/* Motorola m88k family */
#define EM_IAMCU	 6	/* Intel MCU */
#define EM_860		 7	/* Intel 80860 */
#define EM_MIPS		 8	/* MIPS R3000 big-endian */
#define EM_S370		 9	/* IBM System/370 */
#define EM_MIPS_RS3_LE	10	/* MIPS R3000 little-endian */

注释写的很清楚了,不再赘述

e_version

这个字段用来标识文件版本

十分甚至有九分不对劲,我之前是不是写过,对呀,之前的e_ident数组里面不就已经记录过这个信息吗,这里同样是AI的解释:

这两个字段在功能上是相似的,都是用来指示 ELF 文件的版本信息。重复记录的原因可能是为了增强可读性和结构清晰性,使得不同的解析程序能够独立处理和理解文件格式和 ABI 版本,而不需要依赖特定的字段索引。这也有助于在某些情况下进行快速的检查和验证。

emmmmm...whatever

e_entry

这个字段提供了系统首次转移控制的虚拟地址,从而启动进程。如果文件没有关联的入口点,该成员的值为。

其实就是程序开始执行时的pc值

e_phoff

这个字段保存程序头表的文件偏移量,以字节为单位。如果文件没有程序头表,则此成员持有零。

这位是重量级

e_shoff

这个字段保存节头表的文件偏移量,以字节为单位。如果文件没有节头表,则此成员持有零。

这位更是重量级

e_flags

这个字段保存与文件相关的特定于处理器的标志。标志名称的格式为 EF_machine_flag。目前尚未定义任何标志。(不是很懂)

e_ehsize

这个字段保存ELF header的大小,也就是sizeof(ElfN_Ehdr)

e_phentsize

这个字段保存program header table中每一个条目(entry)的大小

e_phnum

这个字段保存program header table中的条目数

e_shentsize

这个字段保存sections header的大小,以字节为单位,一个sections header就是section header table中的一个条目

e_shnum

这个字段保存section header table中的条目数

e_shstrndx

这个字段保存一个索引(index),可以用来在section header table中找到一个特定的section header,很特殊,这个section header所描述的section是一个字符数组,里面保存着所有section的名字(name),需要注意的是,这个字段所保存的这个索引值是从1开始计数的,也就是说假设section header table有一共有十个section header,e_shstrndx == 6,那么e_shstrndx所指向的section header就是第6个(下标为5)

Program header

ElfN_Phdr结构体

typedef struct
{
  Elf32_Word	p_type;			/* Segment type */
  Elf32_Off	p_offset;		/* Segment file offset */
  Elf32_Addr	p_vaddr;		/* Segment virtual address */
  Elf32_Addr	p_paddr;		/* Segment physical address */
  Elf32_Word	p_filesz;		/* Segment size in file */
  Elf32_Word	p_memsz;		/* Segment size in memory */
  Elf32_Word	p_flags;		/* Segment flags */
  Elf32_Word	p_align;		/* Segment alignment */
} Elf32_Phdr;

typedef struct
{
  Elf64_Word	p_type;			/* Segment type */
  Elf64_Word	p_flags;		/* Segment 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 */
} Elf64_Phdr;

ElfN_Phdr结构体字段含义

p_type

这个字段告诉你这个program header描述的是哪一种segment,或者告诉你应该怎么解析这个program header

p_offset

这个字段保存该段的第一个字节在文件开头的偏移量。

p_vaddr

这个字段保存该段在内存中第一个字节的虚拟地址。

p_paddr

在物理寻址相关的系统中,这个字段保留用于段的物理地址。在 BSD 系统中,这个字段未使用,必须为零。

p_filesz

这个字段保存段在ELF文件中的字节数。它的值可以为零。

p_memsz

这个字段保存段在内存中的字节数。它的值可以为零。

p_flags

这个字段保存与段相关的标志位掩码

具体有这些:

#define PF_X		(1 << 0)	/* Segment is executable */
#define PF_W		(1 << 1)	/* Segment is writable */
#define PF_R		(1 << 2)	/* Segment is readable */

这里应该填入以上标志位掩码的不同组合的按位或结果

举个例子,如果一个段同时具有执行(PF_X)和写(PF_W)权限,那么可以使用按位或运算符(|)组合这两个标志位:

p_flags = PF_X | PF_W  

p_align

该成员保存段在内存和文件中对齐的值。 可加载进程段(Loadable process segments)的 p_vaddr 和 p_offset 必须具有一致的值(以页面大小为模)。 值 0 和 1 表示不需要对齐。 否则,p_align 应为 2 的正整数幂,并且 p_vaddr 应等于 p_offset,以 p_align 为模。

以上这些字段的关系是:

p_vaddr % page size == p_offset % page size(任何时候都成立)
p_align 为 2 的正整数幂时,p_vaddr % p_align == p_offset % p_align

Section Header

ElfN_Shdr结构体

typedef struct
{
  Elf32_Word	sh_name;		/* Section name (string tbl index) */
  Elf32_Word	sh_type;		/* Section type */
  Elf32_Word	sh_flags;		/* Section flags */
  Elf32_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf32_Off	sh_offset;		/* Section file offset */
  Elf32_Word	sh_size;		/* Section size in bytes */
  Elf32_Word	sh_link;		/* Link to another section */
  Elf32_Word	sh_info;		/* Additional section information */
  Elf32_Word	sh_addralign;		/* Section alignment */
  Elf32_Word	sh_entsize;		/* Entry size if section holds table */
} Elf32_Shdr;

typedef struct
{
  Elf64_Word	sh_name;		/* Section name (string tbl index) */
  Elf64_Word	sh_type;		/* Section type */
  Elf64_Xword	sh_flags;		/* Section flags */
  Elf64_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf64_Off	sh_offset;		/* Section file offset */
  Elf64_Xword	sh_size;		/* Section size in bytes */
  Elf64_Word	sh_link;		/* Link to 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;

ElfN_Shdr结构体字段含义

sh_name

这个字段指定节的名称。它的值是节头字符串表节(section header string table section)的索引,给出以 null 结尾的字符串的位置。

sh_type

这个字段对该部分的内容和语义进行分类

这个字段存放的数据只会是下面这些宏(太多了,随便列了一些):

#define SHT_NULL	  0		/* Section header table entry unused */
#define SHT_PROGBITS	  1		/* Program data */
#define SHT_SYMTAB	  2		/* Symbol table */
#define SHT_STRTAB	  3		/* String table */
#define SHT_RELA	  4		/* Relocation entries with addends */
#define SHT_HASH	  5		/* Symbol hash table */
#define SHT_DYNAMIC	  6		/* Dynamic linking information */
#define SHT_NOTE	  7		/* Notes */
#define SHT_NOBITS	  8		/* Program space with no data (bss) */
#define SHT_REL		  9		/* Relocation entries, no addends */

sh_flags

这个字段的每一位有不同的含义

节支持描述各种属性的一位标志。如果在 sh_flags 中设置了标志位,则该部分的属性为“on”。否则,该属性为“off”或不适用。未定义的属性设置为零。

这个字段存放的数据只会是下面这些宏之间或运算后的结果:

#define SHF_WRITE	     (1 << 0)	/* Writable */
#define SHF_ALLOC	     (1 << 1)	/* Occupies memory during execution */
#define SHF_EXECINSTR	     (1 << 2)	/* Executable */
#define SHF_MERGE	     (1 << 4)	/* Might be merged */
#define SHF_STRINGS	     (1 << 5)	/* Contains nul-terminated strings */
#define SHF_INFO_LINK	     (1 << 6)	/* `sh_info' contains SHT index */
#define SHF_LINK_ORDER	     (1 << 7)	/* Preserve order after combining */
#define SHF_OS_NONCONFORMING (1 << 8)	/* Non-standard OS specific handling
					   required */
#define SHF_GROUP	     (1 << 9)	/* Section is member of a group.  */
#define SHF_TLS		     (1 << 10)	/* Section hold thread-local data.  */
#define SHF_COMPRESSED	     (1 << 11)	/* Section with compressed data. */
#define SHF_MASKOS	     0x0ff00000	/* OS-specific.  */
#define SHF_MASKPROC	     0xf0000000	/* Processor-specific */
#define SHF_GNU_RETAIN	     (1 << 21)  /* Not to be GCed by linker.  */
#define SHF_ORDERED	     (1 << 30)	/* Special ordering requirement
					   (Solaris).  */
#define SHF_EXCLUDE	     (1U << 31)	/* Section is excluded unless
					   referenced or allocated (Solaris).*/
SHF_WRITE

这个宏意味着这个section包含着可写(writable)的数据

SHF_ALLOC

这个宏意味着这个section将来会被加载进内存,某些控制节(control sections)并不驻留在目标文件的内存映像中。对于这些节,此属性是“off”。

SHF_EXECINSTR

这个宏意味着这个section包含着可执行(executable)的机器指令(machine instructions)

SHF_MERGE

这个宏意味着这个section可能会被合并。具有相同内容的部分可以合并以减少空间使用。

SHF_STRINGS

这个宏意味着这个section包含以 NULL 结尾的字符串。

define SHF_LINK_ORDER (1 << 7) /* Preserve order after combining */

define SHF_OS_NONCONFORMING (1 << 8) /* Non-standard OS specific handling

				   required */

define SHF_GROUP (1 << 9) /* Section is member of a group. */

define SHF_TLS (1 << 10) /* Section hold thread-local data. */

define SHF_COMPRESSED (1 << 11) /* Section with compressed data. */

define SHF_MASKOS 0x0ff00000 /* OS-specific. */

define SHF_MASKPROC 0xf0000000 /* Processor-specific */

define SHF_GNU_RETAIN (1 << 21) /* Not to be GCed by linker. */

define SHF_ORDERED (1 << 30) /* Special ordering requirement

				   (Solaris).  */

define SHF_EXCLUDE (1U << 31) /* Section is excluded unless

				   referenced or allocated (Solaris).*/

sh_addr

如果这个节将会被加载进内存,那么这个字段就意味着这个节的第一个字节将被加载到哪个地址,否则,这个字段为零

sh_offset

这个字段表示从文件开头到该节(section)的第一个字节之间的字节偏移量。

有种特殊的节类型:SHT_NOBITS,在文件中不占用空间,但它的 sh_offset 字段表示该段在文件中的概念性位置。
这种段实际上不会在文件中存储数据,但 sh_offset 仍然用来指示它在文件中的逻辑位置。

sh_size

这个字段表示该节(section)的大小,以字节为单位

sh_info

这个字段包含一些额外的信息,如何解读这个字段取决于这个节(section)的类型

sh_addralign

某些节(section)具有地址对齐限制。如果一个节包含一个双字(doubleword),系统必须确保整个节的双字对齐。 那就是
sh_addr 的值必须等于 0,模
sh_addralign 的值。 仅零和正积分
允许两个权力。 值 0 或 1 表示
该部分没有对齐约束。

sh_entsize

如果这个section header所描述的section被组织成一个列表(table)的形式,那么这个字段就会指出这张表的每一个条目的大小

posted @   鳯先生  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示