一直对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. 为什么要用elasticsearch-理解加深中

    首先的概念 基于Lucene 分布式实时文件存储 实时的分析搜索引擎 能达到实时搜索 优势的地方 1.横向可扩展性:只需要增加一台服务器,做一点儿配置,启动一下ES进程就可以并入集群: 2.分片机制提 ...

  2. Centos 7 安装 设置 IP地址,DNS,主机名,防火墙,端口,SELinux (实测+笔记)

    环境: 系统硬件:vmware vsphere (CPU:2*4核,内存2G,双网卡) 系统版本:CentOS-7.0-1406-x86_64-DVD.iso 安装步骤: 1.虚拟系统安装 1.1 使 ...

  3. 日常工作bug总结

    1.在使用jQuery的text()与val()使用时,如果按钮是<button></button>的形式使用text()写入时,在IE8下会出现延迟,即我在写定时器5秒倒计时 ...

  4. [LeetCode] All solution

    比较全的leetcode答案集合: kamyu104/LeetCode grandyang

  5. dot

    今天写程序的时候发现一个问题啊 在主函数里面吧某个指针node* r=NULL 赋值为空 然后调用函数insert(node* r,....) 在insert里面呢,我给这个指针赋值了啊 但是主函数里 ...

  6. dom中一些节点获取和增改

    1获取标签里的文本对象: 对象.innerText 获取标签里的文本内容     早期的火狐浏览器中是不支持的 赋值会输出转义后的内容 对象.innerHTML 获取标签里的所有内容 赋值会输出原样 ...

  7. 发现meta有个刷新页面的办法。

    meta是html中不可缺少的一个标签,它的应用以方便浏览器搜索并分类当前网页的内容. meta总是放在head标签的第一个位置.今天我在复习前端知识的时候,在网上发现了用meta刷新网页的好办法. ...

  8. io.js入门(一)—— 初识io.js

    io.js可以说是彻底从NodeJS里分离出来的一条分支,其事情始末可以查看这篇报道,此处便也不赘言.既然是分支,io.js便也基本兼容NodeJS的各种API,连执行指令也依旧兼容Node的 nod ...

  9. 软件工程(QLGY2015)博客点评总结

    目录 第一次作业(2015.5.9) 第二次作业(2015.5.21) 第三次作业(2015.6.11) 2015上半年软工助教总结 第一次作业(2015.5.9) 存在主要问题 1)书写这种练习博客 ...

  10. VBA批量查找和复制文件

    Function findAndCopy(srcFile As String, destFile As String, cmdFile As String) Dim WSH As Object, wE ...