【编译、链接、装载十二】动态链接2

news2025/1/16 21:51:01

【编译、链接、装载十二】动态链接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  $0x0
 5fb:	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 hello

hello:     文件格式 elf64-x86-64

Contents 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 hello

Dynamic section at offset 0xe28 contains 24 entries:
  标记        类型                         名称/值
 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]
 0x000000000000000c (INIT)               0x4003e0
 0x000000000000000d (FINI)               0x4005b4
 0x0000000000000019 (INIT_ARRAY)         0x600e10
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x600e18
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x400298
 0x0000000000000005 (STRTAB)             0x400318
 0x0000000000000006 (SYMTAB)             0x4002b8
 0x000000000000000a (STRSZ)              61 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x601000
 0x0000000000000002 (PLTRELSZ)           72 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x400398
 0x0000000000000007 (RELA)               0x400380
 0x0000000000000008 (RELASZ)             24 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x400360
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x400356
 0x0000000000000000 (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 Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
     2: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND b
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     4: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND ext
     5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     6: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
     7: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
     8: 0000000000201038     0 NOTYPE  GLOBAL DEFAULT   21 _edata
     9: 0000000000000715    29 FUNC    GLOBAL DEFAULT   11 bar
    10: 0000000000000732    26 FUNC    GLOBAL DEFAULT   11 foo
    11: 0000000000201040     0 NOTYPE  GLOBAL DEFAULT   22 _end
    12: 0000000000201038     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start
    13: 00000000000005c0     0 FUNC    GLOBAL DEFAULT    9 _init
    14: 000000000000074c     0 FUNC    GLOBAL DEFAULT   12 _fini

Symbol table '.symtab' contains 58 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 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.c
    25: 0000000000200e00     0 OBJECT  LOCAL  DEFAULT   17 __JCR_LIST__
    26: 0000000000000630     0 FUNC    LOCAL  DEFAULT   11 deregister_tm_clones
    27: 0000000000000660     0 FUNC    LOCAL  DEFAULT   11 register_tm_clones
    28: 00000000000006a0     0 FUNC    LOCAL  DEFAULT   11 __do_global_dtors_aux
    29: 0000000000201038     1 OBJECT  LOCAL  DEFAULT   22 completed.6355
    30: 0000000000200df8     0 OBJECT  LOCAL  DEFAULT   16 __do_global_dtors_aux_fin
    31: 00000000000006e0     0 FUNC    LOCAL  DEFAULT   11 frame_dummy
    32: 0000000000200df0     0 OBJECT  LOCAL  DEFAULT   15 __frame_dummy_init_array_
    33: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS pic.c
    34: 000000000020103c     4 OBJECT  LOCAL  DEFAULT   22 a
    35: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    36: 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_handle
    40: 0000000000200e10     0 OBJECT  LOCAL  DEFAULT   19 _DYNAMIC
    41: 0000000000000758     0 NOTYPE  LOCAL  DEFAULT   13 __GNU_EH_FRAME_HDR
    42: 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_deregisterTMCloneTab
    45: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND b
    46: 0000000000201038     0 NOTYPE  GLOBAL DEFAULT   21 _edata
    47: 0000000000000715    29 FUNC    GLOBAL DEFAULT   11 bar
    48: 000000000000074c     0 FUNC    GLOBAL DEFAULT   12 _fini
    49: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    50: 0000000000000732    26 FUNC    GLOBAL DEFAULT   11 foo
    51: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND ext
    52: 0000000000201040     0 NOTYPE  GLOBAL DEFAULT   22 _end
    53: 0000000000201038     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start
    54: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
    55: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
    56: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@@GLIBC_2.2
    57: 00000000000005c0     0 FUNC    GLOBAL DEFAULT    9 _init

[dev1@localhost test05]$ readelf -sD pic.so

Symbol table of `.gnu.hash' for image:
  Num Buc:    Value          Size   Type   Bind Vis      Ndx Name
    8   0: 0000000000201038     0 NOTYPE  GLOBAL DEFAULT  21 _edata
    9   0: 0000000000000715    29 FUNC    GLOBAL DEFAULT  11 bar
   10   0: 0000000000000732    26 FUNC    GLOBAL DEFAULT  11 foo
   11   0: 0000000000201040     0 NOTYPE  GLOBAL DEFAULT  22 _end
   12   1: 0000000000201038     0 NOTYPE  GLOBAL DEFAULT  22 __bss_start
   13   1: 00000000000005c0     0 FUNC    GLOBAL DEFAULT   9 _init
   14   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、《程序员的自我修养链接装载与库》

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

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

相关文章

2.10 高性能异步IO机制:io_uring

一、io_uring的引入 为了方便说明io_uring的作用&#xff0c;先举一个通俗点的例子 1、通过异步提高读写的效率 假设有一批数量很大的货&#xff0c;需要分批次运到厂里处理。这个时候就有两种方式&#xff1a; 1&#xff09;同步方式&#xff1a;运送一批到厂里&#xff0c…

TypeScript ~ TS 掌握编译文件配置项 ④

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; TypeScript ~ TS &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &…

初识EasyX图形库

EasyX图形库 1. EasyX是什么&#xff1f;2. 入手EasyX3. EasyX函数介绍创建和关闭绘图窗口操作initgraphclosegraph 设置绘图背景setbkcolorcleardevice 画图形circlefillcirclerectanglefillrectangle 图形颜色及样式设置setfillcolorsetlinecolorsetbkcolorsetbkmodesetlines…

计算物理专题:有限差分法解决本征值问题

计算物理专题&#xff1a;有限差分法解决本征值问题 定态薛定谔方程差分形式 一维定态薛定谔方程 谐振子 解法代码 import numpy as np def householder(symmetric_matrix):M symmetric_matrixassert np.allclose(M,M.T),"matrix is not symmetric"N len(M)for …

chatgpt赋能python:用Python分析电影评分数据

用Python分析电影评分数据 Python是一种流行的数据分析和可视化工具&#xff0c;它可以让我们更深入地了解电影的评分数据。在本文中&#xff0c;我们将使用Python来分析一些电影评分数据&#xff0c;并试图找出一些有趣的模式和趋势。 数据来源 我们将使用公共数据集IMDb电…

第4章 网络层

1‌、下列关于路由算法描述错误的是&#xff08; &#xff09; A. 链路状态算法是一种全局路由算法&#xff0c;每个路由器需要维护全局状态信息B. OSPF 是一种域内路由协议&#xff0c;核心是基于 Dijkstra 最低费用路径算法C. RIP 是一种域内路由算法&#xff0c;核心是基…

采用SqlSugar的DBFirst相关功能创建数据库表对应的实体类

.NET Core官方教程中推荐使用的EF Core数据库ORM框架虽然能用&#xff0c;但是用起来并不是太方便&#xff08;或者是不习惯&#xff0c;之前用的最多的还是linq&#xff09;。之前下载的开源博客项目中使用的SqlSugar&#xff0c;后者是由果糖大数据科技团队维护和更新 &#…

基于WebAssembly构建Web端音视频通话引擎

Web技术在发展&#xff0c;音视频通话需求在演进&#xff0c;怎么去实现新的Web技术点在实际应用中的值&#xff0c;以及给我们带来更大的收益是需要我们去探索和实践的。LiveVideoStackCon 2022北京站邀请到田建华为我们从实践中来介绍WebAssembly、WebCodecs、WebTransport等…

【裸机开发】IRQ 中断服务函数(一) —— 汇编初始化

IRQ 和前面的Reset 函数不大一样&#xff0c;当一个IRQ中断产生时&#xff0c;我们也不知道这个IRQ中断来自哪个外设&#xff0c;因此&#xff0c;需要先获取到中断ID&#xff0c;随后才会跳转到真正的中断服务函数执行处理逻辑。 整个 IRQ 中断处理可以看做是包含了两个部分&…

MySQL 自增主键一定是连续的吗?

众所周知&#xff0c;自增主键可以让聚集索引尽量地保持递增顺序插入&#xff0c;避免了随机查询&#xff0c;从而提高了查询效率 但实际上&#xff0c;MySQL 的自增主键并不能保证一定是连续递增的。 下面举个例子来看下&#xff0c;如下所示创建一张表&#xff1a; 自增值保…

ORCA优化器浅析——GP数据库调用优化器流程

首先我们需要看CGPOptimizer类(src/include/gpopt/CGPOptimizer.h)为Greenplum数据库提供ORCA优化器export出来的函数的封装。Greenplum数据库主流程调用extern "C"中提供的函数&#xff0c;比如初始化ORCA优化器的函数InitGPOPT&#xff0c;优化查询树的函数GPOPTOp…

springboot+jsp农产品商城宣传网站设计与实现oo6e3

在该在线助农系统设计与实现中&#xff0c;idea能给用户提供更多的方便&#xff0c;其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#xff0c;主要功能是用在对数据库中查询和编程。其功能有比较灵活的数据应用&#xff0c;只需利用小部分代码…

【Leetcode60天带刷】day30回溯算法——332.重新安排行程 , 51. N皇后 ,37. 解数独

​ 题目&#xff1a; 332. 重新安排行程 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的先生&#xff0c;…

【从零开始学习JAVA | 第十四篇】继承

目录 前言&#xff1a; 引入&#xff1a; 继承&#xff1a; 小拓展&#xff1a; 优点&#xff1a; 成员方法的继承问题&#xff1a; 总结&#xff1a; 前言&#xff1a; 继承是面向对象三大特性之一&#xff0c;它是在封装之后我们讲解的一个重要的性质&#xff0c;继承…

在github上创建个人主页的方法【2023更新版】

01-进入github的网站&#xff0c;链接 https://github.com/ &#xff0c;然后注册&#xff0c;登陆&#xff0c;注意登陆时设置的用户名(username)就是将来你个人主页的三级域名&#xff0c;所以这里一定要慎重填写username。如下图所示&#xff1a; 02-注册完成后进入个人主…

2024考研408-计算机组成原理第四章-指令系统

文章目录 前言一、指令系统现代计算机的结构1.1、指令格式1.1.1、指令的定义1.1.2、指令格式1.1.3、指令—按照地址码数量分类①零地址指令②一地址指令&#xff08;1个操作数、2个操作数情况&#xff09;③二地址指令④三地址指令⑤四地址指令 1.1.4、指令-按照指令长度分类1.…

【计算机组成原理】Yy-z02模型机的硬布线控制器设计

目录 一、Yy-z02模型机的系统结构 二、Yy-z02模型机的数据通路 三、Yy-z02模型机的指令执行 四、Yy-z02模型机的硬布线控制器 一、Yy-z02模型机的系统结构 指令系统的实现 <--- 构造它的硬件系统 硬件系统构造过程&#xff1a; 分析指令格式和各指令的功能确定部件连…

金蝶软件遭遇.locked勒索病毒攻击:如何保护与解救您的数据?

引言&#xff1a; 近期&#xff0c;部分运行金蝶云星空软件的服务器遭受了一场勒索病毒的网络安全攻击&#xff0c;其重要数据遭到了.locked勒索病毒的加密。作为一个知名的企业级ERP软件及财务软件&#xff0c;金蝶软件的数据安全事关客户和企业的利益。91数据恢复在本文将深…

【王道·操作系统】第四章 文件管理【未完】

一、初识文件管理 文件&#xff1a;一组有意义的信息/数据集合文件属性&#xff1a; 文件名&#xff1a;创建文件的用户决定&#xff0c;主要是为了方便用户找到文件&#xff0c;同一目录下不允许有重名文件标识符&#xff1a;一个系统内的各文件标识符唯一&#xff0c;对用户来…

老大给了个新需求:如何将汉字转换成拼音字母?1行Python代码搞定!

大家好&#xff0c;这里是程序员晚枫&#xff0c;小红薯也叫这个名。 之前的视频给大家分享了&#xff1a;中文编程&#xff0c;一行代码实现。 今天给大家分享一下&#xff0c;如何通过1行Python代码&#xff0c;实现汉语转拼音 1、先上代码 实现汉语转拼音效果的第三方库…