ldd 命令

linux 下有动态库和静态库,动态库以.so为扩展名,静态库以.a为扩展名,查看某个程序使用了那些动态库,使用ldd命令查看。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程叫做动态链接,是由一个叫做动态链接器的程序来执行的。

image.png

windows 下的隐式调用

隐式调用,load time dynamic linking,就是说你的代码里面已经直接调用了库里面的函数,那么在 link 的时候会把该库的一小段 lib link 进去(而Linux上直接链接.so即可),这里面包含了这个DLL的相关信息以便在真正运行时能找到那个dll。然后当你exe运行时,windows 就会根据那些信息把需要用到的dll载入内存。

windows 下使用动态库,编译后往往提供两个文件:一个引入库(.lib)文件(也称“导入库文件”)和一个DLL(.dll)文件。windows 下隐式连接需要三个东西,分别是 .h头文件,lib库(动态的),DLL库。显式连接只需要.dll文件即可。程序会通过 lib 库,在编译阶段将调用动态库函数的代码之前对*加上动态库进行加载入内存的代码。

windows 下的显式调用

显式调用,run time dynamic linking,在编译以及 link 的时候是并不需要提供这个库的信息的。在程序真正运行的时候,通过自己调用 LoadLibrary, GetProcAddress 等 API 手工把 DLL 载入内存并找到里面的函数来调用。显示调用不会被 ldd 命令看到。

隐式链接虽然实现较简单,但除了必须的.dll文件外还需要DLL的.h文件和.lib文件,在那些只提供.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,能更加有效地使用内存,在编写大型应用程序时往往采用此方式。

  1. 一:显示链接
  2. 调用的 DLL的主工程的 main文件中代码如下:
  3. #include <stdio.h>
  4. #include <Windows.h>
  5. #include <tchar.h>
  6. int main()
  7. {
  8. HMODULE hModule = NULL;
  9. typedef int (*Func)(int a, int b);
  10. // 动态加载 DLL 文件
  11. hModule = LoadLibrary(_TEXT("..//Debug//FuncDll.dll" ));
  12. // 获取 add 函数地址
  13. Func fAdd = (Func)GetProcAddress(hModule, "add" );
  14. // 使用函数指针
  15. printf("%d/n" , fAdd(5, 2));
  16. // 最后记得要释放指针
  17. FreeLibrary(hModule);
  18. return 0;
  19. }
  20. 二:隐式链接:
  21. 调用的 DLL的主工程的 main文件中代码如下:
  22. #include <stdio.h>
  23. #include <Windows.h>
  24. #include <tchar.h>
  25. // 先把 lib 链接进来
  26. #pragma comment (lib , "..//Debug//FuncDll.lib" )
  27. // 外部声明的 add 函数
  28. extern "C" _declspec (dllimport )
  29. int add(int a, char b);
  30. int main()
  31. {
  32. // 直接调用 add 函数
  33. printf("%d/n" , add(5, 2));
  34. return 0;
  35. }

Linux 下的隐式调用

动态库编译后的 .so 文件链接的时候需要这个.so,链接后编译完成之后,当这个程序运行的时候 .so 也是要必须在场的。(在linux中,例如用LD_LIBRARY_PATH环境变量设置动态链接库的查找地址),要不然程序会找不到这个.so而无法运行。LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib cmake_examples_inst_bin

Linux 下的显式调用

dlopenAPI 使得程序能在运行时打开一个共享库,根据名字在库中搜索一个函数并调用(linux/Unix系统编程手册)。

其中主要的函数是:

  • dlopen(libname,flag):打开 libname 共享库,flag 用来设置是否解析库中的未定义符号。如果打开的库还依赖于其他库,那么会递归加载对应的那些库

  • dlerror():如果dlopen()函数调用出错,则用此函数能返回指向错误原因字符串的指针。‘

  • dlclose(libname):卸载库。

动态链接小结

当把可执行文件复制到内存后,且在程序开始运行之前,操作系统会查找可执行文件依赖的动态库信息(主要是动态库的名字以及存放路径),找到该动态库后就将该动态库从磁盘搬到内存,并进行符号决议,如果这个过程没有问题,那么一切准备工作就绪,程序就可以开始执行了,如果找不到相应的动态库或者符号决议失败,那么会有相应的错误信息报告为用户,程序运行失败。

加载时动态链接可以分为两个阶段:阶段一,将动态库信息写入可执行文件(链接阶段);阶段二,加载可执行文件时依据动态库信息进行动态链接(加载阶段)。运行时动态链接这种方式对于“动态链接”阐释的更加淋漓尽致,因为可执行文件在启动运行之前都不知道需要依赖哪些动态库,只在运行时根据代码的需要再进行动态链接。

动态链接下可执行文件的生成

在动态链接下,链接器并不是将动态库中的代码和数据拷贝到可执行文件中,而是将动态库的必要信息写入了可执行文件,这样当可执行文件在加载时就可以根据此信息进行动态链接了。(这里我们以Linux下可执行文件即ELF文件为例)。

在动态链接下,可执行文件当中会新增两段,即dynamic段以及GOT(Global offset table)段,这两段内容就是是我们之前所说的必要信息。dynamic段中保存了可执行文件依赖哪些动态库,动态链接符号表的位置以及重定位表的位置等信息。可执行文件在加载时就可以根据此信息进行动态链接了。

image.png