ELF文件的段

前言

本篇文章接着上一篇ELF文件头和段表继续讲解
本篇文章讲解ELF文件中的不同类型段

示例程序

int printf(const char* format,...);
int global_init_var = 84;
int global_uinit_var;

void func1(int i)
{
	printf("%d\n",i);
}

int main(void)
{
	static int static_var = 85;
	static int static_var1;
	int a = 1;
	int b;
	func1(static_var + static_var1 + a + b);
	return a;
}

字符串表

ELF文件中用到了很多字符串,比如段名、变量名等。因为字符串的长度往往是不定的, 所以用固定的结构来表示它比较困难。一种很常见的做法是把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串

通过这种方法,在ELF中使用字符串只需要标记偏移位置即可。
一般,我们常见的字符串表有两个:.shstrtab.strtab

.shstrtab

段表字符串表,该表保存段表名称的字符串,比如我们打印出上面程序的.shstrtab表的内容如下,空格我们用换行代替了,比较好观察:


.symtab
.strtab
.shstrtab
.rela.text
.data
.bss
.rodata
.comment
.note.GNU-stack
.rela.eh_frame

有一点需要注意:比如段表.text和.rela.text,字符串表之需要保存.rela.text就够了,因为包含.text,这样可以节省空间。

.strtab

这个字符串表就是保存程序中所有使用到的字符串了。包括

  • 函数名,经过编译器的处理
  • 变量名
  • 文件名
    下面列出上面代码的.strtab表数据,空格我们用换行代替了,比较好观察:

SampleSection.c
static_var.1731
static_var1.1732
global_init_var
global_uinit_var
func1
printf
main

符号表

首先理解一下什么是符号,符号是在链接过程中使用的术语,简单一点说,就是变量和函数的名称,只不过可能经过编译器的处理,是为了使名字唯一。

符号表保存了关于所有符号的信息,符号表的每一项也是一个固定的结构,表示每个符号的信息,下面对于这个结构进行说明

// 这是结构体代码
typedef struct
{
  uint32_t	st_name;
  unsigned char	st_info;
  unsigned char st_other;
  uint16_t	st_shndx;
  uint64_t	st_value;
  uint64_t	st_size;
} Elf64_Sym;

st_name

符号的名称,也是一个偏移值,可以去字符串表中进行匹配

st_info

这个字段代表符号类型和绑定字段,他表示两种属性:

  • 低4位表示符号类型
  • 高4位表示绑定信息

符号类型

先看符号类型,下面列出常用的符号类型

  • NOTYPE
    值为0,未知符号类型

  • OBJECT
    值为1,该符号是个数据对象,比如数组,变量

  • FUNC
    值为2,该符号是个函数

  • SECTION
    值为3,该符号是段表,这种符号的绑定信息必须是LOCAL的

  • FILE
    值为4,该符号表示文件名,这种符号的绑定信息必须是LOCAL的,并且该符号的st_shndx一定是ABS。

  • COMMON
    值为5,该符号是一个Common数据对象

  • TLS
    值为6,该符号是一个线程私有数据对象

  • NUM
    值为6,该符号表示定义类型的数量

  • LOOS
    值为10,该符号表示操作系统特定操作的起始位置

  • GNU_IFUNC
    值为10,该符号表示一个间接代码对象

  • HIOS
    值为12,该符号表示操作系统特定操作的结束位置

  • LOPROC
    值为13,该符号表示处理器特定操作的起始位置

  • HIPROC
    值为15,该符号表示处理器特定操作的结束位置

绑定信息

下面是绑定信息的值的列表

  • LOCAL
    值为0,局部符号,对于目标文件的外部不可见

  • GLOBAL
    值为1,全局符号,外部可见

  • WEAK
    值为2,弱引用符号

  • NUM
    值为3,定义类型的数量

  • LOOS
    值为10,操作系统特定操作的起始位置

  • GNU_UNIQUE
    值为10,独一无二的符号

  • HIOS
    值为12,操作系统特定操作的结束位置

  • LOPROC
    值为13,处理器特定操作的起始位置

  • HIPROC
    值为15,处理器特定操作的结束位置

st_shndx

表示符号所在的段,这个根据符号的类型代表的意义不一样

  • 如果符号是在当前目标文件定义的。该值表示符号所在的段索引。这时候我们根据符号的符号值st_value(这种情况下,符号值相当于段内的偏移),就能找到符号的位置。
  • 如果该值为0,还记得段的列表吗?第一个段是个空段。
    这个时候表示该符号在当前目标文件使用了,但是定义在别的地方,这也是我们链接着重处理的情况
  • 如果该值为0xfff1,表示该符号是一个绝对的值。比如表示文件名的符号,我们可以根据符号的符号值st_value,找到符号代表的值。
  • 如果该值为0xfff2,表示该符号是一个Common块类型的值,一般来说,未初始化的全局变量就是这种类型的。此时符号的符号值st_value表示符号的对齐属性。

st_value

符号值代表的意思根据st_shndx的不同也不同,上面介绍st_shndx的时候我们基本都涵盖了,值的说明的是:
在可执行文件中,st_value表示符号的虛拟地址。这个虚拟地址对于动态链接器来说十分有用

st_size

表示符号的大小,对于包含数据的符号,这个值是数据类型的大小
如果该值为0,表示该符号大小为0或者未知。

st_other

该成员暂时没用,为0

重定位表

重定位表是为了表示段中需要在链接阶段重定位的信息,包括重定位的地址计算方式,在段中的偏移,以及符号信息

注意:如果一个段有需要重定位的信息,则会生成一个针对当前段的重定位表
比如.text的重定位表就叫.rel.text。

重定位表项也是一个结构体,并且结构很简单

typedef struct
{
  uint64_t	r_offset;	
  uint64_t	r_info;	
} Elf64_Rel;

r_offset

这个字段分两种情况:

  1. 对于可重定位文件来说,这个值是该重定位入口所要修正的位置的第一个字节相对于段起始的偏移
  2. 对于可执行文件或者共享文件来说,这个值是该重定位入口所要修正的位置的虚拟地址

r_info

该字段表示重定位入口的类型和符号

  • 低32位:表示重定位入口的类型
  • 高32位:表示重定位入口的符号在符号表中的下标
    这样,通过重定位表就能确定符号和需要重定位位置的关联

你可能感兴趣的:(程序设计-编译器,算法)