在处理问题时,怀疑某个动态的api 调用有问题, 但是代码太多不想找,于是就直接hook 此api,打印出参数值判断此处问题!
具体业务就不展示,在此举一个例子记录一下当前使用的方法
#define _GNU_SOURCE #include <dlfcn.h> #include <stdio.h> #include <stdlib.h> typedef void*(* orig_malloc_fun) (size_t size); void* malloc (size_t size) { orig_malloc_fun orig_malloc; orig_malloc = dlsym(RTLD_NEXT,"malloc"); void * tmp = orig_malloc(size); fprintf(stderr, "%s %d %p ", __FILE__, __LINE__, tmp); return tmp; }
[root@localhost ~]# gcc -fpic --shared hk_mm.c -o hk_mm.so [root@localhost ~]# LD_PRELOAD=/root/hk_mm.so ls -l hk_mm.c 13 0x15eb040 hk_mm.c 13 0x15eb060 hk_mm.c 13 0x15eb080 hk_mm.c 13 0x15eb060 hk_mm.c 13 0x15eb100 hk_mm.c 13 0x15eb410 hk_mm.c 13 0x15eb490 ---------------
此处核心点就是使用dlsym 函数
顺便说一说dlopen dlsym dlclose!!
dlopen()
这个函数加载由以null结尾的字符串文件名命名的动态共享对象(共享库)文件,并为加载的对象返回不透明的“句柄”。此句柄与 dlopen API 中的其他函数一起使用,例如dlsym()
,dladdr()
,dlinfo()
和dlclose()
。
如果 filename 为 NULL,则返回的句柄用于主程序。如果 filename 包含斜杠(“/”),则它被解释为(相对或绝对)路径名。否则,动态链接器将按如下方式搜索对象(有关详细信息,请参阅ld.so(8)
):
- (仅限ELF)如果调用程序的可执行文件包含 DT_RPATH 标记,并且不包含 DT_RUNPATH 标记,则会搜索 DT_RPATH 标记中列出的目录。
- 如果在程序启动时,环境变量 LD_LIBRARY_PATH 被定义为包含以冒号分隔的目录列表,则会搜索这些目录。 (作为安全措施,set-user-ID 和 set-group-ID程序将忽略此变量。)
- (仅限ELF)如果调用程序的可执行文件包含 DT_RUNPATH 标记,则搜索该标记中列出的目录。
- 检查缓存文件/etc/ld.so.cache(由ldconfig(8)维护)以查看它是否包含filename的条目。
- 搜索目录 /lib和 /usr/lib(按此顺序)。
如果 filename 指定的对象依赖于其他共享对象,则动态链接器也会使用相同的规则自动加载这些对象。 (如果这些对象依次具有依赖性,则此过程可以递归地发生)
flags 参数必须包括以下两个值中的一个:
- RTLD_LAZY
执行延迟绑定。仅在执行引用它们的代码时解析符号。如果从未引用该符号,则永远不会解析它(只对函数引用执行延迟绑定;在加载共享对象时,对变量的引用总是立即绑定)。自 glibc 2.1.1,此标志被LD_BIND_NOW环境变量的效果覆盖。 - RTLD_NOW
如果指定了此值,或者环境变量LD_BIND_NOW设置为非空字符串,则在dlopen()
返回之前,将解析共享对象中的所有未定义符号。如果无法执行此操作,则会返回错误。
flags 也可以通过以下零或多个值进行或运算设置:
- RTLD_GLOBAL
此共享对象定义的符号将可用于后续加载的共享对象的符号解析。 - RTLD_LOCAL
这与RTLD_GLOBAL相反,如果未指定任何标志,则为默认值。此共享对象中定义的符号不可用于解析后续加载的共享对象中的引用。 - RTLD_NODELETE (since glibc 2.2)
在dlclose()
期间不要卸载共享对象。因此,如果稍后使用dlopen()
重新加载对象,则不会重新初始化对象的静态变量。 - RTLD_NOLOAD (since glibc 2.2)
不要加载共享对象。这可用于测试对象是否已经驻留(如果不是,则dlopen()
返回 NULL,如果是驻留则返回对象的句柄)。此标志还可用于提升已加载的共享对象上的标志。例如,以前使用RTLD_LOCAL加载的共享对象可以使用RTLD_NOLOAD | RTLD_GLOBAL重新打开。 - RTLD_DEEPBIND (since glibc 2.3.4)
将符号的查找范围放在此共享对象的全局范围之前。这意味着自包含对象将优先使用自己的符号,而不是全局符号,这些符号包含在已加载的对象中。
dlclose()
dlclose()
减少指定句柄 handle 引用的动态加载共享对象的引用计数。如果引用计数减少为0,那么这个动态加载共享对象将被真正卸载。所有在dlopen()
被调用的时候自动加载的共享对象将以相同的方式递归关闭。
dlclose()
成功返回并不保证与句柄相关的符号将从调用方的地址空间中删除。除了显式通过dlopen()
调用产生的引用之外,一些共享对象作为依赖项可能已被隐式加载(和引用计数)。只有当所有引用都已被释放才可以从地址空间中删除共享对象。
初始化和终结功能
共享对象可以使用__attribute __((constructor))和__attribute __((destructor))函数属性。构造函数在dlopen()
返回之前执行,而析构函数在dlclose()
返回之前执行。共享对象可以导出多个构造函数和析构函数并且优先顺序可以和每个函数相关联来决定它们的执行顺序。
DLSYM
dlsym,- 获取共享对象或可执行文件中符号的地址
dlsym()
接受由dlopen()
返回的动态加载的共享对象的“句柄”,并返回该符号加载到内存中的地址。如果未找到符号,则在加载该对象时,在指定对象或dlopen()
自动加载的任何共享对象中,dlsym()
将返回NULL。(dlsym()
通过这些共享对象的依赖关系树进行宽度优先搜索。)
因为符号本身可能是 NULL(所以dlsym()
返回 NULL 并不意味着错误),因此判断是否错误的正确做法是调用dlerror()
清除任何旧的错误条件,然后调用dlsym()
,并且再次调用dlerror()
,保存其返回值,判断这个保存的值是否是 NULL。
可以在句柄中指定两个特殊的伪句柄:
- RTLD_DEFAULT
使用默认共享对象搜索顺序查找所需符号的第一个匹配项。搜索将包括可执行文件中的全局符号及其依赖项,以及使用RTLD_GLOBAL 标志动态加载的共享对象中的符号。 - RTLD_NEXT
在当前对象之后的搜索顺序中查找下一个所需符号。这允许人们在另一个共享对象中提供一个函数的包装器,因此,例如,预加载的共享对象中的函数定义(参见ld.so(8)中的LD_PRELOAD)可以找到并调用在另一个共享对象中提供的“真实”函数(或者就此而言,在存在多个预加载层的情况下,函数的“下一个”定义)。一般使用ldd(List Dynamic Dependencies)指令查看库的加载顺序
dladdr()--将地址转换为符号信息
dladdr()确定addr中指定的地址是否位于调用应用程序加载的共享对象之一中。如果是,则dladdr()返回有关共享对象和与addr重叠的符号的信息。此信息以Dl_info结构返回:
dlinfo()函数获取有关句柄引用的动态加载对象的信息(通常是通过对dlopen(3)或dlmopen(3)的较早调用获得的)。 request参数指定要返回的信息。 info参数是一个指向缓冲区的指针,该缓冲区用于存储调用返回的信息。此参数的类型取决于请求。