GCC编译器原理(二)------编译原理一:ELF文件(3)
4.5 String Table:字符串表
字符串表节区包含以 NULL( ASCII 码 0) 结尾的字符序列, 通常称为字符串。 ELF 目标文件通常使用字符串来表示符号和节区名称。 对字符串的引用通常以字符串在字符串表中的下标给出。
一般, 第一个字节(索引为 0)定义为一个空字符串。类似的,字符串表的最后一个字节也定义为 NULL,以确保所有的字符串都以 NULL 结尾。索引为 0 的字符串在不同的上下文中可以表示无名或者名字为 NULL 的字符串。
允许存在空的字符串表节区,其节区头部的 sh_size 成员应该为 0。对空的字符串表而言,非 0 的索引值是非法的。
例如:对于各个节区而言,节区头部的 sh_name 成员包含其对应的节区头部字符串表节区的索引,此节区由 ELF 头的 e_shstrndx 成员给出。下图给出了包含 25 个字节的一个字符串表,以及与不同索引相关的字符串。
上表中包含的字符串如下:
- 在使用、分析字符串表时,要注意以下几点:
- 字符串表索引可以引用节区中任意字节。
- 字符串可以出现多次
- 可以存在对子字符串的引用
- 同一个字符串可以被引用多次。
- 字符串表中也可以存在未引用的字符串。
4.6 Symbol Table:符号表
目标文件的符号表中包含用来定位、 重定位程序中符号定义和引用的信息。 符号表索引是对此数组的索引。索引 0 表示表中的第一表项,同时也作为未定义符号的索引(即 STN_UNDEF)。
目标文件中的符号表通常是文件中的一个段,段名一般叫做".symtab"。符号表的结构是 Elf32_sym,每个 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 |
包含目标文件符号字符串表的索引, 其中包含符号名的字符串表示。 如果该值非 0, 则它表示了给出符号名的字符串表索引, 否则符号表项没有名称。 注:外部 C 符号在 C 语言和目标文件的符号表中具有相同的名称。 |
st_value |
此成员给出相关联的符号的取值。依赖于具体的上下文,它可能是一个绝对值、一个地址等等。 |
st_size |
很多符号具有相关的尺寸大小。 例如一个数据对象的大小是对象中包含的字节数。如果符号没有大小或者大小未知,则此成员为 0。 |
st_info |
此成员给出符号的类型和绑定属性。 下面给出若干取值和含义的绑定关系。 |
st_other |
该成员当前包含 0,其含义没有定义。 |
st_shndx |
每个符号表项都以和其他节区间的关系的方式给出定义。此成员给出相关的节区头部表索引。某些索引具有特殊含义。 |
4.6.1 st_info符号类型和绑定信息
该成员的低 4 位表示符号的类型(Symbol Type),高 28 位表示符号绑定信息(Symbol Binding),符号表项 st_info 字段合成如下:
/usr/include/elf.h
低 4 位表示符号绑定,用于确定链接可见性和行为,具体绑定类型如下:
名称 |
取值 |
说明 |
STB_LOCAL |
0 |
局部符号在包含该符号定义的目标文件以外不可见。 相同名称的局部符号可以存在于多个文件中,互不影响。 |
STB_GLOBAL |
1 |
全局符号对所有将组合的目标文件都是可见的。一个文件中对某个全局符号的定义将满足另一个文件对相同全局符号的未定义引用。 |
STB_WEAK |
2 |
弱符号与全局符号类似,不过他们的定义优先级比较低。 |
STB_LOPROC |
13 |
处于这个范围的取值是保留给处理器专用语义的。 |
STB_HIPROC |
15 |
在每个符号表中,所有具有 STB_LOCAL 绑定的符号都优先于弱符号和全局符号。符号表节区中的 sh_info 头部成员包含第一个非局部符号的符号表索引。
符号类型( ELF32_ST_TYPE)定义如下:
名称 |
取值 |
说明 |
STT_NOTYPE |
0 |
符号的类型没有指定 |
STT_OBJECT |
1 |
符号与某个数据对象相关,比如一个变量、数组等等 |
STT_FUNC |
2 |
符号与某个函数或者其他可执行代码相关 |
STT_SECTION |
3 |
符号与某个节区相关。 这种类型的符号表项主要用于重定位,通常具有 STB_LOCAL 绑定。 |
STT_FILE |
4 |
传统上, 符号的名称给出了与目标文件相关的源文件的名称。文件符号具有 STB_LOCAL 绑定,其节区索引是 SHN_ABS, 并且它优先于文件的其他 STB_LOCAL 符号(如果有的话) |
STT_LOPROC |
13 |
此范围的符号类型值保留给处理器专用语义用途。 |
STT_HIPROC |
15 |
在共享目标文件中的函数符号(类型为 STT_FUNC)具有特别的重要性。当其他目标文件引用了来自某个共享目标中的函数时, 链接编辑器自动为所引用的符号创建过程链接表项。类型不是 STT_FUNC 的共享目标符号不会自动通过过程链接表进行引用。
如果一个符号的取值引用了某个节区中的特定位置,那么它的节区索引成员(st_shndx)包含了其在节区头部表中的索引。当节区在重定位过程中被移动时,符号的取值也会随之变化,对符号的引用始终会 "指向" 程序中的相同位置。
【1】弱符号与强符号
在编程中经常碰到一种情况叫符号重复定义。多个目标文件中含有相同名字全局符号的定义,在这些目标文件链接的时候将会出现符号重复定义的错误。
强符号(Strong Symbol):对于C/C++ 语言来说,编译器默认函数和初始化了的全局变量为强符号
弱符号(Weak Symbol):未初始化的全局变量为弱符号
可以通过 GCC 的 "__attribute__((weak))" 来定义任何一个强符号为弱符号。需要注意的是,强符号和弱符号都是针对定义来说的,不是针对符号引用。
- 链接器处理强弱符号的规则如下:
- 规则1:不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号);如果有多个强符号定义,则链接器报符号重复定义错误
- 规则2:如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号
- 规则3:如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个
【2】弱引用和强引用
强引用(Strong Reference):我们所看到的对外部目标文件的符号引用在目标文件被最终链接成可执行文件时,它们必须要被正确决议,如果没有找到该符号的定义,链接器就会报符号未定义错误,这种就称为强引用。
弱引用(Weak Reference):在处理弱引用的时候,如果该符号有定义,则链接器将该符号的引用决议;如果该符号未定义,则链接器对于该引用不报错。
弱引用和强引用主要用于库的链接过程。
在 GCC 中,可以通过符号 "__attribute__((weakref))" 这个扩展关键字来声明对一个外部函数的引用为弱引用。例如:
__attribute__ ((weakref)) void foo(); int main()
{
if(foo) foo();
}
4.6.2 st_shndx:节区索引
- 某些特殊的节区索引具有不同的语义:
- SHN_ABS:符号具有绝对取值,不会因为重定位而发生变化。
- SHN_COMMON:符号标注了一个尚未分配的公共块。符号的取值给出了对齐约束,与节区的 sh_addralign 成员类似。就是说,链接编辑器将为符号分配存储空间,地址位于 st_value 的倍数处。 符号的大小给出了所需要的字节数。
- SHN_UNDEF: 此节区表索引值意味着符号没有定义。当链接编辑器将此目标文件与其他定义了该符号的目标文件进行组合时, 此文件中对该符号的引用将被链接到实际定义的位置。
4.6.3 STN_UNDEF 符号
符号表中下标为 0(STN_UNDEF)的表项被保留。其中包含如下数值:
名称 |
取值 |
说明 |
st_name |
0 |
无名称 |
st_value |
0 |
0 值 |
st_size |
0 |
无大小 |
st_info |
0 |
无类型,局部绑定 |
st_other |
0 |
无附加信息 |
st_shndx |
0 |
无节区 |
4.6.4 st_value:符号取值
- 不同的目标文件类型中符号表项对 st_value 成员具有不同的解释:
- 在可重定位文件中, st_value 中遵从了节区索引为 SHN_COMMON 的符号的对齐约束。
- 在可重定位的文件中, st_value 中包含已定义符号的节区偏移。 就是说,st_value 是从 st_shndx 所标识的节区头部开始计算,到符号位置的偏移。
- 在可执行和共享目标文件中, st_value 包含一个虚地址。为了使得这些文件的符号对动态链接器更有用,节区偏移( 针对文 件的解释)让位于虚拟地址(针对内存的解释),因为这时与节区号无关。
尽管符号表取值在不同的目标文件中具有相似的含义, 适当的程序可以采取高效的数据访问方式。
4.7 重定位信息
重定位是将符号引用与符号定义进行连接的过程。例如,当程序调用了一个函数时,相关的调用指令必须把控制传输到适当的目标执行地址。
4.7.1 重定位表
成员 |
说明 |
r_offset |
此成员给出了重定位动作所适用的位置。对于一个可重定位文件而言,此值是从节区头部开始到将被重定位影响的存储单位之间的字节偏移。对于可执行文件或者共享目标文件而言, 其取值是被重定位影响到的存储单元的虚拟地址。 |
r_info |
此成员给出要进行重定位的符号表索引, 以及将实施的重定位类型。 例如一个调用指令的重定位项将包含被调用函数的符号表索引。 如果索引是 STN_UNDEF, 那么重定位使用 0 作为"符号值"。重定位类型是和处理器相关的。当程序代码引用一个重定位项的重定位类型或者符号表索引, 则表示对表项的 r_info 成员应用 ELF32_R_TYPE 或者 ELF32_R_SYM 的结果。 #define ELF32_R_SYM(i) ((i)>>8) #define ELF32_R_TYPE(i)((unsigned char)(i)) #define ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t) |
r_addend |
此成员给出一个常量补齐, 用来计算将被填充到可重定位字段的数值。 |
如上所述,只有 Elf32_Rela 项目可以明确包含补齐信息。类型为 Elf32_Rel 的表项在将被修改的位置保存隐式的补齐信息。依赖于处理器体系结构,各种形式都可能存在, 甚至是必需的。 因此, 对特定机器的实现可以仅使用一种形式, 也可以根据上下文使用不同的形式。
重定位节区会引用两个其它节区:符号表、要修改的节区。节区头部的 sh_info 和 sh_link 成员给出这些关系。不同目标文件的重定位表项对 r_offset 成员具有略微不同的解释。
- 在可重定位文件中, r_offset 中包含节区偏移。就是说重定位节区自身描述了如何修改文件中的其他节区;重定位偏移 指定了被修改节区中的一个存储单元。
- 在可执行文件和共享的目标文件中, r_offset 中包含一个虚拟地址。为了使得这些文件的重定位表项对动态链接器更为有用,节区偏移(针对文件的解释)让位于虚地址(针对内存的解释)。
尽管对 r_offset 的解释会有少许不同,重定位类型的含义始终不变。
4.7.2 重定位类型
重定位表项描述如何修改后面的指令和数据字段。一般,共享目标文件在创建时,其基本虚拟地址是 0,不过执行地址将随着动态加载而发生变化。
- 重定位的过程,按照如下标记:
- A 用来计算可重定位字段的取值的补齐。
- B 共享目标在执行过程中被加载到内存中的位置(基地址)。
- G 在执行过程中, 重定位项的符号的地址所处的位置 —— 全局偏移表的索引。
- GOT 全局偏移表( GOT)的地址。
- L 某个符号的过程链接表项的位置(节区偏移/地址)。过程链接表项把函数调用重定位到正确的目标位置。链接编辑器构造初始的过程链接表,动态链接器在执行过程中修改这些项目。
- P 存储单位被重定位(用 r_offset 计算) 到的位置(节区偏移或者地址)。
- S 其索引位于重定位项中的符号的取值。
重定位项的 r_offset 取值给定受影响的存储单位的第一个字节的偏移或者虚拟地址。重定位类型给出那些位需要修改以及如何计算它们的取值。
GCC编译器原理(二)------编译原理一:ELF文件(3)的更多相关文章
- 使用gcc不同选项来编译查看中间生成文件
gcc编译C程序的总体流程如下图 用到的命令如下: .c---> .i gcc -E hello.c .c--->.s gcc -S hello.c .c--->.o gcc -c ...
- Knowledge Point 20180303 对比编译器、解释器与Javac编译原理
编译器与Javac编译原理 在前文我们知道了Java是一种编译语言和解释语言,它的源代码经过编译器Javac编译为能够被JVM识别的二进制语言,然后JVM将其解释为能够被平台识别的机器语言.那么什么是 ...
- 前端与编译原理——用JS写一个JS解释器
说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念.作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于"抽象语法树(AST)".但这仅仅是个开头而已.编 ...
- 扒一扒ELF文件
ELF文件(Executable Linkable Format)是一种文件存储格式.Linux下的目标文件和可执行文件都按照该格式进行存储,有必要做个总结. 目录 1. 链接举例 2. ELF文件类 ...
- GCC编译器原理(二)------编译原理一:ELF文件(2)
四. ELF 文件格式分析 ELF文件(目标文件)格式主要四种: 可重定向文件: 文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件.(目标文件或者静态库文 ...
- GCC编译器原理(二)------编译原理一:ELF文件(1)
二.ELF 文件介绍 2.1 可执行文件格式综述 相对于其它文件类型,可执行文件可能是一个操作系统中最重要的文件类型,因为它们是完成操作的真正执行者.可执行文件的大小.运行速度.资源占用情况以及可扩展 ...
- gcc/g++等编译器 编译原理: 预处理,编译,汇编,链接各步骤详解
摘自http://blog.csdn.net/elfprincexu/article/details/45043971 gcc/g++等编译器 编译原理: 预处理,编译,汇编,链接各步骤详解 C和C+ ...
- GCC编译器原理(三)------编译原理三:编译过程---预处理
Gcc的编译流程分为了四个步骤: 预处理,生成预编译文件(.文件):gcc –E hello.c –o hello.i 编译,生成汇编代码(.s文件):gcc –S hello.i –o hello. ...
- GCC编译器原理(三)------编译原理三:编译过程(3)---编译之汇编以及静态链接【2】
4.1.2 符号解析与重定位 (1)重定位 在完成空间和地址的分配步骤之后,链接器就进入了符号解析和重定位的步骤,这是静态链接的核心部分. 先看看 a.o 的反汇编文件: objdump -d a.o ...
- 跟vczh看实例学编译原理——二:实现Tinymoe的词法分析
文章中引用的代码均来自https://github.com/vczh/tinymoe. 实现Tinymoe的第一步自然是一个词法分析器.词法分析其所作的事情很简单,就是把一份代码分割成若干个tok ...
随机推荐
- CAN总线网络的传输模式
CAN总线网络的传输模式根据触发条件的不同,在车身CAN网络中可分为事件型.周期性及混合型三种传输模式: 1.事件型传输模式: 随着类型或数据的转变及时发送的消息.此类型消息的好处是极少占用总线资源, ...
- Tbox在整车CAN网络的位置与作用
我们讲到了智能车载娱乐系统的5个基本特征: 基本来说, 当今的智能车机基本有以下几个特点: 基于智能操作系统: Android, Yunos, Linux等 基本都是虚拟按键, 较少用实体按键 具备外 ...
- BZOJ 1912: [Apio2010]patrol 巡逻 (树的直径)(详解)
题目: https://www.lydsy.com/JudgeOnline/problem.php?id=1912 题解: 首先,显然当不加边的时候,遍历一棵树每条边都要经过两次.那么现在考虑k==1 ...
- Django 创建超级用户
Django自带的后台管理是Django明显特色之一,可以让我们快速便捷管理数据.后台管理可以在各个app的admin.py文件中进行控制 #创建超级用户 python manage.py creat ...
- Django(九)admin相关知识
https://www.cnblogs.com/yuanchenqi/articles/6083427.htm https://www.cnblogs.com/haiyan123/p/8034430. ...
- 模块---hashlib、configparse、logging
一.hashlib模块 hashlib模块介绍:hashlib这个模块提供了摘要算法,例如 MD5.hsa1 摘要算法又称为哈希算法,它是通过一个函数,把任意长度的数据转换为一个长度固定的数据串,这个 ...
- 自定义QMenu
参考: http://blog.csdn.net/qq1623803207/article/details/77449884 http://blog.sina.com.cn/s/blog_a6fb6c ...
- apache2 以及https证书配置
环境Ubuntu12.04 server 配置 1,首先在进入找到/etc/apache2/apache2.conf的配置文件,里面有包含了较多配置文件的路径如:httpd.conf/ports.co ...
- 字节转字符 OutputStreamWriter
package cn.lideng.demo4; import java.io.FileNotFoundException; import java.io.FileOutputStream; impo ...
- phpmyadmin拿webshell
思路:就是利用mysql的一个日志文件.这个日志文件每执行一个sql语句就会将其执行的保存.我们将这个日志文件重命名为我们的shell.php然后执行一条sql带一句话木马的命令.然后执行菜刀连接之! ...