动态链接库

动态链接库

在运行时打开一个动态链接库,主要使用dlopendlsym这两个函数,这两个函数均可以在dlfcn.h中找到,其原型分别为:

1
2
3
4
extern int dlclose(void * __handle);
extern char * dlerror(void);
extern void * dlopen(const char * __path, int __mode);
extern void * dlsym(void * __handle, const char * __symbol);

dlopen接受两个参数,第一个参数是动态链接库的路径,第二个参数是什么时候解析动态链接库,在第二个参数中,如果使用RTDL_GLOBAL参数,并且当前的可执行文件是带-rdynamic选项编译的,那么动态链接库里面的全局符号也是可用的,flag参数要么使用RTDL_NOW,告诉链接器立即解析对外部符号的引用,要么使用RTDL_LAZY,告诉链接器推迟符号解析直到执行来自库中的代码。这两个标志都可以和RTDL_GLOBAL取或。
该函数会返回一个打开共享库的句柄,如何获取共享库中的函数呢?可以使用dlsym函数,dlsym函数的输入是一个指向前面已经打开了的共享库的句柄和一个symbol名字,如果该符号存在,就返回符号的地址,否则返回NULL

如果没有其他共享库还在使用这个共享库,dlclose函数就卸载该共享库

dlerror函数返回一个字符串,它描述的是调用dlopen、dlsym、dlclose函数时发生的最近的错误,如果没有错误发生,就返回NULL。

1
cc -rdynamic -o prog prog.c -ldl

prog.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

extern int add(int a, int b);

int main(int argc, char *argv[]) {
if (argc < 5) {
printf("./progr libPath fnName a b");
exit(1);
}
char *path = argv[1];
char *fn = argv[2];
void *handle = dlopen(path, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
int (*add)(int a, int b);
add = dlsym(handle, fn);
char *error;
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(1);
}
int a = atoi(argv[3]);
int b = atoi(argv[4]);
int result = add(a, b);
printf("%d + %d = %d", a, b, result);
return 0;
}

位置无关的代码

共享库的一个主要目的就是允许多个正在运行的进程共享内存中相同的库代码,因而节约宝库的内存资源。现代操作系统以这样一种方式编译共享模块的代码段,使得他们可以加载到内存的任何位置而无需链接器修改。使用这张方法,无限多个进程可以共享一个共享模块的代码段的单一副本。
可以加载而无需重定位的代码称为位置无关代码(Position-Independent Code, PIC)。用户对GCC使用-fpic选项指示GNU编译系统生成PIC代码。共享库的编译必须总是使用该选项。

库打桩机制

链接器支持一个很强大的技术,称为库打桩(library interpositioning),它允许你截获对共享库的函数的调用,取而代之执行自己的代码。使用打桩机制,你可以追踪对某个特殊库函数的调用次数,验证和追踪它的输入和输出值,或者甚至把它替换成一个完全不同的实现。
下面是它的基本思想:给定一个需要打桩的目标函数,创建一个包装函数,它的原型与目标函数完全一样。使用某种特殊的打桩机制,你就可以欺骗系统调用包装函数而不是目标函数了。包装函数通常会执行它自己的逻辑,然后调用目标函数,再将目标函数的调用值返回给调用者。
打桩可以发生在编译时、链接时或当程序被夹在和执行的运行时。

封装动态链接库

打开的动态库路径使用@rpath, 然后编译好后,既可以将动态链接库放到系统的动态链接库搜索路径,也可以使用install_name_tool设置

1
install_name_tool -add_rpath $library_path $executable_file
  • $library_path是要添加的动态链接库

参考文档