(全网最细)ELF文件详解

ELF文件是什么

ELF文件是一种对象文件格式。ELF文件的全程是(Executeable and Linking Format,可执行可链接格式)。ELF文件格式主要有三种:

  • 可重定向文件。可重定向文件就是可以用于和其他对象文件链接来创建一个可执行或者可分享对象(so)。
  • 可执行文件。可执行文件,简而言之就是可以运行的文件。
  • so。就是分享对象。so主要有两种用途。第一种,链接器可以和其他的可重定向文件或者其他的so链接,产生一个新的对象文件;第二种就是通过动态链接的方式,将so和其他的so或者其他的可执行程序链接形成一个新的程序镜像。

本文主要是参考System V解析文档。

ELF的文件格式

接下来,我们就来看看ELF文件的文件格式:
ELF文件格式
上面的两个图主要是两种不同的视角:链接视角以及执行视角。
从链接视角来看,主要是分成程序头、节、节头表。其中程序头可选。
从执行视角来看,主要分成ELF头、段、以及节头表。其中节头表可选。

ELF头

ELF头位于文件的头,包含了用于描述这个ELF文件的“地图 road map”。
一个节(section)中主要包含了对象文件大部分链接视角的信息,例如有指令、数据、符号表、重定向信息等。

ELF头中主要的内容存放了当前ELF文件的架构信息、ELF魔数等ELF文件的信息。
下面我们来看看ELF头的定义:

typedef struct {
	unsigned char e_ident[EI_NIDENT]; //elf_identification
	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: e_dient可以看到是一个char类型的数组,正如我在结构体中的注释那样,这是一个elf idnetification,一个elf的表示码,这个表示码有什么用?是什么?待会下面会介绍的。
  • e_type: 这个标记着当前ELF文件的识别码。这个识别码主要有7个。分别是下面的几个:
    e_type尽管核心文件内容未指定,但类型 ET_CORE 被保留用于标记文件。从 ET_LOPROC 到 ET_HIPROC(含)的值保留用于特定于处理器的语义。如果指定了含义,处理器补充将对其进行解释。其他值被保留,并将根据需要分配给新的目标文件类型。如果e_type是ET_REL,那就说明这个ELF文件是一个可重定向文件,ET_DYN就是一个可以动态链接的ELF文件,简称so。大家可以结合上面的介绍来理解这一个字段的内容。
  • e_machine: ELF 文件的所属架构,如果安装了gelf的话,可以在gelf.h文件中查看到所有当前linux支持的架构对应的机器代码。一般我们可以不用关注这个点。
  • e_version: e_version其实主要就是两个。EV_NONE=0;EV_CURRENT=1。虽然EV_CURRENT一般给的是1,但是也会按照需要进行修改。EV_CURRENT表示的是当前ELF为难版本,如果这个ELF是一个拓展版本,那么EV_CURRENT可能会大于1。
  • e_entry: 这个表示elf文件的入口。也就是说当系统加载的时候所进入的地址入口。是一个虚拟地址,用于跳转或者程序入口的。如果这个值为0,说明这个elf文件没有关联的入口。
  • e_phoff:从这里开始,有意思的来可。这个值代表的就是程序头表在ELF文件中的偏移。是一个字节计算的值。如果一个elf文件有程序头表,假设这个值为20,说明程序头表在ELF文件中的入口位置是ELF文件的20 byte偏移。通过这个e_phoff就能跳转到程序头表。如果这个值为0,说明当前elf文件没有程序头表。
  • e_shoff: 那么我们看看这个,其实跟上一个类似。只不过这里是section的罢了。
  • e_flags:这个flag是跟处理器相关的。一般的格式EF_machine_flag。了解一下即可
  • e_ehsize:这个值描述了ELF文件头的大小。以字节为单位
  • e_phentsize:这个值描述的是程序头表中一个入口的大小,以字节为单位,一个ELF文件中所有的程序头表入口大小都是一样的。
  • e_phnum:这个值描述了当前ELF文件中有多少个程序头表入口。那么,有了这些信息,你知道一个可执行文件的程序头表有多大吗?那就是e_phentsize*e_phnum的大小啦。
  • e_shentsize:跟前面的类似,这个记录的就是在链接视角下,一个节头表中的入口信息的大小。也是以字节为单位的。
  • e_shnum:这个就是描述当前节头表中有多少个节入口。这个节头表的大小就是e_shentsize*e_shnum。
  • e_shstrndx:这个值记录的就是节名称字符串表关联的条目的节头表索引。说人话就是这个值代表的是一个索引,这个index指向elf文件中的一个section,这个section就是字符串表,里面存放了section name。
程序头表(program header table)

这个程序头表是可选的,也就是说一个elf文件中不一定会有这个程序头表。如果一个elf文件中提供了这个程序头表的话,这个程序头表就告诉了我们的系统如何去创建一个进程镜像。一个可执行文件程序必须要有程序头表。

节头表(section header table)

节头表中包含了用于描述这个ELF文件的节信息。在ELF文件中每一个section在节头表中都会有一个入口。每一个节的入口都提供了这个节的节名、大小等等的信息。用于链接的ELF文件必须要有节头表,而其他的对象文件呢,看自己的情况可有可无吧。

  • 总结:一个ELF文件中除了ELF头以外,就有程序头表以及节头表的描述信息。这个ELF文件是否有节头表和程序头表依赖于这个对象文件的作用。如果是用于链接的,那就是需要有节头表(链接主要是用于给其他object file提供信息);用于运行的,那就必须要有程序头表。
  • 上图是这样画的,但是实际情况下,他们的顺序并不是一定的。section实际在elf中的顺序应该是乱的,但是是通过section header table中的信息来进行修正的。所以,从链接视角来看,section header table是多么的重要呢。
ELF Identification

在前面,我们看到了ELF的结构体定义的第一个成员就是一个数组,是一个elf idnetification的解释,这里我们就来解释一下这个elf idnetification具体是什么。
elf identification
看到上面的表格了吗,一开始,EI_MAG0到EI_MAG3存放的是ELF魔数,这个从我的角度来看其实实际的意义不大,属于是历史遗留产物。EI_MAG0是0x7f的占位符。
剩下的主要是一个ELF文件的类型啊,填充字段的解释等等。这部分大家有空稍微了解一下就行了。

Section

前面介绍了ELF头,接下来就开始介绍ELF文件中的主体部分,Section(节)。在这里,我会称Section为节,因为从链接视角来看,会分的细一点。多个section组成一个段(Segment)。之前看了点书,section成为段,这个一度给我造成了不少的混淆。所以我在这里还是更希望把section称为节,segment称为段。毕竟,我们程序运行当中会经常遇到段错误(Segment Fault),而不是节错误,是吧。

节头表

我们知道ELF文件中如果是可链接文件,必须要有一个节头表的。因为链接往往是对跟某个section进行链接,这个时候我们只需要拿到节头表,就能知道这个ELF文件中任意一个section在ELF文件中的位置。
在ELF头中,有个成员变量e_shoff,这个成员变量告诉我们这个节头表是从ELF的开头到ELF节头表的以字节为单位的偏移量。通过这个e_shoff,我们只需要从ELF的开头偏移对应个字节就可以到达ELF的节头表了。
在节头表中,有一些非常特殊的索引,这些索引是保留下来的,不会分配给具体的section使用,具体有哪些索引呢?
特殊ELF节头表索引

  • SHN_UNDEF:这个索引为0,这个表示未定义,或者没有意义的section引用。举个例子,如果一个符号所关联的section是SHN_UNDEF的话,说明这是一个未定义的符号。
注意:节头表是包含了index 0的。具体的使用上是有差别的。如果使用gelf库的话,我们会采用gelf_nexscn()函数获取下一个section,但是在第一次调用的时候,返回的section就是index为0的section。
  • SHN_LORESERVE 该值指定了保留索引范围的下限。
  • SHN_LOPROC到SHN_HIPROC之间 这个区间的可以理解为为处理器预留的范围
  • SHN_ABS 这个值代表了关联的引用是一个绝对值。什么意思呢?打个比方,一个符号引用的section的索引是SHN_ABS,说明这个符号是一个绝对值,并不会收到重定向的影响。
  • SHN_COMMON 表明关联的符号是通用符号
  • SHN_HIRESERVE SHN_LORESERVE到SHN_HIRESERVE之间的索引是保留索引。节头表里面是不会包含这个范围之内的条目的。

讲到这里,需要说一下section的四个特性:

  1. ELF文件中每一个section有且只有一个节头(section header)来描述,一般看到elf中shdr说的就是section header。
  2. 每一个section都是占据一段连续的区间。
  3. 文件中的节不得重叠。文件中的字节不得位于多个节中。
  4. 目标文件可能具有非活动空间。各种标头和节可能无法“覆盖”目标文件中的每个字节。非活动数据的内容未指定。这块内容我们可以不用过于执着。

节头

下面我们来看看section header:

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

前面我们知道了,每一个section都会有一个section header来描述的。而ELF中的所有的section header都在一个section header table中描述。他们之间可能并没有顺序,但是所有的section都在section header table中了,我们只需要遍历section header table中的每一个entry即可遍历到每一个section的信息了。这里有一个小妙招,在elf中,看到了sh就可以联想是Section Header(SH)了。下面来逐个将一下每一个成员的作用:

  • sh_name: sh_name是一个索引值。这个索引是指向节头字符串表(section header string table)。这里section header string table是一个字符串表,里面存放的就是各个索引的名字组成的字符串,这个sh_name指的就是这个当前的这个section的名字在这个字符串表中的位置。
  • sh_type: 这个成员存储了这个section对应的类型
    sh_type主要有以下几个值,以SHT_开头(SHT_NULL):
    1. NULL: 0 ;这个值标记的当前的这个section header是不可用的。这个节头对应的那个section是不存在的(或者这么说,这个节头没有对应的section)。一旦这个节头的sh_type是SHT_NULL的话,那么这个section header的其他成员都是未定义的值。
    2. PROGBITS: 1;该部分保存着程序定义的信息,其格式和含义完全由程序决定。
    3. SYMTAB: 2;这个表示这个节头对应的那个section是一个符号表。因为这个section里面提供了一些用于链接的符号信息。对于一个完整的符号表而言,它可能还会包含许多动态链接过程不需要用上的符号信息。
    4. STRTAB: 3;这个代表着对应的section是字符串表。一个目标文件里面可能有多个字符串表的section。
    5. RELA:4;这个代表这个section里面有重定向入口。这些重定向是带有addend的(什么是addend?那就是进行重定向的时候加在后面的常数偏移),当然,一个目标文件里面会有多个RELAsection。
    6. HASH:5;这个代表这section是一个符号哈希表。所有参与动态链接的对象都必须包含一个符号哈希表。具体的哈希表后面会讲到。目前而言,一个目标文件中只有有一个哈希表。
    7. DYNAMIC:6;这个说明了这个section是用于动态链接的。动态链接放后面去讲。
    8. NOTE:7;这个简单,就是写了一些关于这个文件的说明。
    9. NOBITS:8;这是一个很神奇的节,这个节并不占用空间。其余的功能类似于PROGBITS,虽然这个节不占空间,sh_offset成员也是包含了一个偏移量的,虽然只是意思上的。
    10. REL:9;这个跟RELA节是类似的,只不过这个section中的重定向入口是不带有常数偏移(addend)的。
    11. SHLIB:10;此节类型是保留的,但具有未指定的语义。包含此类型节的程序不符合ABI。在一般的ELF文件中我感觉比较少碰到,碰到了一般也不用管。
    12. DYNSYM:11;见SYMTAB,只不过这里包含的是需要用于动态链接的最少量的符号,用于优化空间的。
    13. LOPROC/ HIPROC:这里的官方解释是这两个值构成了一个范围,这个范围内的预留给处理器。如果指定了含义,处理器补充将对其进行解释。其实从名字上也能看出点端倪,PROCessor。
    14. LOUSER/HIUSER: 这个范围表示的是保留给应用程序的。应用程序可以使用 SHT_LOUSER 和 SHT_HIUSER 之间的节类型,而不会与当前或将来的系统定义节类型冲突。
  • sh_flags: 这个是当前section的标记位。section有四种flag:SHF_WRITE:表示当前的这个section中的数据在程序执行过程中是可写的;SHF_ALLOC:这个标记说明了当前的section在进程执行期间占用内存,但一些控制部分不驻留在目标文件的内存映像中;SHF_EXECINSTR:代表当前的section包含了可执行的机器指令;SHF_MASKPROC:一些处理器掩码相关的。
  • sh_addr: 如果当前的section会出现在内存映像中的话,这个addr给出的就是这个section会驻留的第一个字节的地址。
  • sh_offset: 这个是一个偏移量。这个偏移量是从ELF文件的开始到当前这个section的第一个字节的偏移量。打个比方,如果sh_offset=100,说明了从ELF文件的开头偏移100个字节就到了这个section开始的第一个字节。用于section开头定位。
  • sh_size: 表示这个section的大小。如果一个section的类型是SHT_NOBITS的话,这个值不一定等于0,但是实际上这个section是不占用空间的。
  • sh_link: 该成员保存一个节头表索引链接,其解释取决于节类型。
  • sh_info: 里面包含了一些额外的信息。
  • sh_addralign: 在ELF文件中,某些节可能存在地址对齐的约束。举个例子,如果一个section是双字节对齐,那么这一整个section都应该是双字节对齐的。也就是说,sh_addr 的值必须与 0 一致,以 sh_addralign 的值作为模。
  • sh_entsize: 有些节包含固定大小条目的表,例如符号表。对于这样的节,此成员给出每个条目的大小(以字节为单位)。如果节不包含固定大小条目的表,则该成员包含 0。

特殊节

特殊节,special section,这些节是系统使用的,有特殊的含义的类型的。如下表所示:

在这里插入图片描述
既然本文想要全面介绍ELF文件,那么这些特殊节的作用肯定也是要说明的,接下来我们就一个个看看这些special section到底是干什么用的。
实际上,我们看到的section前缀是.的,“一般”可以认为是系统保留section,也就是我们的sepcial_section。

  1. .bss: 这个section是NOBITS类型的section。实际上这个section里面存放的是尚未初始化的数据。当程序开始运行的时候,才会使用0来初始化这些数据。因此,在尚未运行的时候,ELF文件中的这块内容是没有数据的,所以是NOBITS类型的section。
  2. .comment:这里包含的是版本控制信息,不会影响实际运行内容
  3. .data & .data1: 这种section中存放的就是已经初始化的一些数据。
  4. .debug: 这种section带有符号调试信息。一般会有.debug为前缀的话,都是供给调试信息使用的
  5. .dynamic: 这种类型的section一般带有动态链接信息的。一般会带上SHF_ALLOC类型的flag标志位。这块信息的作用会留存在内存映像当中。
  6. .dynstr: 这个section代表着这里面的字符串是需要被动态链接的。最常见的是代表与符号表条目相关联的名称的字符串。
  7. .dynsym: 一个持有动态链接符号表的section
  8. .fini: finish,这个说明了这个section持有的是程序结束过程中需要执行的可执行指令。也就是说,当这个程序正常退出的时候,系统就会执行这个section中的机器指令。
  9. .got: 这个section持有全局偏移表(global offset table)。
  10. .hash :一个符号哈希表节。符号哈希,正如我们前面介绍的那样,一个可以动态链接的section必须会有一个hash table的。
  11. .init:用于程序初始化过程中执行的可执行代码。一般情况下,在代码中通过__init修饰的代码在通过gcc编译以后产生的.o文件里面应该就会被放在这个section里面。这个section只有在程序加载初始化的过程中会被调用,因此,这里面的代码是不支持热补丁的。
  12. interp: 这个section里面保存了程序解析器的路径。如果一个文件的可加载段里面有这个section的话,那么这个section的标记为就会被设置为SHF_ALLOC,因为可加载段里面会加载到内存当中执行。所以留存在内存当中,设置为SHF_ALLOC。
  13. .line: 此部分保存符号调试的行号信息,描述了源程序和机器码之间的对应关系。内容是未指定的。
  14. .note: 代表这是一个note节
  15. .plt: 此节包含过程链接表
  16. .rela{name} & .rel{name}:有rel的,说明这是一个重定向section,里面存放的重定向的条目。rela代表有addend的重定向条目的section。正如前面所说的,如果这个section在可加载段里面的,这个section就是带有SHF_ALLOC的标志位。一般来说,后面带上的名字就是这个用于重定向的section。举个例子,.text节的重定向节就是.rela.text或者.rel.text
  17. .rodata & .rodata1:这部分的section中包含的数据是只读数据。一般是在程序镜像中的不可写的段中。
  18. .shstrtab:这个是section header string table的节。这个section存放的就是section名字的节,前面说的sh_name就是这个section中的索引,这个section中的sh_name索引就是那个section header对应的那个section的名字。
  19. .strtab: 这是一个字符串表,最常见的是代表与符号表条目相关联的名称的字符串。如果文件具有包含符号字符串表的可加载段,则该部分的属性将包含 SHF_ALLOC 位;否则,该位将关闭。
  20. .symtab:这个是一个符号表,遍历这个section,我们可以得到elf文件中所有的symbol
  21. .text:主要是代码节,包含程序的可执行指令。

上面就是针对基本能够遇到的特殊节的意义解析。一般来说,前缀是.的一般是系统预留的section名字。一般的应用程序使用的是没有这个前缀的section,这样可以避免跟系统预留的section出现冲突。

字符串表(String Table)

在ELF文件中,什么是字符串表呢?字符串表节里面存放了很多的字符串,他们用于指示对应的section或者symbol的名字。我们看一下字符串表的示例:
在这里插入图片描述
我们从上面的这个图可以看到,字符串表的开头是\0,每一个字符串之间通过一个\0来分割。确保每一个字符串都是有明确的结尾的。
此时,节头表中的sh_name的作用就是这个表中的索引(index)。index=0的时候,字符串是空的;当index=7的时候,这个字符串就是Variable。也就是说,从index开始,一直读取直到遇到\0。

符号表

ELF文件中有一个表叫做符号表。符号表里面包含需要用于定位或者重定向的符号定义以及引用的信息。符号表其实是一个数组,而对应的符号的索引就是这个符号表数组的下标。下面我们来看看ELF文件中一个符号的定义:

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

这个st_name就是在String table这个数组的下标。如果这个st_name=0,说明这个symbol没有名字。

  1. st_value: 这个代表关联的那个符号的值。这个说法有点奇怪,这个值是什么主要依赖于这个符号的上下文。一般来说,在我遇到的应用范围里面,这个值主要是一个地址。
  2. st_size:这个代表这个符号的大小。打个比方,一个数据对象的大小就是这个对象所包含的字节数。
  3. st_info: 这个里面记录着这个symbol的信息。包括符号的类型以及绑定属性。符号的绑定属性主要有下面5种:
    在这里插入图片描述
  • STB_LOCAL:表示这个符号是一个局部符号。也就是我们的.c语言里面的static 声明的符号。这个是这个文件以外不可见的符号。在不同的文件之间可能会有多个重名的符号,这些重名的局部符号彼此之间不会相互影响。
  • STB_GLOBAL:全局符号。这个符号是一个全局符号,意思是所有链接在一起的对象文件都可以看到这个符号。一般来说EXPORT_SYMBOL以及extern的符号就属于这种类型的符号。
  • STB_WEAK:弱引用符号。在C语言中,有弱引用类型__weak__,编译后的符号就会在这里面。弱符号类似于全局符号,但其定义的优先级较低。
  • STB_LOPROC/STB_HIPROC:这个也是处理器预留的符号类型,由处理器解析其具体的语义。
  1. st_other: 这个值,没有意义。
  2. st_shndx: 每一个符号表的条目入口都跟某个相关的节关联。因此,这个值存储的是节头表的索引。具体是什么意思呢?那就是假如现在有一个.c文件,我定义了一个functionA,然后用-ffsection编译,这个函数会被单独编译成一个section,与此同时,符号表中也有一个符号,symbol[st_name] == functionA,通过符号functionA的st_shndx在街头表中可以找到一个对应的section,这个section就是function A的section。

一般来说,对于一个符号表而言,所有的具有STB_LOCAL绑定属性的符号一般会在弱引用符号或者全局符号之前。就我看来,一般STB_LOCAL是本地可见的符号,一般是static 修饰的函数符号。
上面介绍的STB_*可以认为是symbol table binging type(我自己理解的,没有官方认证的哈)。是一个符号的绑定类型。接下来要介绍的,是符号的类型,也就是符号本身的类型,STT
符号类型

  • STT_NOTYPE: 说明则个符号的类型是没有指定的
  • STT_OBJECT:说明了这个符号的类型是一个object的
  • STT_FUNC:说明这个符号类型是函数的
  • STT_SECTION:说明这个符号是节的。需要注意的是,一般在符号表中是这个类型的符号,它的绑定类型一般是STB_LOCAL的本地绑定符号类型。
  • STT_FILE:这是一个文件类型的符号。对于一个文件类型的符号而言,它的绑定类型一般是STB_LOCAL的本地符号绑定类型。一般指这个符号name是一个文件名来的,其节索引是SHN_ABS类型。这里有一个小知识点,就是如果我们在遍历符号表的时候发现了STT_FILE类型的符号,我们其实可以认为接下来的其他符号,在遇到其他STT_FILE符号之前的符号,都是属于在这个STT_FILE文件下的符号。也就是说,STT_FILE为界限,然后后面跟着的就是这个文件下的符号。
  • 剩下属于处理器语义保留,不做介绍。

具体是什么意思呢?举个例子,在我们遍历elf文件中的符号表的时候,如果我们获得一个符号的的类型是STT_FUNC,说明这个符号指向的对象实际上是一个函数。如果该elf文件在编译的过程中使用的–ffsection的话,那么这个symbol还会指向一个section,就是这个函数的section。

STT_FUNC还有另外的一个影响。针对so文件,如果其他的object文件引用so中的符号属于是STT_FUNC的话,连接器或为这个被引用的符号自动创建一个过程链接表的入口。但是其他类型的符号即使被引用了,也不会有这个过程链接表的。

如果这个符号引用了节中的位置,那么符号结构体中的st_shndx就派上用场了,这个时候st_shndx记录的就是引用的节在节头表(section header table)中的索引。当然,如果section被重定向了,symbol也是会跟着变得,随着section的重定向而重新计算其值。
此外,st_shndx有的时候会指向一个特殊的值的索引,这些section是有特殊的意义的。

  • SHN_ABS:这个代表了当前的这个symbol的值是一个绝对值,它的地址不会因为重定向而发生变化
  • SHN_COMMON:这个代表的当前的这个symbol有一个公用空间,只不过这个公用空间可能还没有分配。也就是说,链接编辑器将在 st_value 的倍数的地址上为符号分配存储空间。符号的大小表示需要多少字节。
  • SHN_UNDEF:代表这个符号没有定义。但是只是表示当前这个符号在当前符号表中没有定义。在ld过程跟其他的object file进行连接的时候,可能这个符号的定义在其他的对象文件上,此时连接器就会将该符号的引用直接连接到实际的位置上去了。

符号值

符号值,就是st_value成员。这个成员的语义有以下三种:

  1. 在一个重定向文件中,st_value 保存部分索引为 SHN_COMMON 的符号的对齐约束。
  2. 在一个可重定向文件中,st_value保存的是偏移量。也就是说,st_shndx保存了对应的section的index,通过st_shndx定位到需要引用的那个section的头地址,然后加上st_value的偏移量,就是这个symbol在这个section引用的具体位置了。
  3. 在可执行文件或者是程序文件中,st_value表示一个虚拟地址,为了使这些文件的符号对于动态链接器更有用,节偏移量(文件解释)让位于与节号无关的虚拟地址(内存解释)。

重定向

重定向(Relocation)是什么

首先,我们假设有一个文件a.c,里面用到的swap函数是定义在b.c里面的。我们首先编译了的是a.c,得到了a.o,那么对于编译器而言,a.c中调用的指令 callq swap对应的swap的地址这个时候是不知道的,既然不知道,编译器就会搞一个假地址放在这里(既然我现在不知道,那就后面知道再补上去咯)。
那么在连接阶段,把a.o与b.o进行连接的时候,连接器知道了swap的定义的地址了,那么对于a.o而言,要执行的话,是不是需要把a.o中的假地址给修正成为一个可以调用的地址啊?那么修正a.o中的假地址过程我们就叫做重定向了。

重定向结构体以及成员解析

重定向结构体定义:

typedef struct {
	Elf32_Word r_info; 
} Elf32_Rel;

typedef struct {
	Elf32_Addr r_offset;
	Elf32_Word r_info;
	Elf32_Sword r_addend; 
} Elf32_Rela;

这里需要注意的是Rel是没有addend的重定向,而Rela是有addend的重定向。addend是什么?addend就是重定向过程中需要加上的常数偏移。也就是说,在进行地址替换的过程中,如果需要加上固定的偏移才能到对应的地址的话,这个固定偏移就是放在addend中了。

成员解析

下面来解析一下重定向结构体的成员:

  • r_offset:这个值给出了需要重定向的地址。对于一个可重定向文件,该值是从节开头到受重定位影响的存储单元的字节偏移量。说人话就是这个值是从对应的section的开头到需要重定位的字节偏移量。举个例子,如果r_offset为8,也就是说从对应的section的开始,+8字节偏移的地址需要重定向。对于可执行文件或共享对象,该值是受重定位影响的存储单元的虚拟地址
  • r_info:这个值给出了必须进行重定位的符号表索引以及要应用的重定位类型。打个比方,一个跳转指令的重定向入口持有需要调用的那个函数的符号表索引。如果这个索引值是SHN_UNDEF的话,说明这个符号没有定义,而且重定向的时候会使用0作为symbol value来用。重定向的类型是由处理器指定的。
  • r_addend:这个上面说了,这里不说了。

总结

下面,我就用一幅图来总结一下这个文章主要介绍的内容:
elf文件
如果我们需要遍历当前ELF文件中所有的section的话,我们只需要遍历elf文件的section header table即可。
通过section header table中的每一个节点,都能定位到对应的section,都能获得该section的全局信息。

而在一个elf文件中,可能需要与别人链接,有许多引用外面的符号,此时我们就可以遍历符号表。符号表中的每一个节点都是一个符号,无论是section的name还是symbol的name,我们都是可以通过对应节点中的index在string table中找对对应的索引来获取对应的字符串,从而获得这个symbol或者section的字符表达。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值