一直对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. css实现一行文字居中,多行文字居左

    第一种方法: <style> *{margin:0;padding:0;} .box{width:500px;height:300px;border:1px solid #000;text ...

  2. router路由去掉#!的踩坑记

    项目中在研究去掉router#!的过程中的踩坑过程.

  3. JAVA中使用DOM解析XML文件

    XML是一种方便快捷高效的数据保存传输的格式,在JSON广泛使用之前,XML是服务器和客户端之间数据传输的主要方式.因此,需要使用各种方式,解析服务器传送过来的信息,以供使用者查看. JAVA作为一种 ...

  4. UML类图关系--继承(泛化)、实现、关联、聚合、组合、依赖

    在UML类图中,常见的有以下几种关系:  泛化(Generalization),  实现(Realization),关联(Association),聚合(Aggregation),组合(Composi ...

  5. ELK+Kafka集群日志分析系统

    ELK+Kafka集群分析系统部署 因为是自己本地写好的word文档复制进来的.格式有些出入还望体谅.如有错误请回复.谢谢! 一. 系统介绍 2 二. 版本说明 3 三. 服务部署 3 1) JDK部 ...

  6. bootstrap基本模板

    <!DOCTYPE html><!--html5文档格式--> <html lang="zh-CN"><!--申明语言是中文简体--> ...

  7. 使用JDBC的批处理功能

    package cn.itcast.jdbc; import java.sql.Connection; import java.sql.Date; import java.sql.PreparedSt ...

  8. ABP理论学习之缓存Caching

    返回总目录 本篇目录 介绍 ICacheManager ICache ITypedCache 配置 介绍 ABP提供了缓存的抽象,它内部使用了这个缓存抽象.虽然默认的实现使用了MemoryCache, ...

  9. 辛巴学院-Unity-剑英陪你零基础学c#系列(三)计算与类型

    辛巴学院:正大光明的不务正业. 中秋节快乐,每逢佳节倍思亲,尤其是那素未谋面的老婆,对吧,屌丝们.   今天我们来探索一下C#里面奇怪的计算,奇怪的类型. 奇怪的计算 当我刚刚接触计算机编程的时候,一 ...

  10. Qcon会议之所见所想

    作为普通码农一枚,Qcon是俺参与过的最高级的技术大会了.大会共历时三天,因为俺第二天就得赶火车休个五一大长假,所以只参加了第一天4/25号的会议(其他俩天自然有其他同事会去观摩),不过第一天的会议有 ...