【编译、链接、装载十二】动态链接2
- 四、延迟绑定(PLT)
- 五、动态链接相关结构
- 1 “.interp”段
- 2 “.dynamic”段
- 3 .动态符号表——dynsym、动态符号字符串表——.dynstr
- 4、动态链接重定位表
- 六、动态链接的步骤和实现
- 1、动态链接器自举
- 2、装载共享对象
- 3、重定位和初始化
- 七、显式运行时链接
- 1、dlopen()
- 2、 dlsym()
- 3、dlerror()
- 4、dlclose()
- 5、demo1
- 6、demo2
四、延迟绑定(PLT)
动态链接的确有很多优势, 比静态链接要灵活得多, 但它是以牺牲一部分性能为代价的。 据统计ELF程序在静态链接下要比动态库稍微快点,大约为1%~5%, 当然这取决于程序本身的特性及运行环境等。 我们知道动态链接比静态链接慢的主要原因是动态链接下对于全局和静态的数据访问都要进行复杂的GOT定位, 然后间接寻址; 对于模块间的调用要先定位GOT, 然后再进行间接跳转, 如此一来, 程序的运行速度必定会减慢。 另外一个减慢运行速度的原因是动态链接的链接工作在运行时完成, 即程序开始执行时, 动态链接器都要进行一次链接工作, 正如我们上面提到的, 动态链接器会寻找并装载所需要的共享对象, 然后进行符号查找地址重定位等工作, 这些工作势必减慢程序的启动速度。 这是影响动态链接性能的两个主要问题, 我们将在这一节介绍优化动态链接性能的一些方法。
ELF采用了一种叫做延迟绑定(Lazy Binding) 的做法, 基本的思想就是当函数第一次被用到时才进行绑定(符号查找、 重定位等) , 如果没有用到则不进行绑定。
ELF使用PLT(Procedure Linkage Table) 的方法来实现, PLT为了实现延迟绑定, 在这个过程中间又增加了一层间接跳转。 调用函数并不直接通过GOT跳转, 而是通过一个叫作PLT项的结构来进行跳转。 每个外部函数在PLT中都有一个相应的项, 比如bar()函数在PLT中的项的地址我们称之为bar@plt。 让我们来看看bar@plt的实现:
00000000000005f0 <bar@plt>:5f0: ff 25 22 0a 20 00 jmpq *0x200a22(%rip) # 201018 <bar@@Base+0x200903>5f6: 68 00 00 00 00 pushq $0x05fb: e9 e0 ff ff ff jmpq 5e0 <.plt>
PLT在ELF文件中以独立的段存放, 段名通常叫做“.plt”, 因为它本身是一些地址无关的代码, 所以可以跟代码段等一起合并成同一个可读可执行的“Segment”被装载入内存。
五、动态链接相关结构
在动态链接情况下, 操作系统还不能在装载完可执行文件之后就把控制权交给可执行文件, 因为我们知道可执行文件依赖于很多共享对象。 这时候, 可执行文件里对于很多外部符号的引用还处于无效地址的状态, 即还没有跟相应的共享对象中的实际位置链接起来。 所以在映射完可执行文件之后, 操作系统会先启动一个动态链接器(Dynamic Linker) 。
在Linux下, 动态链接器ld.so实际上是一个共享对象, 操作系统同样通过映射的方式将它加载到进程的地址空间中。 操作系统在加载完动态链接器之后, 就将控制权交给动态链接器的入口地址(与可执行文件一样, 共享对象也有入口地址) 。 当动态链接器得到控制权之后, 它开始执行一系列自身的初始化操作, 然后根据当前的环境参数, 开始对可执行文件进行动态链接工作。 当所有动态链接工作完成以后, 动态链接器会将控制权转交到可执行文件的入口地址, 程序开始正式执行。
#include<stdio.h>
int main()
{printf("hello world\n");
}
1 “.interp”段
那么系统中哪个才是动态链接器呢, 它的位置由谁决定? 是不是所有的*NIX系统的动态链接器都位于/lib/ld.so呢? 实际上, 动态链接器的位置既不是由系统配置指定, 也不是由环境参数决定, 而是由ELF可执行文件决定。 在动态链接的ELF可执行文件中, 有一个专门的段叫做“.interp”段(“interp”是“interpreter”(解释器) 的缩写) 。 如果我们使用objdump工具来查看, 可以看到“.interp”内容
[dev1@localhost test06]$ gcc hello.c -o hello -fPIE
[dev1@localhost test06]$ ./hello
hello world
[dev1@localhost test06]$ objdump -s hellohello: 文件格式 elf64-x86-64Contents of section .interp:400238 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux-400248 7838362d 36342e73 6f2e3200 x86-64.so.2.
“.interp”的内容很简单, 里面保存的就是一个字符串, 这个字符串就是可执行文件所需要的动态链接器的路径, 在Linux下, 可执行文件所需要的动态链接器的路径几乎都是“/lib64/ld-linux-x86-64.so.2”.
2 “.dynamic”段
动态链接ELF中最重要的结构应该是“.dynamic”段, 这个段里面保存了动态链接器所需要的基本信息, 比如依赖于哪些共享对象、 动态链接符号表的位置、 动态链接重定位表的位置、 共享对象初始化代码的地址等。
使用readelf工具可以查看“.dynamic”段的内容
[dev1@localhost test06]$ readelf -d helloDynamic section at offset 0xe28 contains 24 entries:标记 类型 名称/值0x0000000000000001 (NEEDED) 共享库:[libc.so.6]0x000000000000000c (INIT) 0x4003e00x000000000000000d (FINI) 0x4005b40x0000000000000019 (INIT_ARRAY) 0x600e100x000000000000001b (INIT_ARRAYSZ) 8 (bytes)0x000000000000001a (FINI_ARRAY) 0x600e180x000000000000001c (FINI_ARRAYSZ) 8 (bytes)0x000000006ffffef5 (GNU_HASH) 0x4002980x0000000000000005 (STRTAB) 0x4003180x0000000000000006 (SYMTAB) 0x4002b80x000000000000000a (STRSZ) 61 (bytes)0x000000000000000b (SYMENT) 24 (bytes)0x0000000000000015 (DEBUG) 0x00x0000000000000003 (PLTGOT) 0x6010000x0000000000000002 (PLTRELSZ) 72 (bytes)0x0000000000000014 (PLTREL) RELA0x0000000000000017 (JMPREL) 0x4003980x0000000000000007 (RELA) 0x4003800x0000000000000008 (RELASZ) 24 (bytes)0x0000000000000009 (RELAENT) 24 (bytes)0x000000006ffffffe (VERNEED) 0x4003600x000000006fffffff (VERNEEDNUM) 10x000000006ffffff0 (VERSYM) 0x4003560x0000000000000000 (NULL) 0x0
[dev1@localhost test06]$
每个含义可以查看下面的表格
3 .动态符号表——dynsym、动态符号字符串表——.dynstr
为了表示动态链接这些模块之间的符号导入导出关系, ELF专门有一个叫做动态符号表( Dynamic Symbol Table) 的段用来保存这些信息, 这个段的段名通常叫做“.dynsym”( Dynamic Symbol) 。 与“.symtab”不同的是, “.dynsym”只保存了与动态链接相关的符号, 对于那些模块内部的符号, 比如模块私有变量则不保存。 很多时候动态链接的模块同时拥有“.dynsym”和“.symtab”两个表, “.symtab”中往往保存了所有符号, 包括“.dynsym”中的符号。
Symbol table '.dynsym' contains 15 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab2: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND b3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__4: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND ext5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable7: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)8: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 21 _edata9: 0000000000000715 29 FUNC GLOBAL DEFAULT 11 bar10: 0000000000000732 26 FUNC GLOBAL DEFAULT 11 foo11: 0000000000201040 0 NOTYPE GLOBAL DEFAULT 22 _end12: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 22 __bss_start13: 00000000000005c0 0 FUNC GLOBAL DEFAULT 9 _init14: 000000000000074c 0 FUNC GLOBAL DEFAULT 12 _finiSymbol table '.symtab' contains 58 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000000001c8 0 SECTION LOCAL DEFAULT 1 2: 00000000000001f0 0 SECTION LOCAL DEFAULT 2 3: 0000000000000230 0 SECTION LOCAL DEFAULT 3 4: 0000000000000398 0 SECTION LOCAL DEFAULT 4 5: 000000000000044a 0 SECTION LOCAL DEFAULT 5 6: 0000000000000468 0 SECTION LOCAL DEFAULT 6 7: 0000000000000488 0 SECTION LOCAL DEFAULT 7 8: 0000000000000560 0 SECTION LOCAL DEFAULT 8 9: 00000000000005c0 0 SECTION LOCAL DEFAULT 9 10: 00000000000005e0 0 SECTION LOCAL DEFAULT 10 11: 0000000000000630 0 SECTION LOCAL DEFAULT 11 12: 000000000000074c 0 SECTION LOCAL DEFAULT 12 13: 0000000000000758 0 SECTION LOCAL DEFAULT 13 14: 0000000000000780 0 SECTION LOCAL DEFAULT 14 15: 0000000000200df0 0 SECTION LOCAL DEFAULT 15 16: 0000000000200df8 0 SECTION LOCAL DEFAULT 16 17: 0000000000200e00 0 SECTION LOCAL DEFAULT 17 18: 0000000000200e08 0 SECTION LOCAL DEFAULT 18 19: 0000000000200e10 0 SECTION LOCAL DEFAULT 19 20: 0000000000200fd0 0 SECTION LOCAL DEFAULT 20 21: 0000000000201000 0 SECTION LOCAL DEFAULT 21 22: 0000000000201038 0 SECTION LOCAL DEFAULT 22 23: 0000000000000000 0 SECTION LOCAL DEFAULT 23 24: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c25: 0000000000200e00 0 OBJECT LOCAL DEFAULT 17 __JCR_LIST__26: 0000000000000630 0 FUNC LOCAL DEFAULT 11 deregister_tm_clones27: 0000000000000660 0 FUNC LOCAL DEFAULT 11 register_tm_clones28: 00000000000006a0 0 FUNC LOCAL DEFAULT 11 __do_global_dtors_aux29: 0000000000201038 1 OBJECT LOCAL DEFAULT 22 completed.635530: 0000000000200df8 0 OBJECT LOCAL DEFAULT 16 __do_global_dtors_aux_fin31: 00000000000006e0 0 FUNC LOCAL DEFAULT 11 frame_dummy32: 0000000000200df0 0 OBJECT LOCAL DEFAULT 15 __frame_dummy_init_array_33: 0000000000000000 0 FILE LOCAL DEFAULT ABS pic.c34: 000000000020103c 4 OBJECT LOCAL DEFAULT 22 a35: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c36: 0000000000000800 0 OBJECT LOCAL DEFAULT 14 __FRAME_END__37: 0000000000200e00 0 OBJECT LOCAL DEFAULT 17 __JCR_END__38: 0000000000000000 0 FILE LOCAL DEFAULT ABS 39: 0000000000200e08 0 OBJECT LOCAL DEFAULT 18 __dso_handle40: 0000000000200e10 0 OBJECT LOCAL DEFAULT 19 _DYNAMIC41: 0000000000000758 0 NOTYPE LOCAL DEFAULT 13 __GNU_EH_FRAME_HDR42: 0000000000201038 0 OBJECT LOCAL DEFAULT 21 __TMC_END__43: 0000000000201000 0 OBJECT LOCAL DEFAULT 21 _GLOBAL_OFFSET_TABLE_44: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab45: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND b46: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 21 _edata47: 0000000000000715 29 FUNC GLOBAL DEFAULT 11 bar48: 000000000000074c 0 FUNC GLOBAL DEFAULT 12 _fini49: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__50: 0000000000000732 26 FUNC GLOBAL DEFAULT 11 foo51: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND ext52: 0000000000201040 0 NOTYPE GLOBAL DEFAULT 22 _end53: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 22 __bss_start54: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses55: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable56: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2.257: 00000000000005c0 0 FUNC GLOBAL DEFAULT 9 _init
[dev1@localhost test05]$ readelf -sD pic.soSymbol table of `.gnu.hash' for image:Num Buc: Value Size Type Bind Vis Ndx Name8 0: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 21 _edata9 0: 0000000000000715 29 FUNC GLOBAL DEFAULT 11 bar10 0: 0000000000000732 26 FUNC GLOBAL DEFAULT 11 foo11 0: 0000000000201040 0 NOTYPE GLOBAL DEFAULT 22 _end12 1: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 22 __bss_start13 1: 00000000000005c0 0 FUNC GLOBAL DEFAULT 9 _init14 2: 000000000000074c 0 FUNC GLOBAL DEFAULT 12 _fini
[dev1@localhost test05]$
与“.symtab”类似, 动态符号表也需要一些辅助的表, 比如用于保存符号名的字符串表。 静态链接时叫做符号字符串表“.strtab”( String Table) , ==在这里就是动态符号字符串表“.dynstr”( Dynamic String Table) ==; 由于动态链接下, 我们需要在程序运行时查找符号, 为了加快符号的查找过程, 往往还有辅助的符号哈希表( “.hash”) 。
4、动态链接重定位表
共享对象的重定位与我们在前面“静态链接”中分析过的目标文件的重定位十分类似, 唯一有区别的是目标文件的重定位是在静态链接时完成的, 而共享对象的重定位是在装载时完成的。 在静态链接中, 目标文件里面包含有专门用于表示重定位信息的重定位表, 比如“.rel.text”表示是代码段的重定位表, “.rel.data”是数据段的重定位表。
动态链接的文件中, 也有类似的重定位表分别叫做“.rel.dyn”和“.rel.plt”, 它们分别相当于“.rel.text”和“.rel.data”。 “.rel.dyn”实际上是对数据引用的修正, 它所修正的位置位于“.got”以及数据段; 而“.rel.plt”是对函数引用的修正, 它所修正的位置位于“.got.plt”。 我们可以使用readelf来查看一个动态链接的文件的重定位表:
[dev1@localhost test05]$ readelf -r pic.so重定位节 '.rela.dyn' 位于偏移量 0x488 含有 9 个条目:偏移量 信息 类型 符号值 符号名称 + 加数
000000200df0 000000000008 R_X86_64_RELATIVE 6e0
000000200df8 000000000008 R_X86_64_RELATIVE 6a0
000000200e08 000000000008 R_X86_64_RELATIVE 200e08
000000200fd0 000100000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0
000000200fd8 000200000006 R_X86_64_GLOB_DAT 0000000000000000 b + 0
000000200fe0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000200fe8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 _Jv_RegisterClasses + 0
000000200ff0 000600000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
000000200ff8 000700000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0重定位节 '.rela.plt' 位于偏移量 0x560 含有 4 个条目:偏移量 信息 类型 符号值 符号名称 + 加数
000000201018 000900000007 R_X86_64_JUMP_SLO 0000000000000715 bar + 0
000000201020 000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
000000201028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 ext + 0
000000201030 000700000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0
[dev1@localhost test05]$
六、动态链接的步骤和实现
有了前面诸多的铺垫, 我们终于要开始分析动态链接的实际链接步骤了。
动态链接的步骤基本上分为3步:
- 先是启动动态链接器本身,
- 然后装载所有需要的共享对象,
- 最后是重定位和初始化。
1、动态链接器自举
就是启动动态链接器本身
可是对于动态链接器本身来说, 它的重定位工作由谁来完成? 它是否可以依赖于其他的共享对象?
这是一个“鸡生蛋, 蛋生鸡”的问题, 为了解决这种无休止的循环, 动态链接器这个“鸡”必须有些特殊性。 首先是, 动态链接器本身不可以依赖于其他任何共享对象; 其次是动态链接器本身所需要的全局和静态变量的重定位工作由它本身完成。 对于第一个条件我们可以人为地控制, 在编写动态链接器时保证不使用任何系统库、 运行库; 对于第二个条件,动态链接器必须在启动时有一段非常精巧的代码可以完成这项艰巨的工作而同时又不能用到全局和静态变量。 这种具有一定限制条件的启动代码往往被称为自举(Bootstrap) 。
2、装载共享对象
完成基本自举以后, 动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表当中, 我们可以称它为全局符号表(Global Symbol Table) 。 然后链接器开始寻找可执行文件所依赖的共享对象, 我们前面提到过“.dynamic”段中。
链接器可以列出可执行文件所需要的所有共享对象, 并将这些共享对象的名字放入到一个装载集合中。 然后链接器开始从集合里取一个所需要的共享对象的名字, 找到相应的文件后打开该文件, 读取相应的ELF文件头和“.dynamic”段, 然后将它相应的代码段和数据段映射到进程空间中。如果这个ELF共享对象还依赖于其他共享对象, 那么将所依赖的共享对象的名字放到装载集合中。 如此循环直到所有依赖的共享对象都被装载进来为止。
3、重定位和初始化
当上面的步骤完成之后, 链接器开始重新遍历可执行文件和每个共享对象的重定位表, 将它们的GOT/PLT中的每个需要重定位的位置进行修正。 因为此时动态链接器已经拥有了进程的全局符号表, 所以这个修正过程也显得比较容易, 跟我们前面提到的地址重定位的原理基本相同。
重定位完成之后, 如果某个共享对象有“.init”段, 那么动态链接器会执行“.init”段中的代码, 用以实现共享对象特有的初始化过程, 比如最常见的, 共享对象中的C++的全局/静态对象的构造就需要通过“.init”来初始化。 相应地, 共享对象中还可能有“.finit”段, 当进程退出时会执行“.finit”段中的代码, 可以用来实现类似C++全局对象析构之类的操作。
如果进程的可执行文件也有“.init”段, 那么动态链接器不会执行它, 因为可执行文件中的“.init”段和“.finit”段由程序初始化部分代码负责执行,
当完成了重定位和初始化之后, 所有的准备工作就宣告完成了, 所需要的共享对象也都已经装载并且链接完成了, 这时候动态链接器就如释重负, 将进程的控制权转交给程序的入口并且开始执行。
七、显式运行时链接
支持动态链接的系统往往都支持一种更加灵活的模块加载方式, 叫做显式运行时链接(Explicit Run-time Linking) , 有时候也叫做运行时加载。
在Linux中, 从文件本身的格式上来看, 动态库实际上跟一般的共享对象没有区别, 正如我们前面讨论过的。
- 主要的区别是共享对象是由动态链接器在程序启动之前负责装载和链接的, 这一系列步骤都由动态连接器自动完成, 对于程序本身是透明的;
- 而动态库的装载则是通过一系列由动态链接器提供的API, 具体地讲共有4个函数: 打开动态库(dlopen) 、 查找符号(dlsym) 、 错误处理(dlerror) 以及关闭动态库(dlclose) , 程序可以通过这几个API对动态库进行操作。
这几个API的实现是在/lib/libdl.so.2里面, 它们的声明和相关常量被定义在系统标准头文件<dlfcn.h>。
1、dlopen()
dlopen() 函数用来打开一个动态库, 并将其加载到进程的地址空间, 完成初始化过程, 它的C原型定义为:
void * dlopen( const char * pathname, int mode);
第一个参数是被加载动态库的路径, 如果这个路径是绝对路径(以“/”开始的路径) , 则该函数将会尝试直接打开该动态库; 如果是相对路径,那么 dlopen() 会尝试在以一定的顺序去查找该动态库文件:
第二个参数flag表示函数符号的解析方式, 常量RTLD_LAZY表示使用延迟绑定, 当函数第一次被用到时才进行绑定, 即PLT机制; 而RTLD_NOW表示当模块被加载时即完成所有的函数绑定工作,上面的两种绑定方式必须选其一。使用RTLD_NOW会导致加载动态库的速度变慢。
dlopen的返回值是被加载的模块的句柄, 这个句柄在后面使用dlsym或者dlclose时需要用到。 如果加载模块失败, 则返回NULL。 如果模块已经通过dlopen被加载过了, 那么返回的是同一个句柄。
另外如果被加载的
模块之间有依赖关系, 比如模块A依赖与模块B, 那么程序员需要手工
加载被依赖的模块, 比如先加载B, 再加载A。
2、 dlsym()
dlsym函数基本上是运行时装载的核心部分, 我们可以通过这个函数找到所需要的符号。 它的定义如下:
void * dlsym(void *handle, char *symbol);
定义非常简洁, 两个参数, 第一个参数是由 dlopen()返回的动态库的句柄; 第二个参数即所要查找的符号的名字, 一个以“ \0 ”结尾的C字符串。 如果 dlsym() 找到了相应的符号, 则返回该符号的值; 没有找到相应的符号, 则返回NULL。 dlsym() 返回的值对于不同类型的符号, 意义是不同的。 如果查找的符号是个函数, 那么它返回函数的地址; 如果是个变量, 它返回变量的地址; 如果这个符号是个常量, 那么它返回的是该常量的值。
这里有一个问题是: 如果常量的值刚好是NULL或者0呢, 我们如何判断dlsym() 是否找到了该符号呢? 这就要用到我们下面介绍的 dlerror() 函数了。 如果符号找到了, 那么 dlerror() 返回NULL, 如果没找到, dlerror()就会返回相应的错误信息。
符号不仅仅是函数和变量, 有时还是常量, 比如表示编译单元文件名的符号等, 这一般由编译器和链接器产生, 而且对外不可见, 但它们的确存在于模块的符号表中。
3、dlerror()
每次我们调用 dlopen()、 dlsym()或 dlclose() 以后, 我们都可以调用 dlerror() 函数来判断上一次调用是否成功。 dlerror() 的返回值类型是char*, 如果返回NULL, 则表示上一次调用成功; 如果不是, 则返回相应的错误消息。
4、dlclose()
dlclose()的作用跟dlopen()刚好相反, 它的作用是将一个已经加载的模块卸载。 系统会维持一个加载引用计数器, 每次使用dlopen()加载某模块时, 相应的计数器加一;每次使用dlclose()卸载某模块时, 相应计数器减一。只有当计数器值减到0时, 模块才被真正地卸载掉。 卸载的过程跟加载刚好相反, 先执行“.finit”段的代码, 然后将相应的符号从符号表中去除, 取消进程空间跟模块的映射关系, 然后关闭模块文件。
5、demo1
我们在cmake系列讲过了windows系统的显示加载,也做了demo,在此我们再演示下linux下的显示加载。
// show.c
#include <stdio.h>void say(char str[])
{printf(str);
}
// main.c
#include <stdio.h>
#include <dlfcn.h>int main() {void* libHandle = dlopen("./show.so", RTLD_LAZY);if (libHandle == NULL) {fprintf(stderr, "Failed to load the library: %s\n", dlerror());return 1;}void (*sayFunc)(char[]) = (void (*)())dlsym(libHandle, "say");if (sayFunc == NULL) {fprintf(stderr, "Failed to find the symbol: %s\n", dlerror());dlclose(libHandle);return 1;}sayFunc("Hello, dynamic library!\n");dlclose(libHandle);return 0;
}
[dev1@localhost test07]$ gcc -shared -o show.so show.c -fPIC
[dev1@localhost test07]$ gcc -o main main.c -ldl
[dev1@localhost test07]$ ./main
Hello, dynamic library!
[dev1@localhost test07]$
6、demo2
#include <stdio.h>int add(int a,int b)
{return a+b;
}
#include <stdio.h>
#include <dlfcn.h>int main() {void* libHandle = dlopen("./show.so", RTLD_LAZY);if (libHandle == NULL) {fprintf(stderr, "Failed to load the library: %s\n", dlerror());return 1;}typedef int(*addPtr)(int,int);addPtr add = dlsym(libHandle, "add");if (add == NULL) {fprintf(stderr, "Failed to find the symbol: %s\n", dlerror());dlclose(libHandle);return 1;}int sum = add(1,2);printf("sum = %d\n",sum);dlclose(libHandle);return 0;
}
[dev1@localhost test07]$ gcc -shared -o show.so show.c -fPIC
[dev1@localhost test07]$ gcc -o main main.c -ldl
[dev1@localhost test07]$ ./main
sum = 3
[dev1@localhost test07]$
参考
1、《程序员的自我修养链接装载与库》