# ELF原理二

字符串表节信息(String Table)

字符串表节保存以空白符结尾的字符序列,也被称为字符串。目标对象文件使用这些字符串来表示符号和节名,那么如何使用这些字符串呢?只需要让使用这些字符串的符号保存一个指向字符串表的索引下标即可。在字符串表节中,第一个字节,也就是索引下标为0处,被定义为一个空白符。同样,字符串表的最后一个字节也被定义为保存一个空白符,确保所有字符串以空白符结束。对于空字符串表,其他非零索引下标访问无效。

一个节头部的sh_name变量保存着字符串表的索引下标。下图显示了一个包含25个字节的字符串表,以及与各种索引下标关联的字符串。

img

img

如图所示,每个节中可以保存字符串表的索引下标,并且一个字符串可以引用多次,也可能存在对子字符串的引用,也允许引用空白字符串。这就达到了一个字符串表的字符串公用。

符号表节信息(Symbol Table)

一个目标对象文件的符号表保存了需要进行重定位程序的符号定义。引用符号表时,只需要保存将指向下面描述的 Elf32_Sym 结构数组下标。索引下标为0表示表中的第一个表项,并用作未定义的符号索引。

img

  1. st_name:该值用于保存目标文件的字符串表(也即String Table 表)的索引下标
  2. st_value:该值用于保存关联符号的值。根据上下文的不同,这可以是绝对值、地址等
  3. st_size:该值用于保存符号的大小。例如,数据对象的大小是该对象中所包含的字节数。如果符号没有大小或大小未知,则该值为0
  4. st_info:该值用于保存符号的类型和绑定属性。下面列出了这些值的类型:img

​ 类型为符号的绑定(ELF32_ST_BIND)决定了链接的可见性和行为img

​ STB_LOCAL:表示局部符号,在其定义的目标文件之外是不可见的。相同名称的局部符号可以存在于多个文件中,而不会相互干扰

​ STB_GLOBAL:表示全局符号,该符号对于所有合并的目标文件都是可见的。需要全局唯一。

​ STB_WEAK:表示弱符号,弱符号类似全局符号,但它们的定义优先级较低。也即可以出现相同名的全局符号和弱符号,但是强符号优先级高于弱符号

​ STB_LOPROC 到 STB_HIPROC:这个范围内的值为特定于处理器的语义保留

​ 全局符号和弱符号的区别主要体现在:当链接器组合几个可重定位的对象文件时,它不允许STB_GLOBAL符号具有相同名称的多个定义。但是,如果已定义的全局符号存在,则出现同名的弱符号不会导致错误,此时将忽略弱符号。类似地,如果存在公共符号(例如,一个st_shndx字段包含SHN_COMMON的符号),相同名称的弱符号的出现也不会导致错误。

​ 符号的类型提供了关联实体的一般分类:

img

​ STT_NOTYPE:表示符号的类型未指定

​ STT_OBJECT:表示符号与实际数据对象相关联,如变量、数组等

​ STT_FUNC:表示该符号与一个函数或其他可执行代码相关联

​ STT_SECTION:表示该符号与一个节相关联。这种类型的符号表项主要用于重定位

​ STT_FILE:表示符号与目标文件相关联的源文件名称

​ STT_LOPROC 到 STT_HIPROC:这个范围内的值为特定于处理器的语义保留(作者:黄俊,微信:bx_java) 共享目标文件中的函数符号(也即STT_FUNC类型的函数符号)具有特殊的意义。当另一个对象文件从共享对象引用函数时,链接器会自动为所引用的符号创建一个过程链接表(PLT)表项。引用其他类型(如可重定向文件)的对象文件的STT_FUNC符号不需要通过过程链接表来引用

​ 5.st_other:该变量目前保存0,没有实际意义

​ 6.st_shndx:该值用于保存着与之相关连的节头部表的索引下标

符号值信息(Symbol Values)

不同对象文件类型的符号表项对st_value成员的解释略有不同:

  1. 在可重定位文件中,st_value保存一个符号的对齐约束
  2. 在可重定位文件中,st_value保存一个已定义符号的节偏移量,也即 st_value 是从st_shndx标识的部分开始的偏移量
  3. 在可执行和共享目标文件中,st_value保存一个用于内存排布的虚拟地址

虽然符号表的值对于不同的目标文件有相似的含义,但这些数据由程序控制进行更加有效地访问。

重定位(Relocation)

重定位是将符号引用与符号定义连接起来的过程。例如,当一个程序调用一个函数时,相关的调用指令(如:call sum)必须在执行时将调用函数符号分配适当的目标地址(如:call 0x600010)。换句话说,可重定位文件必须具有描述如何修改节内容的信息,从而允许可执行和共享目标文件保存进程装载到内存中执行的正确信息。重定位表项就描述了这些数据。如下图所示,可重定位表由 ELF32_REL 结构和 ELF32_RELA 数组表示,可以看到他们的唯一区别就是多了一个r_addend 变量。

img

  1. r_offset:该值用于表示程序需要重定位操作的位置。对于可重定位的文件,该值为从节开头到需要重定位的存储单元的字节偏移量。对于可执行文件或共享对象,该值为需要重定位的存储单元的虚拟地址
  2. r_info:该值用于表示必须进行重定位的符号表索引,以及要应用的重定位类型。例如,调用指令的重定位表项将保存被调用函数的符号表索引。如果索引是STN_UNDEF,即未定义的符号索引,则重定位使用0作为其符号值。重定位类型是特定于处理器的(如Intel:R_X86_64_PC32(PC 相对地址)、R_X86_64_32(绝对地址))。
  3. r_addend:该值用于表示指定一个常量加数,用于计算存储在可重定位字段中的值(比如:call sum,此时 sum 地址为 0x4004e8,而r_addend为-4,那么最终地址为:call 0x4004e8 - 4 ,相关计算与r_info的重定位类型有关,这里仅仅举个例子该值用于参与实际地址的计算,详细的描述可以参考下 CSAPP 的程序链接一章)

如上所示,只有Elf32_Rela条目包含显式加数。根据处理器体系结构的不同,使用这种rela的方式来计算地址是相对方便且必要的。因此,特定机器的实现可以专门使用一种专用形式,也可以根据上下文使用任意一种形式来进行重定位。

一个重定位节引用另外两个节:符号表、要修改的节。节头的sh_info和sh_link成员,在上面的节信息(Section)中描述,指定了这些关系。注意:不同目标文件的重定位表项对r_offset成员的解释略有不同:

  1. 在可重定位文件中,r_offset保存着一个节偏移量。也就是说,重定位节本身描述了如何修改文件中的另一个节
  2. 在可执行和共享目标文件中,r_offset保存一个虚拟地址,用于内存排布表示

虽然r_offset的解释会根据不同的目标文件而改变,以允许相关程序有效地访问,但重定位类型的含义将保持不变。

重定位类型(Relocation Types)

重定位表项描述了如何改变下面的指令和数据字段(位数字出现在下面的左右两个角)。

img

word32指定了一个32位字段,该字段使用任意字节对齐方式并占用4个字节。这些值与32位Intel体系结构中的其他值使用相同的字节序。如下图所示,使用小端序来排列。

img

下面的计算假设操作正在将可重定位文件转换为可执行文件或共享对象文件(考虑下混沌学堂说的:小世界组成大世界)。链接器将合并一个或多个可重定位的文件并将它们组合成一个新的目标文件。它首先决定如何组合和定位输入文件,然后更新符号值,最后执行重定位。应用于可执行或共享目标文件的重定位是类似的,具有相同的实现。下面的描述使用以下符号来描述对应的意义:

  1. A:表示加数,用于计算可重定位字段的值
  2. B:表示在执行期间将共享对象加载到内存中的基址。通常,共享对象文件使用虚拟地址0作为基址构建,但执行地址会有所不同
  3. G:表示在执行期间重定位表项的符号所在的全局偏移表的偏移量
  4. GOT:表示GOT表的首地址
  5. L :表示过程链接表项中符号的位置(节偏移量或地址)。过程链接表项将函数调用重定向到正确的地址。静态链接器负责构建过程链接表,动态链接器在执行期间修改GOT.PLT表项(后面还会详细描述)
  6. P:表示重新定位后的存储单元位置(相对偏移量或绝对地址,使用r_offset计算得出)(作者:黄俊,微信:bx_java)
  7. S:表示在重定位表项中对应下标处的符号值

重定位表项的 r_offset 值指定存储单元首字节的偏移量或虚拟地址。重定位类型指定要更改哪些位以及如何计算它们的值。SYSTEM V架构只使用Elf32_Rel重定位表项。在所有情况下,加数和计算结果使用相同的字节顺序。重定位类型包含以下值:

img

  1. R_386_GOT32:该重定位类型用于计算从全局偏移表的基地址到全局偏移表符号地址的距离。此外,它还指示链接器构建一个全局偏移量表(GOT)
  2. R_386_PLT32:该重定位类型用于计算过程链接表项的地址,并指示链接器构建一个过程链接表(PLT)。
  3. R_386_COPY:该重定位类型用于链接器为动态链接创建这种重定位类型。它的offset变量指向可写段中的一个位置。符号表索引指定了一个应该同时存在于当前对象文件和共享对象文件中的符号。在执行过程中,动态链接器将与共享对象符号相关的数据复制到由偏移量指定的位置
  4. R_386_GLOB_DAT:该重定位类型用于将全局偏移表项设置为指定符号的地址
  5. R_3862_JMP_SLOT:链接器在需要动态链接时创建这种重定位类型。它的offset变量给出了过程链接表项(也即GOT.PLT 后面会讲)的位置。动态链接器根据该偏移量修改过程链接表项,将控制转移到指定符号的真实地址。
  6. R_386_RELATIVE:链接器为动态链接创建这种重定位类型。它的offset变量给出了共享对象的位置,该位置使用相对地址来表示。动态链接器通过将加载共享对象时的虚拟地址与该相对地址相加来计算相应的虚拟地址
  7. R_386_GOTOFF:该重定位类型用于计算符号值与全局偏移表地址之间的差值。它还指示链接器构建全局偏移量表。
  8. R_386_GOTPC:该重定位类型类似于R_386_PC32,只是它在计算中使用了全局偏移表的地址。在重定位中引用的符号通常是GLOBAL_OFFSET_TABLE,它也指示链接器构建全局偏移表