对于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 程序名
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/11377.html