什么系统调用用于在Linux中加载库?


23

strace输出中,可执行文件调用的库路径在对的调用中open()。这是动态链接的可执行文件使用的系统调用吗?那dlopen()open()这不是我猜想会在程序执行中发挥作用的电话。

Answers:


33

dlopen不是系统调用,而是libdl库中的库函数。中仅显示系统调用strace

在Linux和许多其他平台(尤其是使用ELF格式的可执行文件的平台)上,dlopen可以通过打开目标库open()并将其映射到内存中来实现mmap()mmap()这实际上是关键的部分,它是将库合并到进程的地址空间中的原因,因此CPU可以执行其代码。但是您必须先open()归档文件mmap()


2
“ mmap()确实是关键部分”:然后,动态链接器必须进行重定位,初始化等等(但这在系统调用级别上看不到)。
ysdx

1
由于库的加载是由库函数完成的,因此我认为添加可执行文件本身ld-linux并由内核映射为execve系统调用的一部分是很重要的。
卡巴斯德(Kasperd)

按照这个答案的mmap。还请注意,在“打开”每个库之后,在mmap调用之前已读取了一些(832)字节,我假设要检查该库是否有效。
约翰

@kasperd Linux内核知道动态加载程序吗?运行应用程序时会调用它吗?还是应用程序本身执行此操作?如果是后者,另一个可执行文件如何访问应用程序的内存?
Melab

@Melab是的,内核知道动态链接器。内核将从可执行文件的头读取动态链接器的路径。内核会将两者都映射到内存中。我不知道内核转移控制最初的入口点是在链接器还是可执行文件中。如果要实现它,则可能会将内核传输控制权传递给链接器中的入口点,并且堆栈上的返回地址指向可执行文件的入口点。
kasperd '16

5

当您想到共享库时,dlopen与它们无关。有两种加载共享库的方法:

  1. 您告诉编译时链接程序(ld,尽管通常通过编译器调用它)要使用特定共享库中的函数。使用这种方法,您必须知道运行编译时链接程序时库的名称,但是您可以调用库的函数,就像它们是静态链接到程序中一样。运行应用程序时,将在调用main函数之前立即调用动态的运行时链接程序(ld.so),并设置应用程序的进程空间,以便应用程序可以找到库的功能。这涉及open()先润滑润滑脂,然后润滑mmap()它,然后设置一些查找表。
  2. 您告诉编译时链接器要与链接libdl,然后您(使用第一种方法)可以从中调用dlopen()dlsym()功能。使用dlopen可以获得库的句柄,然后可以将其与dlsym一起使用以接收指向特定函数的函数指针。对于程序员而言,此方法比第一种方法复杂得多(因为您必须手动进行设置,而不是让链接程序为您自动进行设置),并且它也更加脆弱(因为您没有获得编译的权限) -time检查您在第一种方法中获得的参数类型正确的函数),但优点是您可以决定在运行时加载哪个共享库(甚至根本不加载),从而这是用于插件类型功能的接口。最后,dlopen接口的可移植性也比其他方法差,因为它的机制取决于动态链接程序的确切实现(因此,libtool的接口libltdl,它试图消除这些差异)。

有趣; 因此,动态加载的库最好称为动态链接库,因为将二进制文件加载到内存中并非难事,因此使其中使用的地址有意义。当我要求加载动态库时,实际上是在要求将库链接(或取消链接)到(或离开)我的地址空间。
德米特里(Dmitry)

4

今天,大多数操作系统都使用SunOS-4.0在1987年末引入的共享库方法。此方法基于通过mmap()映射内存。

考虑到在1990年代初期,Sun甚至向FreeBSD员工捐赠了旧的基于a.out的代码(当时的Solaris已经基于ELF),并且后来将该代码移交给了许多其他系统(包括Linux) ,您可能会理解为什么平台之间没有太大区别。


3

ltrace -S一个最小示例的分析表明,该示例mmap已在glibc 2.23中使用

在glibc 2.23中,Ubuntu 16.04在运行latrace -S于以下程序的最小程序上运行dlopen

ltrace -S ./dlopen.out

显示:

dlopen("libcirosantilli_ab.so", 1 <unfinished ...>
SYS_open("./x86_64/libcirosantilli_ab.so", 524288, 06267650550)      = -2
SYS_open("./libcirosantilli_ab.so", 524288, 06267650550)             = 3
SYS_read(3, "\177ELF\002\001\001", 832)                              = 832
SYS_brk(0)                                                           = 0x244c000
SYS_brk(0x246d000)                                                   = 0x246d000
SYS_fstat(3, 0x7fff42f9ce30)                                         = 0
SYS_getcwd("/home/ciro/bak/git/cpp-cheat"..., 128)                   = 54
SYS_mmap(0, 0x201028, 5, 2050)                                       = 0x7f1c323fe000
SYS_mprotect(0x7f1c323ff000, 2093056, 0)                             = 0
SYS_mmap(0x7f1c325fe000, 8192, 3, 2066)                              = 0x7f1c325fe000
SYS_close(3)                                                         = 0
SYS_mprotect(0x7f1c325fe000, 4096, 1)                                = 0

因此我们立即看到dlopen调用open+ mmap

很棒的ltrace工具可以跟踪库调用和系统调用,因此非常适合检查这种情况下的情况。

进一步分析表明,open该文件返回文件描述符3(stdin,out和err之后的下一个空闲描述符)。

read然后使用该文件描述符,但是TODO为什么将mmap参数限制为四个,我们不能看到在那里使用了哪个fd,因为那是第五个参数strace确认这3是预期的,并且恢复了宇宙的顺序。

勇敢的人也可以尝试glibc代码,但是mmap经过一番快速grep之后,我找不到了,我很懒。

通过在GitHub上构建样板的最小示例进行了测试。


2

strace报告系统调用(即由内核直接实现的功能)。动态库不是内核功能。dlopen是C库的一部分,而不是内核。dlopenwill调用的实现open(这是系统调用),以打开库文件以便可以读取它。


5
使用可以看到库调用ltrace
卡巴斯德(Kasperd)

@kasperd ltrace -S非常适合分析此问题,因为它也显示系统调用:unix.stackexchange.com/a/462710/32558
Ciro Santilli新疆改造中心法轮功六四事件
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.