共享库的一个关键目的是为了使多个进程能够共享内存中的同一份代码拷贝,已达到节约内存资源的目的。如何做到呢?一种方法是预先为每一个共享库指定好加载的地址范围,然后要求加载器总是将共享库加载至指定的位置。这种方法尽管很简单,但是会产生一些严重的问题。因为就算一个进程并没有用到某个库,相应的地址范围依然会被保留下来,这是一种效率很低的内存使用方式。另外,这种方法管理起来也很困难。我们必须保证预留的地址块之间没有重叠。每当一个库被修改后,我们还必须要保证它能被放回到修改前的位置,否则,我们还要为它重新找一个新的位置。当我们创建一个新的库时,我们还要为它寻找合适的空间,地址空间碎片化造成的大量无用的内存空洞。更糟糕的是,不同的系统为动态库分配内存的方式不尽相同,这使得管理起来更为困难。
一个更好的方法是将动态库编译成可以在任意位置加载而无需链接器进行修改。这样的代码被称作位置无关代码(PIC)。GNU编译系统可以通过指定-fPIC选项来生成PIC代码。
在IA32系统中,同一个模块中的过程调用无需特殊处理就是PIC的,因为其引用相对于PC地址的偏移量是已知的。但是,对外部过程的调用和对全局变量的引用一般却不是PIC的,因此需要在链接的时候进行重定位。

PIC数据引用
编译器对全局变量生成PIC引用是基于下面这个有趣的事实:无论目标模块(包括共享目标模块)被加载到内存中的什么位置,数据段总是紧跟着地址段的。因此,代码段中的任意指令与数据段中的任意变量之间的距离在运行时都是一个常量,而与代码和数据加载的绝对内存位置无关。
为了利用这一特点,编译器在数据段的开头创建了一个全局偏移表(GOT)。在GOT中,目标模块所引用的每个全局数据对象都对应一个表项。编译器同时为GOT中的每个表项生成了一个重定位记录。在加载时,动态链接器重定位GOT中的每个表项,使其包含正确的绝对地址。每个包含全局数据引用的目标模块都有其自己的GOT。
在运行时,每个全局变量通过GOT被间接引用,如下列代码所示:


call L1
L1: popl %ebx            ebx包含着当前的PCaddl $VAROFF, %ebx   ebx指向var所对应的GOT表项movl (%ebx), %eax    通过GOT间接引用movl (%eax), %eax

在这段代码中,对L1的调用将返回地址(也就是popl指令所对应的地址)压入堆栈。接着popl指令将其弹出至%ebx。这两条指令的效果就相当于把PC值加载至寄存器%ebx。
addl指令将%ebx加上一个常数偏移量,使其指向对应的GOT表项,其中包含着引用数据的绝对地址。此时,全局变量可以通过包含在%ebx中的GOT表项被间接引用。在这个例子中,两条movl指令将全局变量的内容通过GOT间接地加载进寄存器%eax。
PIC代码具有性能上的缺陷。现在每次全局变量引用都需要5条指令而不是1条,同时GOT还需要占用额外的内存空间。并且,PIC代码需要使用额外的寄存器来保存GOT表项的地址。在寄存器较多的机器上,这不是什么大问题。但是在寄存器较少的IA32系统中,缺少哪怕一个寄存器都可能会触发将寄存器内容暂存在堆栈里。

PIC函数调用
可以使用相同的方法实现对外部函数调用的PIC代码:


call L1
L1:     popl %ebx       ebx保存当前的PC值addl $PROCOFF, %ebx  ebx指向调用函数的GOT表项地址call *(%ebx)       通过GOT间接调用

但是这种方法对每次调用都需要3条额外的指令。于是,ELF编译系统使用了一种叫做延迟绑定(lazy binding)的技术将函数地址的绑定推迟到函数被首次调用时。这样,仅在第一次调用是会产生额外的时间开销,但在后面的调用中仅仅消耗一条额外指令和内存引用。
延迟绑定需要在两个数据结构之间进行密集而复杂的交互:GOT和过程连接表(procedure linkage table, PLT)。如果一个目标模块调用了共享库中的任意函数,那么它就有它自己的GOT和PLT。GOT是.data段的一部分。PLT是.text段的一部分。
GOT的前3项包含了一些特殊的值:GOT[0]中保存的是.dynamic段的地址,其中保存着动态链接器用来绑定过程地址所需要的信息,包括符号表的位置和重定位信息。GOT[1]中保存着当前模块的一些信息。GOT[2]是动态链接器延迟绑定代码的入口地址。每个被调用的动态对象中的函数在GOT中都有一个对应的表项,从GOT[3]开始。比如,定义在libc.so的printf,以及定义在libvector.so的addvec。
下面的代码展示了PLT的内容。PLT是16字节表项构成的数组。第一项PLT[0]是一个特殊项,为跳到动态链接器的入口。从PLT[1]开始,每一个被调用的函数在PLT中都有对应项,其中,PLT[1]对应printf,PLT[2]对应addvec.

PLT[0]
08048444:   pushl   $GOT[1]jmp to   *GOT[2](linker)  paddingpaddingPLT[1] <printf>
8048454:    jmp to  *GOT[3]pushl   $0x0     ID for  printfjmp to    PLT[0]PLT[2] <addvec>
8048464:    jmp to *GOT[4]pushl   $0x8  ID for  addvecjmp to    PLT[0]<other PLT entries>

在程序被动态链接之后并开始执行时,函数printf和addvec被绑定到相应PLT项的第一条指令,比如,对addvec的调用为:

call 8048464 <addvec>

当addvec第一次被调用时,PLT[2]的第一条指令被执行,即间接跳转到GOT[4]保存的地址。GOT表项的初始值为PLT表项中的pushl指令的地址。因此,跳转后的执行指令重新回到了PLT表项的第二条指令。这条指令将addvec符号的ID压入栈中。然后下一条指令跳转到PLT[0],将另一个存放于GOT[1]中的标识信息字压入栈,然后通过GOT[2]间接跳转到动态链接器。动态链接器通过栈中的这两项数据来定位addvec,并用其覆盖GOT[4]的值,然后将控制权交给addvec。

当下次addvec被调用时,同之前一样,先是执行PLT[2]的第一条指令。但是这次将通过GOT[2]直接跳转到addvec。所增加的开销只是间接跳转所需的内存引用。

翻译自《深入理解计算机系统》第七章

位置无关(PIC)代码原理剖析相关推荐

  1. linux在当前目录下创建pic目录,在Linux上创建.SO文件而不使用PIC(与位置无关的代码)(x86 32位)...

    据我所知,x86汇编代码在很大程度上受寄存器数量的限制. 当我了解到在Linux上要创建一个.so文件时,必须为gcc指定-fPIC命令行参数才能创建与位置无关的代码,我不敢首先相信它. 据我所知,e ...

  2. 位置无关代码(PIC)的思考

    应用程序必须经过编译.汇编和链接后才变成可执行文件,在链接时,要对所有目标文件进行重定位(relocation),建立符号引用规则,同时为变量.函数等分配运行地址.当程序执行时,系统必须把代码加载到链 ...

  3. ARM 位置无关代码(PIC)的分析理解

    2019独角兽企业重金招聘Python工程师标准>>> PIC的特点是: 它被加载到任意地址空间都可以正确的执行.其原理是PIC对常量和函数入口地址的操作都是基于PC+偏移量的寻址方 ...

  4. 共享库中的位置无关代码(PIC)

    原作者:Eli Bendersky http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-li ...

  5. 计算机科学基础知识(四): 动态库和位置无关代码

    一.前言 本文主要描述了动态库以及和动态库有紧密联系的位置无关代码的相关资讯.首先介绍了动态库和位置无关代码的源由,了解这些背景知识有助于理解和学习动态库.随后,我们通过加-fPIC和不加这个编译选项 ...

  6. Linux位置无关代码实现,浅谈位置无关代码

    原标题:浅谈位置无关代码 引言 最近参与的一个项目涉及到了二进制重写相关的问题,也因此看了几篇相关工具的论文.与之前曾经一直想做的动态装载有不少重合,因此在此做一个整理. 本文主要整理了动态库装载地址 ...

  7. 彻底搞透视觉三维重建:原理剖析、代码讲解、及优化改进

    视觉三维重建 = 定位定姿 + 稠密重建 + surface reconstruction +纹理贴图.三维重建技术是计算机视觉的重要技术之一,基于视觉的三维重建技术通过深度数据获取.预处理.点云配准 ...

  8. 对于位置无关代码的理解

    这个概念其实很好理解.对于一个可重定位目标文件,里面一定有很多需要重定位的符号.这些符号需要在静态链接器进行链接的时候回填绝对的虚拟内存地址.这种叫做位置有关代码. 而位置无关代码的意思是,在动态链接 ...

  9. 【时间序列模型】AR模型(原理剖析+MATLAB代码)

    目录 一.AR模型的原理剖析 1.1 AR模型原理 1.2 模型的参数估计 1.3 模型的定阶方法 (1)AIC (2)BIC 二.MATLAB代码 三.AR相关论文 参考文献: 前言 时间序列分析方 ...

最新文章

  1. 使用TestStack.White进行Windows UI的自动化测试 (1) 基础篇
  2. SAP MM ME21N 创建委外采购PO报错 - Not possible to determine any components - 之对策
  3. 流量暴涨擒凶记(转)
  4. 魏代汉,晋代魏,背后是士族与寒族的博弈,也是士族门阀的形成时期
  5. 【NOI2012】迷失游乐园【概率期望】【换根dp】【基环树】
  6. 计算机的复数英语怎么读,英语的复数怎么读
  7. EXT Grid celleditor列编辑,动态控制某一单元格只读
  8. python属性和方法的区别_Python中几种属性访问的区别与用法详解
  9. openJDK之如何下载各个版本的openJDK源码
  10. 如何将SQL Profiler Trace读入到SQL的表中?
  11. python批量删除文件名_用python批量删掉文件名中共同存在的字符
  12. 论文翻译 SLAM综述
  13. 使用Idea进行Junit单元测试
  14. QNX系统MfgTool烧写工具脚本说明
  15. 国内使用bing国际版(非国内国际切换版本)
  16. PSD格式截图软件 ScreenToLayers 1.2.3中文版
  17. 什么情况下,微信聊天里会出现【对方正在输入】,原来如此
  18. 血型(输血-受血)匹配数电设计
  19. linux内核源码 github,GitHub - groot2013/Linux-0.11code: 这是Linux0.11内核源代码
  20. 电梯门禁系统服务器一般在哪,电梯控制系统与门禁系统的区别

热门文章

  1. matlab-错误 428: 您的许可证需要进行电子邮件验证。有关说明,请在您的收件箱中查看 MathWorks 发送的电子邮件。如需其他帮助。
  2. 说说background-size:cover;与background-size:contain的区别
  3. MBTI测试结果:您的性格类型倾向为“ ENFP ”
  4. 问题七:vue+ts The left-hand side of an assignment expression may not be an optional property?
  5. annotations are not allowed here
  6. QT5的程序打包发布(Windows)
  7. 大公司和创业公司怎么选?
  8. kmeans聚类算法matlab实现
  9. 二哥,你为什么要写作啊?
  10. 网络协议-前端重点——DNS和CDN