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. centos7安装部署本地局域网yum源

    应用场景: 当Linux系统都是最小化安装的系统,又无法做到每台都能访问外网的情况下,安装常用工具或者依赖包的最好办法可能就是建立本地yum源了. 安装环境: 一台 centos 7.4 minima ...

  2. BZOJ 1143: [CTSC2008]祭祀river(最大独立集)

    题面: https://www.lydsy.com/JudgeOnline/problem.php?id=1143 一句话题意:给一个DAG(有向无环图),求选出尽量多的点使这些点两两不可达,输出点个 ...

  3. 2019.4.1考试&2019.4.2考试&2019.4.4考试

    4.1:T1原题,T2码农板子题,T3板子题 4.2 好像是三个出题人分别出的 以及#define *** 傻逼 T1 思维好题 转成树形DP,$dp[i][j]$表示点i值为j的方案数,记录前缀和转 ...

  4. tyvj/joyoi 2018 小猫爬山

    2018,这个题号吼哇! 搜索第一题,巨水. WA了一次,因为忘了还原... #include <cstdio> ; int n, W, ans, weigh[N], cost[N]; i ...

  5. 获取url中的参数并以对象的形式展示出来

    速记:获取url中的参数并以对象的形式展示出来 function getUrlData(){ let url=window.location.search;//url中?之后的部分 console.l ...

  6. 网上找的Backbone.js

    // Backbone.js 0.9.2 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. // Backbone may be freely ...

  7. my97DatePicker选择年、季度、月、周、日(转)

    My97DatePicker是一款非常灵活好用的日期控件.使用非常简单. 下面总结下使用该日历控件选择年.季度.月.周.日的方法. 1.选择年 <input id="d1212&quo ...

  8. Mac上在终端上解压与压缩

    1.安装rar 1.brew install unrar 2.unrar -version 3.进入需要解压的文件目录下,unrar x 文件夹名.rar 1.tar -xvf [file.tar.g ...

  9. 多线程程序在单核cpu与多核cpu上是怎么工作的?

    转自 1.多线程在单核和多核CPU上的执行效率问题的讨论 a1: 多线程在单cpu中其实也是顺序执行的,不过系统可以帮你切换那个执行而已,其实并没有快(反而慢) 多个cpu的话就可以在两个cpu中同时 ...

  10. (字符串 枚举)The Hardest Problem Ever hdu1048

    The Hardest Problem Ever 链接:http://acm.hdu.edu.cn/showproblem.php?pid=1048 Time Limit: 2000/1000 MS ...