#ELF 文件结构详解#简介本文在不特殊声明的情况下,默认架构为 x86-64,操作系统内核为 linux
ELF(Executable and Linkable Format) 是 linux 平台上的目标文件,主要有以下三种类型:
.o,这种文件一般和其他目标文件一起被链接器链接成可执行文件或者共享目标文件.so,就是我们常说的“库文件”ELF 的文件结构如下图所示:

这是一张很经典的用来讲解 ELF 文件结构的图,ELF 文件结构主要是从两方面解析:执行视图和链接视图。从 ELF 文件的全名也可看出,一个合法的 ELF 文件既有可能参与链接,也有可能直接执行。
#文件结构#宏观视图链接视图

执行视图

注意,所谓“链接视图”和“执行视图”其实是看待 ELF 文件的两种不同的视角,实际上在一个合法的 ELF 文件中,二者是完全重合的,也就是本文第一张图。
我们把上面两种视图结合起来分析,其实 sections 和 segments 在 ELF 中占用的是一样的地方,其中,sections 是程序员可见的,是给链接器使用的概念,而 segments 是程序员不可见的,是给加载器使用的概念。一般来讲,一个 segement 可以包含多个 sections
另外,尽管这些图中是按照 ELF 头,程序头部表,节区,节区头部表的顺序排列的,但实际上除了 ELF 头部表以外,其它部分都没有严格的的顺序。
#数据结构这部分可以直接参考 elf.h 里的源码。
/* 32-bit ELF base types. */
typedef __u32 Elf32_Addr; // unsigned int
typedef __u16 Elf32_Half; // unsigned short
typedef __u32 Elf32_Off; // unsigned int
typedef __s32 Elf32_Sword; // __signed__ int
typedef __u32 Elf32_Word; // unsigned int
/* 64-bit ELF base types. */
typedef __u64 Elf64_Addr; // unsigned long long
typedef __u16 Elf64_Half; // unsigned short
typedef __s16 Elf64_SHalf; // __signed__ short
typedef __u64 Elf64_Off; // unsigned long long
typedef __s32 Elf64_Sword; // __signed__ int
typedef __u32 Elf64_Word; // unsigned int
typedef __u64 Elf64_Xword; // unsigned long long
typedef __s64 Elf64_Sxword; // __signed__ long long
/* 32-bit ELF base types. */
typedef __u32 Elf32_Addr; // unsigned int
typedef __u16 Elf32_Half; // unsigned short
typedef __u32 Elf32_Off; // unsigned int
typedef __s32 Elf32_Sword; // __signed__ int
typedef __u32 Elf32_Word; // unsigned int
/* 64-bit ELF base types. */
typedef __u64 Elf64_Addr; // unsigned long long
typedef __u16 Elf64_Half; // unsigned short
typedef __s16 Elf64_SHalf; // __signed__ short
typedef __u64 Elf64_Off; // unsigned long long
typedef __s32 Elf64_Sword; // __signed__ int
typedef __u32 Elf64_Word; // unsigned int
typedef __u64 Elf64_Xword; // unsigned long long
typedef __s64 Elf64_Sxword; // __signed__ long long
很清楚了。
#ELF Header源码如下
#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;
#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;
以 64 位架构为例分析一下:
| 字段名 | 解释 | 字节数 |
|---|---|---|
| e_ident | ELF的一些标识信息,固定值 | 16 |
| e_type | 目标文件类型:1-可重定位文件,2-可执行文件,3-共享目标文件 | 2 |
| e_machine | 文件的目标系统架构 | 2 |
| e_version | 目标文件版本:1-当前版本 | 4 |
| e_entry | 程序入口的虚拟地址,没有可为0 | 8 |
| e_phoff | 程序头表(segment header table)的偏移量,没有可为0 | 8 |
| e_shoff | 节区头表(section header table)的偏移量,没有可为0 | 8 |
| e_flags | 与文件相关的,特定于处理器的标志 | 4 |
| e_ehsize | ELF头部的大小,单位字节 | 2 |
| e_phentsize | 程序头表每个表项的大小,单位字节 | 2 |
| e_phnum | 程序头表表项的个数 | 2 |
| e_shentsize | 节区头表每个表项的大小,单位字节 | 2 |
| e_shnum | 节区头表表项的数目 | 2 |
| e_shstrndx | 某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数 | 2 |
#program header table/* These constants define the permissions on sections in the program
header, p_flags. */
#define PF_R 0x4
#define PF_W 0x2
#define PF_X 0x1
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; /* 从文件开始到该段开头的第一个字节的偏移 */
Elf64_Addr p_vaddr; /* 该段第一个字节在内存中的虚拟地址 */
Elf64_Addr p_paddr; /* 该字段仅用于物理地址寻址相关的系统中 */
/* 由于”System V”忽略了应用程序的物理寻址 */
/* 可执行文件和共享目标文件的该项内容并未被限定 */
Elf64_Xword p_filesz; /* 文件镜像中该段的大小,可能为0 */
Elf64_Xword p_memsz; /* 内存镜像中该段的大小,可能为0 */
Elf64_Xword p_align; /* 可加载的程序的段的 p_vaddr 以及 p_offset 的大小必须是 page 的整数倍。该成员给出了段在文件以及内存中的对齐方式。如果该值为 0 或 1 的话,表示不需要对齐。除此之外,p_align 应该是 2 的整数指数次方,并且 p_vaddr 与 p_offset 在模 p_align 的意义下,应该相等。 */
} Elf64_Phdr;
/* These constants define the permissions on sections in the program
header, p_flags. */
#define PF_R 0x4
#define PF_W 0x2
#define PF_X 0x1
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; /* 从文件开始到该段开头的第一个字节的偏移 */
Elf64_Addr p_vaddr; /* 该段第一个字节在内存中的虚拟地址 */
Elf64_Addr p_paddr; /* 该字段仅用于物理地址寻址相关的系统中 */
/* 由于”System V”忽略了应用程序的物理寻址 */
/* 可执行文件和共享目标文件的该项内容并未被限定 */
Elf64_Xword p_filesz; /* 文件镜像中该段的大小,可能为0 */
Elf64_Xword p_memsz; /* 内存镜像中该段的大小,可能为0 */
Elf64_Xword p_align; /* 可加载的程序的段的 p_vaddr 以及 p_offset 的大小必须是 page 的整数倍。该成员给出了段在文件以及内存中的对齐方式。如果该值为 0 或 1 的话,表示不需要对齐。除此之外,p_align 应该是 2 的整数指数次方,并且 p_vaddr 与 p_offset 在模 p_align 的意义下,应该相等。 */
} Elf64_Phdr;
依然是以 x64 为例,对于执行视图下的 ELF 文件,它的程序头表记录了程序的所有段,每个记录有 8 个属性,对于这 8 种属性的介绍可以看注释。
关于段的类型,下面是一些常见类型的介绍:
此外,还有一些名称为 GNU_STACK、GNU_EH_FRAME、GNU_RELRO 的段。
关于段的内容,由于一个段可以包含多个节区,所以详见下一节。
一个小细节:通常情况下,没有被初始化的数据在段的尾部,因此,p_memsz 才会比 p_filesz 大。
#section header table/* sh_type */
#define SHT_NULL 0 // 非活动的,这种类型的节头中的其它成员取值无意义。
#define SHT_PROGBITS 1 // 包含程序定义的信息,它的格式和含义都由程序来决定。
#define SHT_SYMTAB 2 // 符号表
#define SHT_STRTAB 3 // 字符串表
#define SHT_RELA 4 // 包含显式指定位数的重定位项,目标文件可以有多个重定位节
#define SHT_HASH 5 // 符号哈希表
#define SHT_DYNAMIC 6 // 动态链接的信息
#define SHT_NOTE 7 // 包含以某种方式标记文件的信息
#define SHT_NOBITS 8 // 不占用文件的空间的节区,其它方面和SHT_PROGBITS相似
#define SHT_REL 9 // 包含重定位表项,不过没有显式指定位数
#define SHT_SHLIB 10 // 保留节区,语义未定义
#define SHT_DYNSYM 11 // 完整的符号表,可能包含很多对动态链接不必要的符号。目标文件可以包含一个 SHT_DYNSYM 节区,其中保存动态链接符号的一个最小集合,以节省空间。
#define SHT_NUM 12 // 没找到资料。。。
#define SHT_LOPROC 0x70000000
#define SHT_HIPROC 0x7fffffff
#define SHT_LOUSER 0x80000000
#define SHT_HIUSER 0xffffffff
/* sh_flags */
#define SHF_WRITE 0x1
#define SHF_ALLOC 0x2 // 该部分在进程执行期间占用内存
#define SHF_EXECINSTR 0x4
#define SHF_RELA_LIVEPATCH 0x00100000
#define SHF_RO_AFTER_INIT 0x00200000
#define SHF_MASKPROC 0xf0000000
/* special section indexes */
#define SHN_UNDEF 0
#define SHN_LORESERVE 0xff00
#define SHN_LOPROC 0xff00
#define SHN_HIPROC 0xff1f
#define SHN_LIVEPATCH 0xff20
#define SHN_ABS 0xfff1
#define SHN_COMMON 0xfff2
#define SHN_HIRESERVE 0xffff
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_type */
#define SHT_NULL 0 // 非活动的,这种类型的节头中的其它成员取值无意义。
#define SHT_PROGBITS 1 // 包含程序定义的信息,它的格式和含义都由程序来决定。
#define SHT_SYMTAB 2 // 符号表
#define SHT_STRTAB 3 // 字符串表
#define SHT_RELA 4 // 包含显式指定位数的重定位项,目标文件可以有多个重定位节
#define SHT_HASH 5 // 符号哈希表
#define SHT_DYNAMIC 6 // 动态链接的信息
#define SHT_NOTE 7 // 包含以某种方式标记文件的信息
#define SHT_NOBITS 8 // 不占用文件的空间的节区,其它方面和SHT_PROGBITS相似
#define SHT_REL 9 // 包含重定位表项,不过没有显式指定位数
#define SHT_SHLIB 10 // 保留节区,语义未定义
#define SHT_DYNSYM 11 // 完整的符号表,可能包含很多对动态链接不必要的符号。目标文件可以包含一个 SHT_DYNSYM 节区,其中保存动态链接符号的一个最小集合,以节省空间。
#define SHT_NUM 12 // 没找到资料。。。
#define SHT_LOPROC 0x70000000
#define SHT_HIPROC 0x7fffffff
#define SHT_LOUSER 0x80000000
#define SHT_HIUSER 0xffffffff
/* sh_flags */
#define SHF_WRITE 0x1
#define SHF_ALLOC 0x2 // 该部分在进程执行期间占用内存
#define SHF_EXECINSTR 0x4
#define SHF_RELA_LIVEPATCH 0x00100000
#define SHF_RO_AFTER_INIT 0x00200000
#define SHF_MASKPROC 0xf0000000
/* special section indexes */
#define SHN_UNDEF 0
#define SHN_LORESERVE 0xff00
#define SHN_LOPROC 0xff00
#define SHN_HIPROC 0xff1f
#define SHN_LIVEPATCH 0xff20
#define SHN_ABS 0xfff1
#define SHN_COMMON 0xfff2
#define SHN_HIRESERVE 0xffff
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;
以 64 位 ELF 为例,节头表的每个字段的含义和取值范围以及对应取值的含义都可以看注释。
#Sections and Segements在链接视图下,节区包含目标文件中除了 ELF 头部、程序头部表、节区头部表的所有信息,而加载视图下,段可以细分为多个节区,所以这里把节区(下文简称的节都是指节区)和段放在一起讲。
首先看节,对于一个合法的节,满足以下条件:
注意:
一些比较常见的节的介绍如下:
这里重点说一下符号表、字符串表、重定位表等动态链接是经常使用的部分,一个经典的应用场景就是ret2dlresolve 手法。
#字符串表常见的字符串表有 .dynstr,.shstrtab 和 .strtab,其中:
#符号表符号名是函数名和变量名的统称。
目标文件的符号表保存着定位和重新定位程序的符号定义和引用所需的信息。
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; /* 一个字符串表的索引值,如果为0说明符号没有名称 */
unsigned char st_info; /* 指定符号的类型和绑定的属性 */
unsigned char st_other; /* 定义了符号的可见性 */
Elf64_Half st_shndx; /* 每个符号表条目都与某个部分相关。该成员保存相关的段头表索引。 */
Elf64_Addr st_value; /* 相关符号的地址 */
Elf64_Xword st_size; /* 符号的大小 */
} Elf64_Sym;
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; /* 一个字符串表的索引值,如果为0说明符号没有名称 */
unsigned char st_info; /* 指定符号的类型和绑定的属性 */
unsigned char st_other; /* 定义了符号的可见性 */
Elf64_Half st_shndx; /* 每个符号表条目都与某个部分相关。该成员保存相关的段头表索引。 */
Elf64_Addr st_value; /* 相关符号的地址 */
Elf64_Xword st_size; /* 符号的大小 */
} Elf64_Sym;
注意到,32 位和 64 位下符号表的结构中成员是相同的,只是顺序不同。
常见的符号表包括:.symtab 和 .dynsym,这两个节都是我们常说的“符号表”,只不过前者在所有目标文件上都会存在,除非进行 strip 剥离,而后者只会出现在动态链接的目标文件上,且无法被剥离,这是因为后者在动态链接解析符号的过程中起到了非常关键的作用,如果被剥离程序将无法运行。
另外,.dynsym 保存的符号是 .symtab 所保存的符号的子集,且前者具有 ALLOC 标记后者没有,意味着前者在运行时需要被装载入内存,后者不需要。
两个重要的成员:
st_name 保存着动态符号在 .dynstr 表里的偏移;st_value 保存着动态符号的虚拟地址。#重定位表重定位表包括 .rel 和 .rela,是将符号引用与符号定义连接起来的过程,在动态链接解析函数符号的过程中非常重要。
/* The following are used with relocations */
#define ELF32_R_SYM(x) ((x) >> 8)
#define ELF32_R_TYPE(x) ((x) & 0xff)
#define ELF64_R_SYM(i) ((i) >> 32)
#define ELF64_R_TYPE(i) ((i) & 0xffffffff)
typedef struct elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
typedef struct elf64_rel {
Elf64_Addr r_offset; /* Location at which to apply the action */
Elf64_Xword r_info; /* index and type of relocation */
} Elf64_Rel;
typedef struct elf32_rela {
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;
typedef struct elf64_rela {
Elf64_Addr r_offset; /* Location at which to apply the action */
Elf64_Xword r_info; /* index and type of relocation */
Elf64_Sxword r_addend; /* Constant addend used to compute value */
} Elf64_Rela;
/* The following are used with relocations */
#define ELF32_R_SYM(x) ((x) >> 8)
#define ELF32_R_TYPE(x) ((x) & 0xff)
#define ELF64_R_SYM(i) ((i) >> 32)
#define ELF64_R_TYPE(i) ((i) & 0xffffffff)
typedef struct elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
typedef struct elf64_rel {
Elf64_Addr r_offset; /* Location at which to apply the action */
Elf64_Xword r_info; /* index and type of relocation */
} Elf64_Rel;
typedef struct elf32_rela {
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;
typedef struct elf64_rela {
Elf64_Addr r_offset; /* Location at which to apply the action */
Elf64_Xword r_info; /* index and type of relocation */
Elf64_Sxword r_addend; /* Constant addend used to compute value */
} Elf64_Rela;
要理解重定位表的结构,先要搞明白“重定位”是什么。
在程序从“代码”到“可执行文件”这个过程中,要经历编译器,汇编器和链接器对代码的处理。然而编译器和汇编器通常创建的是一个地址从 0 开始的目标代码,但几乎没有计算机会允许从地址 0 加载某个程序。如果一个程序是由多个子程序组成的,那么所有的子程序也必须要加载到互不重叠的地址上。“重定位”就是为程序不同部分分配加载地址,调整程序中的数据和代码以反映所分配地址的过程。简而言之,程序的重定位是将程序中的各个部分映射到合理的地址上的过程。
还有一种说法是,重定位是将符号引用与符号定义连接起来的过程。在 ELF 文件中,对于每一个需要重定位的 ELF 节都有对应的重定位表,比如说 .text 节如果需要重定位,那么其对应的重定位表为 .rel.text。
例如,当程序调用了一个函数时,相关的调用指令必须把控制传输到适当的目标执行地址。在这个程序倒入某个函数时,.dynstr 就会包含对应函数名称的字符串,.dynsym 中就会包含一个相应的结构(Elf64_Sym),然后在 .rel.dyn 里就会包含一个指向这个符号的重定位表项。
理解了上面的概念,再来看源码,在动态链接的程序中,同时有 .rel(a).dyn 节和 .rel(a).plt 节,这两个节都使用如上源码的结构。
r_offset 是重定位动作需要用到的地址r_info 要进行重定位的符号表索引,以及相应的重定位类型(哪些位需要修改,以及如何计算它们的取值)r_addend 给出一个常量补齐,用来计算将被填充到可重定位字段的数值。r_info 的计算依赖于上面的那两个宏。
一般来说,32 位程序只使用 Elf32_Rel,64 位程序只使用 Elf32_Rela。
重定位类型有很多,这里不再赘述。
#GOT 全局偏移表GOT 表在 ELF 文件中分为两个部分
其相应的值由能够解析 .rel.plt 段中的重定位的动态链接器来填写。
GOT 表的功能是储存外部符号在动态链接之后的绝对地址。
通常来说,地址无关代码不能包含绝对虚拟地址,理由很简单。然而 GOT 表中可以包含“隐藏的绝对虚拟地址”,这使得在不违背位置无关性以及程序代码段兼容的情况下,得到相关符号的绝对地址。
一般来说,GOT 表的内容如下:
link_map 的指针,这个结构只会在动态装载器中使用,包含了进行符号解析需要的当前 ELF 对象的信息。每个 link_map 都是一条双向链表的一个节点,而这个链表保存了所有加载的 ELF 对象的信息。_dl_runtime_resolve 函数的指针。至于通过谁来完成“填写 GOT 表中的绝对地址”这个工作呢?答案是 PLT 表。
#PLT 过程链接表PLT 表是用来实现惰性加载的关键部分。
动态链接器和程序按照如下方式解析过程链接表和全局偏移表的符号引用:
第一次执行时:

第二次执行时:

而 PLT[0] 是什么呢?举个例子:
$ objdump -d -j .plt main
main: 文件格式 elf64-x86-64
Disassembly of section .plt:
0000000000003020 <.plt>:
# PLE[0]
3020: ff 35 72 4d 01 00 push 0x14d72(%rip) # 17d98 <_GLOBAL_OFFSET_TABLE_+0x8>
3026: f2 ff 25 73 4d 01 00 bnd jmp *0x14d73(%rip) # 17da0 <_GLOBAL_OFFSET_TABLE_+0x10>
302d: 0f 1f 00 nopl (%rax)
# PLT[1]
3030: f3 0f 1e fa endbr64
3034: 68 00 00 00 00 push $0x0
3039: f2 e9 e1 ff ff ff bnd jmp 3020 <_init+0x20>
303f: 90 nop
...
$ objdump -d -j .plt main
main: 文件格式 elf64-x86-64
Disassembly of section .plt:
0000000000003020 <.plt>:
# PLE[0]
3020: ff 35 72 4d 01 00 push 0x14d72(%rip) # 17d98 <_GLOBAL_OFFSET_TABLE_+0x8>
3026: f2 ff 25 73 4d 01 00 bnd jmp *0x14d73(%rip) # 17da0 <_GLOBAL_OFFSET_TABLE_+0x10>
302d: 0f 1f 00 nopl (%rax)
# PLT[1]
3030: f3 0f 1e fa endbr64
3034: 68 00 00 00 00 push $0x0
3039: f2 e9 e1 ff ff ff bnd jmp 3020 <_init+0x20>
303f: 90 nop
...
可以看到,PLT[0] 的内容其实就是去将 link_map 压栈,然后调用 _dl_runtime_resolve 函数,这一部分的内容会在 ret2dlresolve 手法总结 那篇博客里详细解释。
#参考