关于dlsym的总结

本文介绍了C/C++中dlopen、dlsym和dlclose等函数的作用,以及如何利用它们在hook函数时实现对标准库或外部库函数的替换。作者通过实例演示了如何使用dlsym找到并调用动态链接库中的函数,以及链接器如何根据-l选项确定使用哪个库的函数。
摘要由CSDN通过智能技术生成

对于dlopen、dlsym,dlclose等这一些列的函数,可能大家几乎很少能用到,可能有的朋友见都没有见过,我也是在别的项目(sylar的hook模块)上遇到的,刚看到的时候,一头雾水,这是什么玩意儿?经过漫长的岁月,我终于了解一点了。


初识sylar的hook函数

sylar的hook了很多函数,像read, write, sleep, socket, connect等很多函数,感兴趣的,可以取看一下源码的实现。

什么是hook了?我的理解就是在原有的函数上在包装一层,且包装的这一层,和以前是一样的使用方法。比如linux下的read()函数,函数原型是这样的:

//标准库的
ssize_t read(int fd, void *buf, size_t count);

 现在,我自己实现了一个read()函数,是这样的:

//自定义的
ssize_t read(int my_fd, void *buf, size_t count);

 在我自己实现的read函数里面去调用标准库的read函数!这样当我在代码里面使用read函数,就像使用标准库的read函数,一点区别没有(函数名,参数,返回值都一样)。大概就像下面这样:

//自定义的实现
ssize_t read(int my_fd, void *buf, size_t count) {
    //偷偷摸摸干点其他事
    return read(my_fd, buf, count); //这里假装是标准库的read函数
}

 你会发现,当我在代码里面实际使用的时候,根本没有差异,还是调用read函数,多舒服。

 ps  :   但是上面的代码直接编译,运行的话,最终就会无限套娃,直到爆栈,原因是return 的时候,调用的read函数,还是我们自己定义的read函数。

dlsym登场

为了更简单的说明,如下:

//add.h
int add(int, int);

//add.cpp
#include <iostream>
#include "add.h"
int add(int a, int b) {
    std::cout << ".so add func" << std::endl; 
    return a + b;
}

将源文件编译成 动态链接库 :gcc -fPIC -shared add.cpp -o libadd.so 

这样就得到了一个libadd.so的库,回到hook操作。

//test.cpp
#include <iostream>
#include "add.h"

int add(int a, int b) {
    std::cout << "my add func" << std::endl;
    return add(a, b);
}

int main() {
    add(1, 2);
}

 按照我们的想法,我自定义的add函数内部应该要去调用libadd.so里面的add函数,直接编译(gcc -g test.cpp -o test -L. -ladd),运行(./test)的话,就会一直输出 my add func。这肯定和我们所想要的是不一致的。使用gdb调试,发现它会一直自己调用自己。

使用ldd test命令你会发现,test他都不需要这个依赖项。怎么办?使用dlsym函数:

//test.cpp
#include <iostream>
#include <dlfcn.h>
#include "add.h"

int(*add_f)(int, int); //函数指针

int add(int a, int b) {
    std::cout << "my add func" << std::endl;
    return add_f(a, b);
}

int main() {
    void* handle = dlopen("./libadd.so", RTLD_LAZY);
    if(handle) {
        add_f = int(*)(int, int)dlsym(handler, "add");
        
        if(add_f) {
            std::cout << "find to add func" << std::endl;
        } else {
            std::cout << "symbol err" << std::endl;
            dlclose(handle);
            return 0;
        }
    }
    //调用add函数;
    add(1, 2);
    ........
}

分析一下上面的,先声明了一个函数指针,返回值int, 两个参数都是int 。然后在main函数中,通过dlopen函数将libadd.so加载进来,返回一个void*的句柄。

然后就是最重要的一点,通过dlsym函数找到add函数运行时所在地址。如果找到了,add_f就会是这个函数的地址。就这样,找不到的话,可能就是第二个参数不对,第二个参数是链接符号,我这里写成"add",是不准确的,甚至不对的。在我的电脑上准确说是"_Z3addii",因为是cpp文件,如果是c文件,那就是"add";

最后调用add(1, 2),就会输出my add func 和.so add func 。这样,hook就算实现完成了,调用add函数,就会去调用libadd.so里面的add函数。类似的,调用read函数,就会去调用标准库的read函数。

其实这中间还有一下动态链接器的链接过程没说,后面在补充。

链接器的哪些事

有下面这样的一种情况,有3的动态库, libadd1.so、libadd2.so、libadd3.so 这3个库都都有一个一模一样的add函数,就是函数体不一样。并且在编译链接的时候,我都给链接进去了,像这样(gcc test.cpp -o test  -ladd2 -ladd1 -ladd3)你觉得应该使用谁的add函数了,答案是:谁先 -l 则使用谁的。我先-ladd2,则使用libadd2.so的add函数。

假如我的test.cpp里面也有一模一样的add函数了,直接公布答案吧,使用test.cpp里面的add。大家可以直接实验,至少我的是这样的。

所以链接器先在本地源文件里面找有没有这个符号,有则使用,没有则取动态库里面找,库找的顺序是按照编译时 -l 指定的顺序找的。

自己真笨

发现写着写着,自己都不知道这么写了,写了一坨屎,越写越懵。

关于dlsym等一些列函数,还有很多使用的方法和诀窍,我笨,我不知道这么写了。

后续如果有时间,写一篇正式的文章吧,不想写这样的白话文了。

秘密武器

查看动态库里面有哪些链接的符号

1.nm -D 库名

2.objdump -T 库

3.readelf -s 库

查看程序运行时动态库加载的顺序

1.LD_DEBUG=libs 程序名

查看程序的动态库依赖项

1.ldd 程序名

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值