ELF动态库加载技术

库用于将相似函数打包在一个单元中。Linux支持两种类型的库:静态库(在编译时静态绑定到程序)和动态库(在运行时绑定到程序)。Linux系统使用的动态库是ELF格式,后缀名为so

1 加载

动态库内部划分为段,段分为不同的类型:

  •  PT_LOAD段:包含代码或数据,是需要被映射到内存中的,每个段有不同的访问权限(读、些、执行);

  • PT_DYNAMIC段:包含动态链接信息,如符号表、重定位表、引用的其他库等。

其他段类型暂不说明。

加载器将库文件第一个PT_LOAD段和最后一个PT_LOAD段之间的内容映射到一段连续的内存地址空间(好处是任意代码和数据的相对地址固定),其首地址称为基地址(如图)。

库的加载只是把文件内容映射到内存地址,但没有真正读取文件数据,在发生内存缺页异常时才由操作系统读入对应的文件数据到内存。延迟读取文件可以加快库的加载速度。

1.1 预链接

一般来说,映射的基地址是不固定的,但如果动态库使用了预链接(prelink技术,则会被映射到预定的地址(保存在文件上)。如果预定的地址范围已经被占用了,则加载失败(Androidlinker是这样,其他加载器可能不同)。Prelink的好处是简化重定位,加快加载速度。

2 重定位

2.1 内部函数和变量

在没有使用prelink的情况下,库的基地址不是固定的(运行时才确定),其全局变量和函数的绝对地址也不是固定的。由于库加载之后任意代码和数据的相对地址是固定的(如前一节所述),因此一些系统(如x86)可以使用相对地址来访问全局变量和函数。ARM系统由于指令长度限制(32位),无法在指令中直接使用大范围的偏移量(但可通过寄存器指定),另外绝对地址在执行效率上要优于相对地址,因此还是需要重定位。

如这个例子:

[cpp] view plaincopyprint?
  1. __attribute__((visibility("hidden")))int errBase = 1;  
  2. void setErr(){ errBase = 0x999; }  
__attribute__((visibility("hidden")))int errBase = 1;
void setErr(){ errBase = 0x999; }

编译得到so,然后反编译(ARM架构):

[plain] view plaincopyprint?
  1. $ gcc -shared-nostdlib -o libtest.so test.c  
  2. $ objdump -dlibtest.so  
  3. 000002c4<setErr>:  
  4. 2c4:  mov    ip,sp  
  5. 2c8:  push   {fp,ip, lr, pc}  
  6. 2cc:  sub    fp,ip, #4  
  7. 2d0:  ldr    r2,[pc, #12]      ; r2 = &errBase  
  8. 2d4:  mov    r3,#2448          ; r3 = 990  
  9. 2d8:  add    r3,r3, #9         ; r3 += 9  
  10. 2dc:  str    r3,[r2]           ; *r2 = r3  
  11. 2e0:  ldm    sp,{fp, sp, pc}  
  12. 2e4:  .word  0x0000109c        ; 这里保存着errBase变量的地址  
$ gcc -shared-nostdlib -o libtest.so test.c
$ objdump -dlibtest.so
000002c4<setErr>:
2c4:  mov    ip,sp
2c8:  push   {fp,ip, lr, pc}
2cc:  sub    fp,ip, #4
2d0:  ldr    r2,[pc, #12]      ; r2 = &errBase
2d4:  mov    r3,#2448          ; r3 = 990
2d8:  add    r3,r3, #9         ; r3 += 9
2dc:  str    r3,[r2]           ; *r2 = r3
2e0:  ldm    sp,{fp, sp, pc}
2e4:  .word  0x0000109c        ; 这里保存着errBase变量的地址

查看重定位表:

[plain] view plaincopyprint?
  1. $ readelf -rlibtest.so  
  2. Relocationsection '.rel.dyn' at offset 0x2bc contains 1 entries:  
  3. Offset    Info       Type            Sym.Value  Sym. Name  
  4. 000002e4  00000017   R_ARM_RELATIVE  
$ readelf -rlibtest.so
Relocationsection '.rel.dyn' at offset 0x2bc contains 1 entries:
Offset    Info       Type            Sym.Value  Sym. Name
000002e4  00000017   R_ARM_RELATIVE

对比汇编代码和重定位表,2e4即是保存errBase变量地址的偏移量。

重定位表中一个RELATIVE类型的表项,指向变量和函数的相对地址,加载器把它加上基地址,使成为绝对地址。如果使用了prelink,则不需要进行重定位。

2.2 外部函数和变量

外部变量和函数是指目标库引用依赖库的变量和函数,需要加载器在依赖库的符号表查找对应的名称和绝对地址,然后写入目标库的全局偏移量表(GlobalOffset Table,简称GOT。目标库通过GOT来访问外部变量和函数。

外部变量重定位对应一个GLOB_DAT类型的表项,外部函数重定位对应一个JMP_SLOT类型表项,表项的值是外部变量或函数的绝对地址,由加载器进行设置。

如这个例子:

[cpp] view plaincopyprint?
  1. extern interrBase;  
  2. void setErr(){ errBase = 0x999; }  
extern interrBase;
void setErr(){ errBase = 0x999; }

编译得到so,然后反编译(ARM架构):

[cpp] view plaincopyprint?
  1. $ gcc -shared-nostdlib -o libtest.so test.c  
  2. $ objdump -dlibtest.so  
  3. 00000218<setErr>:  
  4. 218:  push   {fp}  
  5. 21c:  add    fp,sp, #0  
  6. 220:  ldr    r3,[pc, #28]    ; r3=GOT偏移  
  7. 224:  add    r3,pc, r3       ; r3=GOT地址  
  8. 228:  ldr    r2,[pc, #24]    ; r2=errBase项在GOT的偏移  
  9. 22c:  ldr    r3,[r3, r2]     ; r3=errBase的地址  
  10. 230:  ldr    r2,[pc, #20]    ; r2=0x999  
  11. 234:  str    r2,[r3]         ; r3=r2  
  12. 238:  add    sp,fp, #0  
  13. 23c:  pop    {fp}  
  14. 240:  bx     lr  
  15. 244:  .word  0x00008dc4      ; GOT偏移  
  16. 248:  .word  0x0000000c      ; errBase在GOT的偏移  
  17. 24c:  .word  0x00000999  
$ gcc -shared-nostdlib -o libtest.so test.c
$ objdump -dlibtest.so
00000218<setErr>:
218:  push   {fp}
21c:  add    fp,sp, #0
220:  ldr    r3,[pc, #28]    ; r3=GOT偏移
224:  add    r3,pc, r3       ; r3=GOT地址
228:  ldr    r2,[pc, #24]    ; r2=errBase项在GOT的偏移
22c:  ldr    r3,[r3, r2]     ; r3=errBase的地址
230:  ldr    r2,[pc, #20]    ; r2=0x999
234:  str    r2,[r3]         ; r3=r2
238:  add    sp,fp, #0
23c:  pop    {fp}
240:  bx     lr
244:  .word  0x00008dc4      ; GOT偏移
248:  .word  0x0000000c      ; errBase在GOT的偏移
24c:  .word  0x00000999

查看重定位表:

[cpp] view plaincopyprint?
  1. $readelf -rlibtest.so  
  2. Relocationsection '.rel.dyn' at offset 0x210 contains 1 entries:  
  3. Offset      Info        Type              Sym.Value    Sym.Name  
  4. 00008ffc    00000415    R_ARM_GLOB_DAT    00000000     errBase  
$readelf -rlibtest.so
Relocationsection '.rel.dyn' at offset 0x210 contains 1 entries:
Offset      Info        Type              Sym.Value    Sym.Name
00008ffc    00000415    R_ARM_GLOB_DAT    00000000     errBase

8ffcc正好对应errBaseGOT表项地址。

2.3 延迟绑定

外部函数和变量的重定位需要查找依赖库的符号表,并进行字符串比较,效率较低,不过一般一个库使用的外部变量和函数都不会太多。如果使用了较多的外部函数,为了加快动态库加载速度,可以使用过程链接表(ProcedureLinkageTable,简称PLT,把外部函数的定位延迟到第一次调用的时候(称为延迟绑定)。函数延迟绑定需要编译器对函数调用生成额外的代码,主要由编译器实现。

看这个例子:

[cpp] view plaincopyprint?
  1. voidprintf1(const char*, ...);  
  2. void setErr(){ printf1("setErr\n"); }  
voidprintf1(const char*, ...);
void setErr(){ printf1("setErr\n"); }

对应汇编代码(x86-64):

[html] view plaincopyprint?
  1. 4c0<printf1@plt>:  
  2. 4c0:  jmpq   *0x200b3a(%rip)  
  3. 4c6:  pushq  $0x0  
  4. 4cb:  jmpq   4b0 <_init+0x18>  
  5.   
  6. 5ac <setErr>:  
  7. 5ac:  push   %rbp  
  8. 5ad:  mov    %rsp,%rbp  
  9. 5b0:  lea    0x5f(%rip),%rdi  
  10. 5b7:  mov    $0x0,%eax  
  11. 5bc:  callq  4c0  
  12. 5c1:  pop    %rbp  
  13. 5c2:  retq  
4c0<printf1@plt>:
4c0:  jmpq   *0x200b3a(%rip)
4c6:  pushq  $0x0
4cb:  jmpq   4b0 <_init+0x18>
5ac <setErr>:
5ac:  push   %rbp
5ad:  mov    %rsp,%rbp
5b0:  lea    0x5f(%rip),%rdi
5b7:  mov    $0x0,%eax
5bc:  callq  4c0
5c1:  pop    %rbp
5c2:  retq

调用printf1会调用printf1@plt,然后跳转到*0x200b3a(%rip),即*(基地址+0x201000)。

如果是第一次执行,*0x(基地址+0x201000)的值是(基地址+4c6),后面的代码会进行函数绑定,对应的重定位项是:

[plain] view plaincopyprint?
  1. $ readelf -rlibtest.so  
  2. Relocationsection '.rela.plt' at offset 0x468 contains 2 entries:  
  3. 201000 000300000007 R_X86_64_JUMP_SLO  printf1 + 0  
$ readelf -rlibtest.so
Relocationsection '.rela.plt' at offset 0x468 contains 2 entries:
201000 000300000007 R_X86_64_JUMP_SLO  printf1 + 0

绑定之后*0x(基地址+0x201000)会对应printf1函数的地址,下次再进入printf1@plt,就可以直接跳转到printf1函数了。

2.4 位置无关代码

一般来说,程序和动态库的代码和只读数据被加载到内存之后,可以被多个进程共享,但被写的脏数据则不能被多个进程共享。RELATIVE类型的重定位会修改代码段的变量地址,导致代码段被污染,从而不能被多个进程共享。为了让动态库的代码段可以在进程间共享,可以让编译器编译出位置无关代码(简称PIC,通过GOT来访问变量和函数。

PIC使代码段可在进程间共享,从而节省了内存,但是通过GOT表来访问变量和函数会比相对定位慢一点,如果没有需要则可以不使用PIC

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/435339.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

geoserver安装(war安装+exe安装)

下载 官网&#xff1a;http://geoserver.org/ 方法一 当前只有war版本的 下载结果 tomcat安装最好不要使用exe版本的&#xff0c;因为会出现问题&#xff0c;页面加载不出来&#xff0c;所以大家只需要下载下来解压即可 然后将geoserver安装包中的war文件放到webappx下 然后…

LSGO软件技术团队2015~2016学年第十六周(1214~1220)总结

团队简述&#xff1a; LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为大数据处理与机器学习。成立几年来为学校…

与大家分享一下2010我的找工作历程!真累啊!不过都已经结束了!

经常看到网上别人把找工作的过程与别人分享&#xff0c;这次俺也写下我的一点经历来与大家分享。 大三结束就准备找工作了&#xff0c;因为感觉在学校待着实在没意思&#xff0c;还是出去混混吧。 记得在暑假&#xff0c;想找份实习的&#xff0c;投了些简历&#xff0c;也不知…

LSGO软件技术团队2015~2016学年第十七周(1221~1227)总结

团队简述&#xff1a; LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为大数据处理与机器学习。成立几年来为学校…

postgresql+postgis安装

下载 官网https://www.postgresql.org/ 作者的电脑为win 下载结果为 postgis要选择下载对应版本的 官网http://www.postgis.org/ 以下安装最好装到除c盘之外的&#xff0c;否则有些安装需要权限&#xff0c;一般为d盘 用户名和密码都设置为postgres postgis安装教程 之前设置过…

话说Python:非主流编程语言

【编者按】Python编程语言广受开发者的喜爱&#xff0c;并被列入LAMP (Linux, Apache, MySQL 以及Python/Perl/PHP)中。尽管它在一段时期曾引领了动态语言&#xff0c;但这门颇受好评的编程语言却又从未大红大紫过。是什么让它在开发领域尤其是Web应用开发方面如此受宠呢&#…

把共享库(SO)加载到指定的内存地址

一位朋友最近遇到一个棘手的问题&#xff0c;希望把共享库(SO)加载到指定的内存地址&#xff0c;目的可能是想通过prelink来加快应用程序的起动速度。他问我有没有什么方法。我知道Windows下是可以的&#xff0c;比如在VC6里设置/base的值就行了&#xff0c;所以相信在linux下也…

aop实现原理_SpringAOP原理分析

目录Spring核心知识SpringAOP原理AOP编程技术什么是AOP编程AOP底层实现原理AOP编程使用Spring核心知识Spring是一个开源框架&#xff0c;Spring是于2003年兴起的一个轻量级的Java开发框架&#xff0c;由Rod Johnson在其著作Expert One-On-One J2EE Development and Design中阐述…

欧盟无条件批准甲骨文收购Sun

北京时间1月21日消息&#xff0c;据国外媒体报道&#xff0c;甲骨文以70亿美元的价格收购Sun的交易今天获得了欧盟无条件批准。 这笔交易将改变科技产业的格局&#xff0c;这意味着全球第二大商用软件提供商甲骨文进入了硬件产业。在规模达170亿美元的高端计算机服务器市场&…

如何高效的访问内存

影响内存访问速度的因素主要有&#xff1a; 1.内存带宽&#xff1a;每秒读写内存的数据量&#xff0c;由硬件配置决定。 2.CACHE高速缓冲&#xff1a;CPU与内存之间的缓冲器&#xff0c;当命中率比较高时能大大提供内存平均访问速度。 3.TLB转换旁视缓冲&#xff1a;系统虚拟地…

初学者怎样看懂python代码_入门编程(初学者怎样看懂代码)

你既然喜欢编程&#xff0c;就应该认认真真的学习一门语言&#xff0c;学习微软的就先从vb开始&#xff0c;vb是比较好的入门语言&#xff0c;可视化的&#xff0c;比较简单&#xff0c;是非常好的入门语言。书籍最少应该准备两. 先认认真真的学习一门语言&#xff0c;学习微软…

MAVEN安装和配置

maven官网下载 https://maven.apache.org/download.cgi

MIPS架构的医院智能导诊系统设计

摘要&#xff1a;通过研究基于MIPS架构的SMP8654芯片的硬件架构&#xff0c;并且利用芯片内部的图形加速引擎GFX的方式实现了具有高清视频显示和图片文字处理功能的播放器。系统以嵌入式Linux和MiniGUI为平台设计了智能导诊系统&#xff0c;提高了医院的导诊就医的服务效率。智…

C、CPP const 详解

1.const修饰变量一般有两种写法&#xff1a; constTYPE value;TYPE constvalue;这两种写法在本质上是一样的。它的含义是&#xff1a;const修饰的类型为TYPE的变量value是不可变的。对于一个非指针的类型TYPE&#xff0c;无论怎么写&#xff0c;都是一个含义&#xff0c;即valu…

arcgis在线地图插件安装

软件下载链接 https://download.csdn.net/download/qq_39397927/15761863

hadoop namenode启动不了_集群版hadoop安装,写给大忙人看的

导语 如果之前的单机版hadoop环境安装满足不了你&#xff0c;集群版hadoop一定合你胃口&#xff0c;轻松入手。目录 集群规划前置条件配置免密登录 3.1 生成密匙 3.2 免密登录 3.3 验证免密登录集群搭建 4.1 下载并解压 4.2 配置环境变量 4.4 修改配置 4.4 分发程序 4.5 初始化…

patch文件制作

一、为单个文件打补丁1、首先我用的ubuntu12 os&#xff0c;cat >>test0<<eof但是这命令执行得是root身份more命令功能&#xff1a;让画面在显示满一页时暂停&#xff0c;此时可按空格健继续显示下一个画面&#xff0c;或按Q键停止显示。more test0:查看test0内容2…

[转]百万级访问网站前期的技术准备

作者&#xff1a;一路凯歌来源&#xff1a;http://zhiyi.us/ 开了自己域名的博客&#xff0c;第一篇就得来个重磅一点的才对得起这4美金的域名。作为一个技术从业者十年&#xff0c;逛了十年发现有些知识东一榔头西一棒槌的得满世界 看个遍才整理出个头绪&#xff0c;那咱就系统…