自我修养-动态链接

1.为什么动态链接

  • 静态链接导致同一模块在多个进程中使用,在内存中存在多个副本,造成空间浪费。
  • 静态链接给程序的更新、部署和发布造成麻烦。

动态链接的基本实现

基本思想是把程序按照模块拆分成各个相对独立的部分,在程序运行的时候才将它们链接在一起形成一个完整的程序。

  • Linux系统中,ELF动态链接文件被称为动态共享对象(DSO Dynamic Shared Objects)简称共享对象,一般以.so为拓展名。

  • Windows系统中,动态链接文件被称为动态链接库(Dynamical Linking Library),即DLL文件

  • Linux中,常用的C语言库的运行库glibc,它的动态链接形式的版本保存在/lib目录下,文件名为libc.so。所有的C语言编写的、动态链接程序都可以在运行时使用它。

2.简单的动态链接例子

  1. 创建以下文件
// dlib.c
#include<stdio.h>
void fooBar(int i){
    print("echo from %d\n",i);
}
///test1.c
int main(){
    fooBar(1);
    return 0;
}
  1. 使用GCC将dlib.c编译成一个共享对象文件
    gcc -fPIC -shared -o MyLib.so dlib.c
  2. 然后我们链接test1.c文件
    gcc -o test1 test1.c ./MyLib.so

如果在dlib.c中的fooBar函数中写入sleep(-1);然后
./test1 &
查看PID,接着就可以查看进程的虚拟地址空间分布
cat /proc/12985/maps
kill 12985
我们还可以通过readelf工具查看MyLib.so的装载特性。
readelf -l MyLib.so

  • 有一点和普通程序不一样的地方是:动态链接模块的装载地址是从0x00000000开始的。我们知道这个地址是无效地址,并且从得到的进程的虚拟空间分布看到,MyLib.so的最终装载地址并不是0x00000000.从这一点可以看到,共享对象的最终装载地址在编译时时不确定的

3.地址无关代码技术

装载时重定位

在链接时,对所有绝对地址的引用不作重定位,而把这一步推迟到装载时在完成。一旦模块装载地址确定,即目标地址确定,那么系统就对程序所有的绝对地址引用进程重定位。
静态链接时提到过重定位叫做链接时重定位。现在这种情况被称为装载时重定位,在Windows中,这种装载时重定位被称作基址重置

地址无关代码

装载时重定位时解决动态模块中有绝对地址引用的办法之一,但它有一个很大的缺点就是指令部分无法在多个进程之间共享,这样就失去了动态链接节省内存的一大优势。
目的:程序模块中共享的指令部分在装载时不需要因为装载地址的改变而改变。
基本想法:把指令部分需要被修改的部分分离出来,跟数据部分放一起,这样指令部分就可以保持不变,而数据部分可以在每个进程中拥有一个副本。这种方案被称为地址无关代码(PIC,
Position Independent Code)。
模块中各种地址引用方式可以分为以下几种:

  • 模块内部函数调用、跳转等。
  • 模块内部的数据访问。
  • 模块外部的函数调用、跳转等。
  • 模块外部的数据访问。
类型一 模块内部调用或跳转

模块内部的跳转、函数调用都可以是相对地址调用,或者是基于寄存器的相对调用,所以对于这种指令是不需要重定位的。

类型二 模块内部数据访问

指令中不能直接包含数据的绝对地址,那么唯一的办法就是相对寻址。

类型三 模块间数据访问

模块间的数据访问目标地址要等到装载时才决定。ELF的做法是在数据段里面建立一个指向这些变量的指针数组,也被称为全局偏移表(GlobalOffsetTable,GOT),当代码需要引用该全局变量时,可以通过GOT中相对的项间接引用。

类型四 模块间调用、跳转

GOT中相应的项保存的是目标函数的地址,当模块要调用目标函数时,可以通过GOT中的项进行间接跳转。

如何区分一个DSO是否为PIC

readelf -d MyLib.so |grep TEXTREL

PIE

一个以地址无关方式编译的可执行文件称为地址无关可执行文件(PIE,Position-Independent-Executable).
GCC产生PIE的参数为-fPIE或者-fpie

-fPIC和-fpic

-fPIC和-fpic从功能上讲完全一样,但是-fPIC产生的代码大,但是没有在平台上的限制,而-fpic产生的代码小,而且较快,但是在某些平台上会有限制。所以常用-fPIC

延迟绑定

动态链接有很多优势,比静态链接要灵活,但它牺牲了一部分性能代价。
动态链接比静态链接慢的主要原因是动态里链接对于全局和静态的数据访问都要进行复杂的GOT定位,然后间接寻址;对于模块间的调用也要线定位GOT,然后进行跳转。另一个减慢运行速度的原因是动态链接工作运行时完成,程序开始执行时,动态链接器都要进行一次链接。
在一个程序运行过程中,很多函数在程序执行完时都不会被用到,如果一开始把所有函数都链接好实际是一种浪费。所有ELF采用一种叫做延迟绑定的做法,基本思想:当函数第一次被用到时才进行绑定,如果不用到,则不绑定。
ELF使用PLT(Procedure Linkage Table)的方法来实现。
调用函数并不直接通过GOT跳转,而是通过一个叫做PLT项结构来进行跳转。每个外部函数在PLT中都有一个相对于的项,比如bar()函数在PLT中的项的地址我们称为bar@plt。
bar@plt实现:

bar@plt:

jmp *(bar@GOT)//通过GOT间接跳转的指令.连接器初始化阶段并没有将bar()地址填入该项,而是将下面的代码地址填入
push n//将决议符号下标压入堆栈
push moduleID//将模块ID压入堆栈
jump _dl_runtime_resolve//调用链接器_dl_runtime_resolve函数来完成符号解析和重定位工作
  • ELF将GOT拆分成两张表叫做”.got”和”.got.plt”。
    “.got”用来保存全局变量引用地址
    “.got.plt”用来保存函数引用地址

.got.plt 表中,前三项分别是

  • .dynamic 段的地址
  • 本模块的 ID
  • _dl_runtime_resolve()的地址

与动态链接相关结构

.interp段

  • 动态链接器的位置由ELF可执行文件决定。在动态链接的ELF可执行文件中,有一个专门的段叫做”.interp”段。
  • “.interp”段的内容很简单,里面保存的就是一个字符串,这个字符串就是可执行文件所需要的动态链接器路径(通常实际上是一个软链接)。
    动态链接器在Linux下是Glibc的一部分,也就是属于系统库级别。
  • 可以使用objdump -s a.out查看.interp的内容
  • 还可以使用objdump -l a.out | grep interpreter查看可执行文件所需要的动态链接器的路径。

.dynamic段

  • 是动态链接ELF中最重要的结构,这个段里面保存了动态链接器所有需要的基本信息,比如:依赖与那些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。

  • “.dynamic”段可以看成动态链接下ELF文件的“文件头”。

  • 使用readelf -d MyLib.so可以查看“.dynamic”段的内容

  • 另在Linux还提供了ldd命令还查看一个程序主模块或一个共享库依赖于哪些共享库。

$ ldd program

动态符号表

.dynsym 段

该段与 “.symtab”段类似,但只保存了与动态链接相关的符号,很多时候,ELF文件同时拥有 .symtab 与 .synsym段,其中 .symtab 将包含 .synsym 中的符号。该符号表中记录了动4 .dynstr 段

该段是 .dynsym 段的辅助段,.dynstr 与 .dynsym 的关系,类比与 .symtab 与 .strtab 的关系

.dynstr 段

(动态符号字符串表),用于保存符号名的字符串表。
静态链接时叫做符号字符表(.strtab)。

.hash 段

在动态链接下,需要在程序运行时查找符号,为了加快符号查找过程,增加了辅助的符号哈希表(.hash)

  • 可以使用readelf -sD MyLib.so查看ELF文件的动态符号表和哈希表

动态链接重定位表

共享对象需要重定位的主要原因是导入符号的存在。
在动态链接中,导入符号的地址在运行时才确定,所以需要在运行时将这些导入符号引用修正,即需要重定位。

动态链接重定位相关结构

在动态链接的文件中,也有和静态文件类似的重定位的表,分别叫做”.rel.dyn”和”.rel.plt”

动态链接时进程堆栈初始化信息

当操作系统将控制权交给动态链接器时,它需要知道可执行文件和本进程的一些信息,这些信息由操作系统传递给动态链接器,保存在进程的堆栈里面。
堆栈里面还保存了一些辅助信息数组

操作系统传递给动态链接器的辅助信息有4个:(例)

  • AT_PHDR,值为0x08048034,程序表头位于0x08048034
  • AT_PHENT,值为20,程序表头中每个项的大小为20字节
  • AT_PHNUM,值为7,程序表头共有7个项
  • AT_ENTRY,0x08048320,程序入口地址为0x08048320

动态链接的步骤和实现

动态链接基本上分为3步:先是启动动态链接器本身,然后装载所有需要的共享对象,最后重定位和初始化。

1.动态链接器自举

2.装载共享对象

完成基本自举后,动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表中,我们称为全局符号表。然后链接器开始寻找可执行文件所依赖的共享对象。在”.dynamic”段中,类型入口DT_NEEDED,它所指出的是该可执行文件所依赖的共享对象。链接器可以列出可执行文件所需要的所有共享对象,并将这些共享对象的名字放入一个装载集合中。然后链接器开始从集合里取一个所需要的共享对象名字,找到相对应的文件后打开该文件,读取相应的ELF文件头和”.dynalic”段,然后将它相应的代码段和数据段映射到进程空间。
当一个新的共享对象被装载进来的时候,它的符号表会被合并到全局符号表中,所以当所有共享对象都被装载进来的时候,全局符号表里面将包含进程中所有的动态链接器所需要的符号。

符号的优先级

两个不同模块定义了同一个符号会怎么样?
当一个共享对象里面的全局符号被另一个共享对象的同名全局符号覆盖的现象称为共享对象全局符号介入
全局符号介入这个问题,在Linux下动态链接器是这样处理的:它定义了一个规则,那就是当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号被忽略

重定位和初始化

当上面的步骤完成后,链接器开始重新遍历可执行文件和每个共享对象的重定位表,将它们的GOT/PLT中每个需要重定位的位置进行修正。

重定位完成之后,如果某个共享对象有”.init”段,那么动态链接器会执行”.init”段中代码,用以实现共享对象特有的初始化过程。共享对象中可能还有”.finit”段,当进程退出时,会执行”.finit”段中的代码,可以用来实现类似C++全局对象析构之类的操作。
如果进程的可执行文件也有”.init”段,那么动态链接器不会执行它,因为可执行文件中”.init”段和”.finit”段由程序初始化部分代码负责执行。

Linux动态链接器实现

  • 内核在装载完ELF可执行文件以后就返回到用户空间,将控制权交给程序的入口。
  • 对于动态链接器的可执行文件,内核会分析它的动态链接器地址,将动态链接器映射至进程地址空间,然后把控制权交给动态链接器。
  • Linux动态链接器本身就是一个共享对象。共享对象其实也就是ELF文件,它也有跟可执行文件一样的ELF文件头。动态链接器是个非常特殊的共享对象,它不仅是个共享对象,还是个可执行的程序。
  • Linux的内核在执行execve()时不关心目标ELF文件是否可执行,它只是简单按照程序头表里面的描述对文件进行装载然后把控制权交给ELF入口地址。
  • windows系统中的EXE和DLL也是类似的区别,DLL也可以被当作程序来运行,WINDOWS提供一个叫做rundll32.exe的工具可以把一个DLL当作可执行文件运行。
  • 动态链接器本身应该是静态链接的,它不能依赖于其他共享对象,动态链接器本身是用来帮助其他ELF文件解决共享对象依赖问题的。

显示运行时链接

支持动态链接的系统往往都支持一种更加灵活的模块加载方式,叫做显示运行时链接,有时候也叫做运行时加载
一般的共享对象不需要进行任何修改就可以进行运行时装载,这种共享对象往往叫做动态转载库

动态库实际上跟一般的共享对象没有区别。主要区别是共享对象是由动态链接器在程序启动之前负责装载和链接的,这一系列步骤都由动态连接器自动完成,对于程序是透明的。而动态库的装载则是通过一系列由动态链接器提供的API,具体地讲共有4个函数:打开动态库(dlpen),查找符号(dlsym),错误处理(dlerror),关闭动态库(dlclose),程序可以通过这几个API对动态库进行操作。

dlopen()

void * dlopen(const char *filename,int flag)

  • 第一个参数是被加载动态库的路径
  • 第二个参数flag表示函数符号的加载方式,常量RTLD_LAZY表示使用延迟绑定,即PLT机制;常量RTLD_NOW表示当模块被加载时即完成所有的函数的绑定工作。或者RTLD_GLOBAL可以和前面的两个常量一起用(用或操作),表示将被加载的模块的全局符号合并到进程的全局符号表中。
  • dlopen()函数用来打开一个动态库,并将其加载到进程的地址空间,完成初始化过程。返回被加载模块的举兵,这个句柄在后面使用dlsym()或者dlclose()的时候会用到。如果模块及加载失败,则返回NULL

dlsym()

void * dlsym(void *handle,char *symbol);

  • handle是dlopen返回的句柄
  • symbol是要查找的符号的名字,以\0结尾的C字符串
    dlsym函数基本上是运行时装载的核心部分,可以通过这个函数找到所需要的符号。

dlerror()

每次调用dlopen(),dlsym(),dlclose(),以后,我们都可以调用dlerror()函数来判断上次调用是否成功。

dlclose()

dlclose()的作用跟dlopen()刚好相反,它的作用是将一个已经加载的模块卸载。系统会维持一个加载引用计数器,每次使用dlopen()加载某模块时,相应的的计数器加一;每次使用dlclose()卸载模块时,相应的计算器减一。只有当计数器减到0时,模块才被真正卸载掉。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容

  • 感赏儿子按时回家。 感赏儿子自己去理发,他会注意自己的形象的。 投射孩子早日醒悟,早睡早起,开启自己的正常生活。
    风景_6b35阅读 114评论 0 0
  • 最近好像脾气越来越暴躁,总是莫名其妙对身边的人发脾气,对一些微不足道的小事斤斤计较,可能是空余的时间太多,生活太过...
    北木向南阅读 158评论 0 0
  • 彬换了新手机号,正巧灵和同事正在日本参加时装周,顺手给她的灵哥哥发了条匿名短信:“老板,需要特殊服务吗?” 灵没理...
    汐鹿生银灵阅读 244评论 0 0
  • 2018年8月12号,星期日,晴。 今天算起来,已经是离开家的第三天了,心中有过很多起起伏伏,比如说现在很想吃家里...
    枫郁樰阅读 142评论 0 1
  • 作家李枫微博公开声明曾遭郭敬明骚扰、性侵犯,受害者还有签约到郭敬明公司的男作者及公司男性职员,并呼吁社会各界的网友...
    喵了个说职场阅读 329评论 4 2