4.1.2 符号解析与重定位

(1)重定位

在完成空间和地址的分配步骤之后,链接器就进入了符号解析和重定位的步骤,这是静态链接的核心部分。

先看看 a.o 的反汇编文件: objdump -d a.o:

程序代码里面都是使用的虚地址,main 起始地址为 0 ,这是因为在未进行空间分配之前,目标文件代码段中的起始地址以 0x00000000 开始,等到空间分配完成之后,各个函数才会确定自己在虚拟地址空间中的位置。

从反汇编来看,a.o 中定义了一个函数 main,这个函数占 0x55 个字节,共 21 条指令。冒号前的代表每条指令的偏移量。

上面的 eax,esi 等为寄存器,在参数很少的情况下编译器会选择让寄存器来传递参数,但这并不是一个通用的方法,通用的方法是将参数压入栈。

在第36偏移处,callq 调用了 swap 函数,swap 的地址是 0x00000000。对于shared 的引用,在第 29 偏移处,从寄存器中直接读取,当前 shared 的地址是 0x00000000。因为 a.c 并不知道 swap 和 shared 的地址,所以都为0。

再看下 ab 文件:

40056f 处的偏移有了地址 0x601038,即shared 分配出来了地址;同样的,40057c 偏移处,也有了地址 0x40059c,这个是 swap 的地址。这两个地址都是经过链接器修正的地址。

(2)重定位表

链接器通过重定位表(Relocation Table)中保存的与重定位相关的信息来确定哪些指令需要被调整以及指令的哪些部分需要被调整。

对于可重定位的目标文件来说,它必须包含有重定位表,用来描述如何修改相应的段里的内容。对于每个要重定位的 ELF 段都有一个对应的重定位表,而一个重定位表往往就是 ELF 文件中的一个段,所以重定位表也可以叫做重定位段。一般表示为".rel.text"等等。可以使用 objdump 来查看重定位表: objdump -r a.o,这个命令可以用来查看"a.o"里面要重定位的地方,即 "a.o"所有引用到外部符号的地址。

每个要被重定位地方叫一个重定位入口(Relocation Entry),"a.o"中有三个重定位入口,第三个我们不关心,只关心前面两个。重定位入口的偏移表示该入口在要被重定位的段中的位置,"RELOCATION RECORDS FOR [.text]"表示这个重定位表是代码段的重定位表,所以偏移表示代码段中需要被调整的位置。

从前面的反汇编可以知道,0x2a 和 0x37 分别就是代码中的 "mov" 和 "callq"指令。

重定位表的结构如下:

r_offset

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

r_info

重定位入口的类型和符号。这个成员你的低 8 位,表示重定位入口的类型,高 24 位表示重定位入口的符号在符号中的下标。

因为各种处理器的指令格式不一样,所以重定位所修正的指令地址格式也不一样。每种处理器都有自己一套重定位入口的类型。对于可执行文件和共享目标文件来说,它们的重定位入口是动态链接类型。

(3)符号类型

重定位过程伴随着符号解析的过程,每个目标文件都可能定义一些符号,也可能引用到定义在其他目标文件的符号。重定位过程中,每个重定位的入口都是对一个符号的引用,那么当链接器需要对某个符号的引用进行重定位时,它就要确定这个符号的目标地址。这时候链接器就会去查找由所有输入目标文件的符号表组成的全局符号表,找到相应的符号后进行重定位。

"GLOBAL"类型的符号,除了 main 定义在代码段之外,其他两个 "shared" 和 "swap" 都是"UND",即"undefined"未定义类型,这种未定义的符号都是因为该目标文件中有关于它们的重定位项。所以在链接器扫描完所有的输入目标文件后,所有这些未定义的符号都应该能够在全局符号表中找到,否则链接器就报符号未定义错误。

(4)指令修正方式

不同的处理器指令对于地址的格式和方式都不一样。主要的寻址方式有如下区别:

  • 近址寻址或远址寻址
  • 绝对寻址或相对寻址
  • 寻址长度为 8 位、16位、32位或 64 位

但对于 32 位 x86 平台下的 ELF 文件的重定位入口所修正的指令寻址方式只有两种:

  • 绝对近址 32 位寻址
  • 相对近址 32 位寻址

这两种重定位方式指令修正方式每个被修正的位置的长度都为 32 位,即 4 个字节。而且都是近址寻址。

x86 重定位入口的 r_info 成员低 8 位表示重定位入口类型,如下表:

x86基本重定位类型

宏定义

重定位修正方法

R_X86_64_32

10

绝对寻址修正 S + A

R_X86_64_PC32

2

相对寻址修正 S + A – P

A = 保存在被修正位置的值

P = 被修正的位置(相对于段开始的偏移量或者虚拟地址),注意,该值可以通过 r_offset 计算得到

S = 符号的实际地址,即由 r_info 的高 24 位指定的符号的实际地址

修正方式简单介绍即可,如需要深入研究,看看 GCC 方面的书籍。

4.1.3 静态库链接

一个静态库就是一组目标文件的集合,即很多目标文件打包后形成的一个文件。

一个 C 语言的运行库中,包含了很多跟系统功能相关的代码,如输入输出、文件操作等。当我们编译完成后,这些代码生成各种 .o 文件,我们通常使用"ar"压缩程序将这些目标文件压缩到一起,并且对齐进行编号和索引,以便于查找和检索,形成了 libc.a 类似的静态库文件。我们也可以使用 ar 命令来查看静态库文件包含哪些目标文件:ar -t libc.a

可以使用 objdump 工具查看 libc.a 符号: objdump -t libc.a

合理运用 grep 和 objdump 可以查找到我们需要的符号:objdump -t libc.a | grep -abw "printf.o"

编译程序:gcc -c -fno-builtin hello.c(-fno-builtin 禁止GCC 的自动优化功能)

解压静态库:ar -x libc.a

链接:ld hello.o printf.o

4.1.4 链接控制

链接器一般提供多种控制整个链接过程的方法,以用来产生用户需要的文件。一般连接器有如下三种方法:

  • 使用命令行来给链接器指定参数
  • 将链接指令存放在目标文件当中,编译器会通过这种方法像链接器传递指令
  • 使用链接控制脚本

ld 在用户没有指定链接脚本的时候会使用默认链接脚本。可以使用如下命令查看链接器的默认链接脚本:ld -verbose

如果看过 u-boot 的代码的话,可以知道 u-boot 中的 ARM 的链接控制脚本就是 u-boot.lds 这个文件。

我们可以自己编写自己的链接脚本,然后通过命令指定编译的链接脚本 ld -T link.script

具体的链接脚本在分析 u-boot 的时候再介绍。

GCC编译器原理(三)------编译原理三:编译过程(3)---编译之汇编以及静态链接【2】的更多相关文章

  1. 原创 C++应用程序在Windows下的编译、链接:第三部分 静态链接(二)

    3.5.2动态链接库的创建 3.5.2.1动态链接库的创建流程 动态链接库的创建流程如下图所示: 在系统设计阶段,主要的设计内容包括:类结构的设计以及功能类之间的关系,动态链接库的接口.在动态链接库中 ...

  2. 编译有哪些阶段,动态链接和静态链接的区别 c++

    预处理—->编译—->汇编—->链接 预处理:编译器将C程序的头文件编译进来,还有宏的替换 编译:这个阶段编译器主要做词法分析.语法分析.语义分析等,在检查无错误后后,把代码翻译成汇 ...

  3. 跟vczh看实例学编译原理——三:Tinymoe与无歧义语法分析

    文章中引用的代码均来自https://github.com/vczh/tinymoe.   看了前面的三篇文章,大家应该基本对Tinymoe的代码有一个初步的感觉了.在正确分析"print ...

  4. jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理

    这一节要分析的东东比较复杂,篇幅会比较大,也不知道我描述后能不能让人看明白.这部分的源码我第一次看的时候也比较吃力,现在重头看一遍,再分析一遍,看能否查缺补漏. 看这一部分的源码需要有一个完整的概念后 ...

  5. gcc/g++等编译器 编译原理: 预处理,编译,汇编,链接各步骤详解

    摘自http://blog.csdn.net/elfprincexu/article/details/45043971 gcc/g++等编译器 编译原理: 预处理,编译,汇编,链接各步骤详解 C和C+ ...

  6. 正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——3 计算4个函数

    整个引擎代码在github上,地址为:https://github.com/sun2043430/RegularExpression_Engine.git nullable, firstpos, la ...

  7. GCC编译器原理(二)------编译原理一:ELF文件(2)

    四. ELF 文件格式分析 ELF文件(目标文件)格式主要四种: 可重定向文件: 文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件.(目标文件或者静态库文 ...

  8. 学了编译原理能否用 Java 写一个编译器或解释器?

    16 个回答 默认排序​ RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...

  9. 浅谈C++编译原理 ------ C++编译器与链接器工作原理

    原文:https://blog.csdn.net/zyh821351004/article/details/46425823 第一篇:      首先是预编译,这一步可以粗略的认为只做了一件事情,那就 ...

随机推荐

  1. optimize PHP-FPM优化

    php-fpm进程pidpids=$(ps aux | grep ${process} | grep -v "grep" | awk '{print $2}') php-fpm 关 ...

  2. [HAOI2015]按位或(容斥+前缀和)

    题目描述 刚开始你有一个数字0,每一秒钟你会随机选择一个[0,2^n-1]的数字,与你手上的数字进行或(c++,c的|,pascal 的or)操作.选择数字i的概率是p[i].保证0<=p[i] ...

  3. selenium的等待~

    既然使用了selenium,那么必然牺牲了一些速度上的优势,但由于公司网速不稳定,导致频频出现加载报错,这才意识到selenium等待的重要性. 说到等待又可以分为3类, 1.强制等待 time.sl ...

  4. Zabbix3.4监控平台部署

    环境依赖 CentOS 7.3 + PHP5.4 + MariaDB + Nginx Zabbix Server 3.4.1 环境要求 12 CPU ,最少8 CPU 32G 内存,最少16G 1T ...

  5. 牛客小白月赛12 F 华华开始学信息学 (分块+树状数组)

    链接:https://ac.nowcoder.com/acm/contest/392/F来源:牛客网 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 32768K,其他语言65536K ...

  6. 项目部署之nginx实现PC端和移动端自动跳转

    假设PC端域名为 www.abc.com     移动端域名为m.abc.com PC端nginx配置文件server中加入如下代码: if ($http_host !~ "^www.abc ...

  7. Dreamweaver - <!DOCTYPE html>

    最近设计网页,很多使用 <!DOCTYPE html> 关于<!DOCTYPE html>的详细介绍: http://www.w3school.com.cn/tags/tag_ ...

  8. Can not issue data manipulation statements with executeQuery()错误解决

    转: Can not issue data manipulation statements with executeQuery()错误解决 2012年03月27日 15:47:52 katalya 阅 ...

  9. 第十八节,TensorFlow中使用批量归一化(BN)

    在深度学习章节里,已经介绍了批量归一化的概念,详情请点击这里:第九节,改善深层神经网络:超参数调试.正则化以优化(下) 神经网络在进行训练时,主要是用来学习数据的分布规律,如果数据的训练部分和测试部分 ...

  10. TestNg 2.套件测试

    看一下我的目录结构,新建一个包,名字叫做suite,主要为了做套件的测试用.然后在resource下新建一个文件,一般的叫做testng.xml,我这里随便起个名字,叫做suite.xml. 运行的时 ...