动态库装载及 dlsym的RTLD_NEXT参数详解

在看公司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函数的定义,不过在这里这么理解没毛病)。

参看:dlsym参数 RTLD_NEXT详解 – 夕月阁 

  • 8
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

焱齿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值