在看公司spp框架代码的时候发现了如下一段宏定义,其中的dlsym函数及其RTLD_NEXT参数的含义不是很明白,于是网上搜了下这里做个记录。
#define mt_hook_syscall(name) \
do { \
if (!g_mt_syscall_tab.real_##name) { \
g_mt_syscall_tab.real_##name = (func_##name)dlsym(RTLD_NEXT, #name);\
} \
} while (0)
我们知道动态库装载是由一些列动态库提供的API来完成的,准确来说就是打开动态库(dlopen)、查找符号(dlsym)、关闭动态库(dlcose)、错误处理(dlerror)四个函数。这四个函数包含dlfcn.h头文件(#include<dlfcn.h>):
1、打开动态库dlopen。
函数定义 void * dlopen(const char* pathName, int mode);
(1)pathName 指的是动态库路径。如果是绝对路径就直接打开此动态库文件;如果是相对路径则则会以一定顺序查找动态文件,顺序如下:
1)查找由环境变量LD_LIBRARY_PATH指定的一系列目录;
2)查找由/etc/ld.so.cache里面所指定的共享库路径;
注:运行“/sbin/ldconfig -v”使生效。
3)/lib、/usr/lib等默认搜索路径。
(2)mode指的是解析方式:
1)RTLD_LAZY:暂缓决定,在dlopen返回前,对于动态库中的未定义的符号不执行解析(只对函数引用有效,对于变量引用总是立即解析)
2)RTLD_NOW:立即决定,在dlopen返回前,解析出所有未定义的符号,如果解析不出来,在dlopen会返回NULL,错误为 undefined symbol:XXX...
2、查找符号dlsym。这里先介绍下dlsym函数。
函数定义 void *dlsym(void *handle, const char* symbol);
handle是由dlopen打开动态链接库后返回的指针,symbol就是要求获取的函数的名称。dlsym函数的返回值是void*,指向要查找的函数symbol的地址,供调用使用
2.1、dlsym函数的RTLD_NEXT选项
经过查询知道其含义是使用RTLD_NEXT参数找到的的函数指针就是后面第一次出现这个函数名的函数指针。大致意思就是说我们可能会链接多个动态库,不同的动态库可能都会有symbol这个函数名,那么使用RTLD_NEXT参数后dlsym返回的就是第一个遇到(匹配上)symbol这个符号的函数的函数地址。进一步的我们使用dlsym的返回调用的也就是这个第一个匹配上的函数了。
3、关闭动态库dlerror
作用和dlopen相反,将一个动态库卸载。
4、错误处理dlerror
每次调用完上述三个函数后都可以调用dlerror来判断上次调用是否成功。
5、dlsym的RTLD_NEXT选项效果实例:
(1)如下几个文件分别编译成动态库或目标文件
/*
文件名:first_one.c
编译成动态库:gcc -fpic --shared first_one.c -o libfirst_one.so
*/
#include <stdio.h>
void print_message()
{
printf("the first lib~~\n");
}
void first()
{
printf("init first\n");
}
/*
文件名:second_one.c
编译动态库: gcc -fpic --shared second_one.c -o libsecond_one.so
*/
#include <stdio.h>
void print_message()
{
printf("the second lib~~\n");
}
void second()
{
printf("init second \n");
}
/*
文件名:wrap.c
编译动态库: gcc -fpic --shared wrap.c -o libwrap.so
注:void load_func() __attribute__((constructor))的含义是在执行main函数前,执行load_func这个函数,便于我们做一些准备工作。显然这里的作用就是触发dlsym以实现查找第一个"print_message"函数符号的目的。
具体参见 jianshu.com/p/dd425b9dc9db
*/
# define RTLD_NEXT ((void *) -1l)
#include <stdio.h>
#include <dlfcn.h>
#include <errno.h>
void(*f)();
void load_func() __attribute__((constructor));
void load_func()
{
f = (void(*)())dlsym(RTLD_NEXT,"print_message");
char *error_str;
error_str = dlerror();
if (error_str != NULL) {
printf("%s\n", error_str);
}
printf("load func first f=%p\n",f);
}
void print_message()
{
printf("the wrap lib~~\n");
f();
}
/*
文件名:main.c
编译动态库: gcc -c main.c
*/
#include <stdio.h>
void print_message();
void first();
void second();
int main()
{
first();
second();
print_message();
return 0;
}
(2)调整链接顺序,使链接器第一个找到/匹配到不同实现的"print_message"函数(或者说是符号)。
#优先链接libfirst_one.so
gcc -o first main.o -lwrap -lfirst_one -lsecond_one -ldl -L.
#优先链接libsecond_one.so
gcc -o second main.o -lwrap -lsecond_one -lfirst_one -ldl -L.
设置执行时的环境变量:
#环境变量LD_LIBRARY_PATH主要用于查找共享库时除了默认路径外的其他路径;
#此处是把当前路径加入到查找路径的意思
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
(3)使用ldd(List Dynamic Dependencies)指令查看库的加载顺序
ldd:列出一个程序所需要的动态链接库。
(4)执行结果如下:
(5)执行结果分析:
int main()
{
first();
second();
print_message();
return 0;
}
0.第一行输出“load func first”,就是__attribute__((constructor))的效果{main函数前先执行某预备函数}
1.first函数无异议,就是执行的first_one中的函数;对应输出中的第二行。
2.second函数无异议,就是执行的second_one中的函数;对应输出中的第三行。
3.print_message执行的实际上是wrap.c中的实现——看链接顺序第一个匹配的是libwrap.so
其中又调用了再之后dlsym查找到的print_message函数;这个时候进一步往后匹配到的就是-lwrap后面的-lfirst_one或者-lsecond_one了。
这里贴两段《程序员的自我修养》一书中关于全局符号介入和dlsym函数的描述:
1 全局符号介入
linux下的动态链接器存在以下原则:当共享对象被load进来的时候,它的符号表会被合并到进程的全局符号表中(这里说的全局符号表并不是指里面的符号全部是全局符号,而是指这是一个汇总的符号表),当一个符号需要加入全局符号表时,如果相同的符号名已经存在,则后面加入的符号被忽略。
2 dlsym函数
查找符号的地址。对应于函数,即函数地址,对应于变量,即变量地址。通过传入RTLD_NEXT参数,在当前库之后load进来的动态库中寻找对应符号的地址,显然在这里找到的会是glibc中相关socket函数的地址(这句话在下文中的波折部分被证明是错的,原因是并不是只有glibc中有socket函数的定义,不过在这里这么理解没毛病)。
参看:http://www.tecyle.com/2017/03/03/dlsym%E5%8F%82%E6%95%B0-rtld_next%E8%AF%A6%E8%A7%A3/
————————————————
版权声明:本文为CSDN博主「shuozhuo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mijichui2153/article/details/109561978