标签:特殊符号 问题 AC 结构 使用 off 接口 location 技术
ELF目标文件格式最前部ELF文件头(ELF Header),它包含了描述了整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。其中ELF文件与段有关的重要结构就是段表(Section Header Table)
我们可以使用readelf命令来详细查看elf文件,代码如清单3-2所示:
从上面输出的结构可以看到:ELF文件头定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台等。
ELF文件头结构及相关常数被定义在“/usr/include/elf.h”,因为ELF文件在各种平台下都通用,ELF文件有32位版本和64位版本的ELF文件的文件头内容是一样的,只不过有些成员的大小不一样。它的文件图也有两种版本:分别叫“Elf32_Ehdr”和“Elf64_Ehdr”。
typedef struct {
unsigned char e_ident[16];
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;
段表就是保存ELF文件中各种各样段的基本属性的结构。段表是ELF除了文件以外的最重要结构体,它描述了ELF的各个段的信息,ELF文件的段结构就是由段表决定的。编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。段表在ELF文件中的位置由ELF文件头的“e_shoff”成员决定的,比如SimpleSection.o中,段表位于偏移0x118。
我们注意到:SimpleSection.o中有一个叫做".rel.text"的段,它的类型是(sh_type)为“SHT_REL”,也就是说它是一个重定位表(Relocation Table)。正如我们开始所说的,链接器在处理目标文件时,须要对目标文件中某些部位进行重定位,即代码段和数据段中哪些对绝对地址的引用的位置。这些重定位的信息都记录在ELF文件的重定位表里面,对于每个须要重定位代码段或数据段,都会有一个相应的重定位表。
ELF文件中用到了许多的字符串,比如段名,变量名等。因为字符串的长度往往是不定的,所以用固定的结构来表示它比较困难。一种常见的做法是把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串。
通常用这种方式,在ELF文件中引用字符串只需给一个数字下标即可,不用考虑字符串的长度问题。一般字符串标在ELF文件中国也以段的方式保存,常见的段名为“.strtab”或“.shstrtab”。这两个字符串分别表示为字符串表和段表字符串表。
只有分析ELF文件头,就可以得到段表和段表字符串表的位置,从而解析整个ELF文件。
链接的过程的本质就是要把多个不同目标文件之间相互“粘”到一起。或者说像玩具积木一样,可以拼装成一个整体。为了使不同目标文件之间能够相互黏合,这些目标文件之间必须有固定的规则才行,就像积木模块必须有凹凸部分才能相互黏合。在链接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。
比如目标文件B要用到目标文件中的函数“foo”,那么我们就成目标文件A定义(Define)了函数“foo”,称目标文件B引用(Reference)了目标文件A中的函数“foo”。这两个概念也同样适用于变量。每个函数或变量都有自己独特的名字,才能避免链接过程中不同变量和函数之间的混淆。在链接中,我们将函数和变量统称为符号(Symbol),函数名或变量名就是符号名(Symbol Name)。
我们可以将符号看作是链接中的粘合剂,整个链接过程正是基于符号才能够完成。链接过程中很关键的一部分是符号的管理,每一个目标文件都会有一个相应的符号表(Symbol Table),这个表里记录了目标文件所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值(Symbol Value),对于变量和函数来说,符号值就是它们的地址,除了函数和变量之外,还存在着其他几种不常用的符号。我们将符号表中的所有符号进行分类,它们有可能是下面这些类型中的几种:
ELF文件中的符号表往往是文件中的一个段,段名一般叫做“.symtab”。符号表的结构很简单,它是一个Elf32_Sym结构(32位ELF文件)的数组,每个Elf32_Sym结构对应一个符号。这个数组的第一个元素,也就是下标0的元素为无效的“未定义”符号。Elf32_Sym的结构对应如下:
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 | 符号名。这个成员包含了该符号名在字符串表中的下标 |
---|---|
st_value | 符号对应的值。这个值跟符号有关,可能是一个绝对值 |
st_size | 符号大小 |
st_info | 符号类型和绑定信息 |
st_other | 该成员目前为0,没用 |
st_shndx | 符号所在段 |
当我们使用ld作为链接器产生可执行文件,它会为我们定义很多特殊符号。这些符号并没有在你的程序中定义,但是你可以直接声明并引用它,我们称之为特殊符号。其实这些符号是被定义在链接器脚本中的,我们无须定义它们,但可以声明它们并且使用它们。链接器在程序最终连接成可执行文件将其解析成正确的值,注意,只有使用ld链接生成最终可执行文件的时候这些符号才会存在。
几个很具有代表性的特殊符号如下:
在早期,编译器编译源代码产生目标文件时,符号名与相应的变量和函数名字一样的。比如在一个汇编源代码中包含了一个函数foo,那么汇编器将它编译成目标文件后,foo在目标文件中所对应的符号名也是foo。后来的UNIX平台和C语言发明时,已经存在了相当多的使用汇编编写的库和目标文件。这样就产生了一个问题,那就是如果一个c程序要使用这些库的话,C语言不可以使用这些库中定义的函数和变量的名字作为符号名,否则将会跟现有的目标文件冲突。比如有个用汇编语言编写的库定义了一个函数叫做main,那么我们在C语言里面就不可以定义一个main函数或变量了。同样的道理,如果一个C语言的目标文件要用到一个使用Fortran语言编写的目标文件,我们也必须防止它们的名称冲突。
为了防止类似的符号名冲突,UNIX的C语言就规定,C语言源代码文件中的所有全局变量和函数经过编译后,相对应的符号名加上“”。而Fortran语言的源代码经过编译以后,所有符号名前加上“”,后面也加上“_”
这种方式虽然能够减少冲突的概率,但还是有可能造成冲突。于是C++开始考虑到这个问题,增加了namespace来解决多模块的符号冲突问题。
标签:特殊符号 问题 AC 结构 使用 off 接口 location 技术
原文地址:https://www.cnblogs.com/linhaostudy/p/8855238.html