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. [luogu1110][报表统计]

    题目链接 思路 set+map+优先队列就可以水过去.可以发现,每插入一个元素,都会使得操作2中原来相邻的那个差值消失,然后多了两个新的差值.对于新的差值,只要直接扔到优先队列里就好了.那么删除呢.可 ...

  2. ANIS与UNICODE字符格式转换:MultiByteToWideChar() 和WideCharToMultiByte() 函数

    资料来自: http://blog.csdn.net/holamirai/article/details/47948745 http://www.cnblogs.com/wanghao111/arch ...

  3. 团体程序设计天梯赛(CCCC) L3012 水果忍者 上凸或下凹的证明

    团体程序设计天梯赛代码.体现代码技巧,比赛技巧.  https://github.com/congmingyige/cccc_code #include <cstdio> #include ...

  4. R语言修改标题、坐标轴刻度、坐标轴名称的大小(cex.axis、cex.lab、cex.main函数)

    修改标题.坐标轴刻度.坐标轴名称的大小,用到了cex.axis.cex.lab.cex.main函数,其中,cex.axis表示修改坐标轴刻度字体大小,cex.lab表示修改坐标轴名称字体大小,cex ...

  5. IDEA或Webstorm设置Ctrl+滚轮调整字体大小

    按Ctrl+Shift+A,出现搜索框 输入mouse: 点击打开这个设置:勾选 点击ok,之后就可以通过Ctrl+滚轮 调整字体大小了.

  6. poj 3273"Monthly Expense"(二分搜索+最小化最大值)

    传送门 https://www.cnblogs.com/violet-acmer/p/9793209.html 题意: 有 N 天,第 i 天会有 a[ i ] 的花费: 将这 N 天分成 M 份,每 ...

  7. socket关闭状态问题

    下面是对 譬如  “CLOSE_WAIT” 现象的一些解释: 主动关闭方和被动方经历的状态:FIN_WAIT_1(主动关闭一方): 当SOCKET在ESTABLISHED状态时,它想主动关     闭 ...

  8. 利用/dev/urandom文件创建随机数

    1:/dev/urandom和/dev/random是什么 这两个文件记录Linux下的熵池,所谓熵池就是当前系统下的环境噪音,描述了一个系统的混乱程度,环境噪音由这几个方面组成,如内存的使用,文件的 ...

  9. (LIS DP) codeVs 1044 拦截导弹

    题目描述 Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某 ...

  10. (大数 string easy。。。)P1781 宇宙总统 洛谷

    题目背景 宇宙总统竞选 题目描述 地球历公元6036年,全宇宙准备竞选一个最贤能的人当总统,共有n个非凡拔尖的人竞选总统,现在票数已经统计完毕,请你算出谁能够当上总统. 输入输出格式 输入格式: pr ...