一直对ELF目标文件是怎样链接成可执行文件感到比较的疑惑,ELF文件里面的重定位段是怎样解决符号引用问题的?前几天偶然看了《深入理解计算机系统》里面讲了这个问题,看了之后对里面的实现机制终于有了一定的理解。

  当有链接器链接多个可重定位的共享对象时,共享对象时怎样合并的呢?很简单,链接器将相同类型的节合并在一起,比如将所有输入文件的.text合并到输出文件的.text段,接着是.data段,.bss段等。

  链接器扫描所有的输入目标文件,并且获得它们各个节的长度,属性和位置,并将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表中。链接器能够获得所有输入目标文件的节长度,并将它们合并,并计算出输出文件中各个节合并后的段长度和位置,并建立段和节之间的映射关系。也就是说,在将输入文件的各个节映射到段中后,输入文件中的各个节在链接后的虚拟地址就已经确定了,那么全局符号表中的符号地址就可以知道了。

  知道了定义符号的虚拟地址,合并了各个节,是不是就大功告成了?显然没有这么简单,由于输入给链接器的文件都是可重定位的目标文件,这些目标文件中引用符号的地方存放的地址肯定不是最终的虚拟地址,因为这个时候符号地址还不确定。那么链接器在知道了符号的各个虚拟地址后怎样来修改引用符号的地址为实际的符号虚拟地址呢?这个工作是通过重定位目标文件中的重定位表来实现的。

  对于每个要重定位的ELF节都有一个对应的重定位表,而一个重定位表往往就是ELF文件的一个节。比如代码节.text有要重定位的地方,那么会有一个相对应的.rel.text的节保存.text的重定位表;如果数据节.data有要被重定位的地方,也会有一个相对应的叫.rel.data的节与之对应。

  每个要被重定位的地方叫做一个重定位入口(Relocation Entry),重定位入口的偏移(Offset)表示该入口在要被重定位的节中的位置。重定位表的结构很简单,它是一个Elf32_Rel结构的数组,每个数组元素对应一个重定位入口。Elf32_Rel定义如下:

typedef struct elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;

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

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

  • R_386_PC32:重定位一个使用32位pc相对地址的引用。
  • R_386_32:重定位使用32位绝对地址的引用。

  链接器中根据重定位表来修改符号引用地址的算法是:

  第一行迭代每个节,第二行迭代这个节中的每一个重定位表项,为了简单,我们假设节是一个字节数组,每个重定位表项都是Elf32_Rel类型,并假设当这个算法运行时,链接器也已经知道了每个节的运行时地址(定义为ADDR(s))和每个符号的运行时地址(定义为ADDR(r.symbol))。

  根据重定位入口的类型,需要分两个情况来修改引用地址。

  重定位PC相对引用

  假设函数printf在main函数被调用,现在来看下它在main函数中是怎么被调用的:

1b:   e8 fc ff ff ff          call    <main+0x7>

  我们可以看到call指令在节的偏移量为0x7,这条指令由1个字节的操作码0xe8和32比特的引用0xfffffffc(-4)组成。我们也可以看到对这个引用的重定位项:

r.offset = 0x7
r.symbol = printf
r.type = R_386_PC32

  重定位的项告诉链接器修改32位PC相对引用在节的偏移量为0x7,现在假设链接器已经决定了:

ADDR(s) = ADDR(.text) = 0x80483b4
ADDR(r.symbol) = ADDR(printf) = 0x80483c8

  使用前面的算法来计算引用的运行时地址:

refaddr = ADDR(s) + r.offset
= 0x80483b4 + 0x7
= 0x80483bb

  修改引用符号地址的值,使得引用指向printf运行时的地址:

*refptr = (unsigned)(ADDR(r.symbol) + *refptr - refaddr)
= (unsigned)(0x80483c8 + (-) - 0x80483bb)
= (unsigned)(0x9)

  这样,printf的重定位值就为0x9。当程序运行时,由于call指令时相对pc寻址的,所以计算出的printf的实际地址为:

pc + 0x9 = 0x80483bf + 0x9 = 0x80483c8

  重定位绝对地址引用

  假设定义了下面一个全局变量:

int *bufp0 = &buf[]

  由于bufp0是一个初始化了的数据对象,它会被存放在.data节中。由于它被初始化为全局变量buf[0]的地址,因此bufp0需要被重定位。下面是bufp0在.data节中的反汇编代码:

<bufp0>
:

  我们可以看到.data节中包含了一个简单的32位的引用,指针bufp0的值位0x0。重定位表项告诉链接器这是一个32位的绝对地址引用,相对于.data节的偏移量为0。它必须被重定位以指向buf[0]的地址。假设链接器已经决定了:

ADDR(r.symbol) = ADDR(buf) = 0x8049454

  链接器将根据上面的算法更新这个引用:

*refptr = (unsigned)(ADDR(r.symbol) + *refptr)
= (unsigned)0x8049454 + )
= (unsigned)(0x8049454)

ELF静态链接的更多相关文章

  1. 实例分析ELF文件静态链接

    参考文献: <ELF V1.2> <程序员的自我修养---链接.装载与库>第4章 静态链接 开发平台: [thm@tanghuimin static_link]$ uname ...

  2. ELF动态链接

    为什么要使用动态链接? 在现代的linux系统中,假设一个普通的程序会使用到c语言静态库至少1MB以上,那么,如果我们的机器运行100个这样的程序,就用浪费近100MB的内存:如果磁盘有2000个这样 ...

  3. C/C++ 静态链接库(.a) 与 动态链接库(.so)

    平时我们写程序都必须 include 很多头文件,因为可以避免重复造轮子,软件大厦可不是单靠一个人就能完成的.但是你是否知道引用的那些头文件中的函数是怎么被执行的呢?这就要牵扯到链接库了! 库有两种, ...

  4. [CSAPP-II] 链接[符号解析和重定位] 静态链接 动态链接 动态链接接口

    1 平台 转http://blog.csdn.net/misskissc/article/details/43063419 1.1 硬件 Table 1. 硬件(lscpu) Architecture ...

  5. 关于C语言静态链接的个人理解,欢迎指正

    摘要:本篇主要介绍在静态链接中多个文件合并.地址确定.符号解析和重定位相关问题,以GCC编译器为例.     首先,链接器链接多个文件时,采用何种方式合并为一个文件?方式一,按序叠加,即多个文件依次叠 ...

  6. GCC编译器原理(三)------编译原理三:编译过程(3)---编译之汇编以及静态链接【2】

    4.1.2 符号解析与重定位 (1)重定位 在完成空间和地址的分配步骤之后,链接器就进入了符号解析和重定位的步骤,这是静态链接的核心部分. 先看看 a.o 的反汇编文件: objdump -d a.o ...

  7. GCC编译过程与动态链接库和静态链接库

    1. 库的介绍 库是写好的现有的,成熟的,可以复用的代码.现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常. 本质上来说库是一种可执行代码的二进制形式,可 ...

  8. Linux 静态链接库和动态连接库

    (0)文件夹 VMware 下安装Ubuntu的吐血经历 零基础学习Shell编程 Linux下的makefile的妙用 Linux调试神器 -- gdb 十分钟学会Python的基本类型 Linux ...

  9. 怎么快速构建自己的C/C++程序?——有关编译、静态链接和SCons

    怎么快速构建自己的C/C++程序?--有关编译.静态链接和SCons 1. 写在前面 最初写C++是在Visual Studio这个IDE里,那时我并没有makefile的概念,对程序的编译和链接的一 ...

随机推荐

  1. / fluxChatDemo / 系列 ——fluxDemoChat 组件编写

    还是用各部分来表示过程吧,没文采,就先这样记着吧 嘻嘻 梳理问题: 编写es6风格的组件时,需要引入import React from ‘react’ 然后页面就华丽丽的展示出了我写的1.2两个字 在 ...

  2. 可变字符串NSMutableString

    //可变字符串继承自字符串 //拼接 NSMutableString *string = [NSMutableString string]; [string appendString:@"今 ...

  3. setTimeout 学习闭包

    @(技术笔记)[css] 学习参考网站 css 网站,可供参考 javascript学习网站 var create = function (i){ return function(){ console ...

  4. Java调用批处理或可执行文件

    import java.io.BufferedReader; import java.io.InputStreamReader; public class Test { public static v ...

  5. Python之路Day19-Django(二)

    本节内容概要: 一.路由系统URL 二.视图 三.模板 四.ORM操作 问题1:Django请求生命周期 -> URL对应关系(匹配) -> 视图函数 -> 返回用户字符串 -> ...

  6. 【http代理报文】通过发包实现代理请求网页内容

    工作中,我们难免需要通过TCP/IP协议发送报文来直接请求网页内容(比如爬虫工具),有同学问如何通过HTTP代理来请求网页,其实我们只需要把报文稍稍修改下,发送给代理服务器即可实现. 基础不过关的朋友 ...

  7. Unity - Apk包的代码与资源提取

    最近在研究如何给Unity游戏进行加密,让别人不能轻易破解你的apk包,不过网上的加密方法都是有对应的破解方法~_~!!结果加密方法没找到好的,逆向工程倒会了不少.今天就来讲解如何提取一个没做任何保护 ...

  8. 《.NET之美》消息及勘误

    <.NET之美>消息及勘误 编辑最终还是采用了<.NET之美>作为书名,尽管我一直觉得这个名字有点文艺了,而更倾向于使用<.NET专题解析>这个名称. 目前已经可以 ...

  9. SQLite vs MySQL vs PostgreSQL:关系型数据库比较

    自1970年埃德加·科德提出关系模型之后,关系型数据库便开始出现,经过了40多年的演化,如今的关系型数据库种类繁多,功能强大,使用广泛.面对如此之多的关系型数据库,我们应该如何权衡找出适合自己应用场景 ...

  10. .NET不可变集合已经正式发布

    微软基础类库(Base Class Library)团队已经完成了.NET不可变集合的正式版本,但不包括ImmutableArray.与其一起发布的还包括针对其它不可变对象类型的设计指南. 如果你需要 ...