前言
LD_PRELOAD和ld --wrap都能实现不修改原始代码,替换指定函数的实现。通常我们会使用这些方法,替换如malloc)()/free()、read()/write()等函数,并在替换函数中做一些记录,以便能分析程序执行时的内存分配和IO情况。这些函数一般叫包裹函数。
LD_PRELAD
启动程序时,链接器会做符号绑定,一般是按照编译时指定的链接顺序,查找动态库对应的符号地址。LD_PRELOAD指定的库优先级最高,因此就会先绑定该库中的符号地址。
以malloc()/free()为例
代码清单 main.c
int main()
{
malloc(10);
return 0;
}
代码清单 my.c
void* malloc(size_t size)
{
void (*__real_malloc)(int) = NULL;
// 获取真实的malloc()地址
__real_malloc = dlsym(RTLD_NEXT, "malloc");
void* ptr = __real_malloc(size);
printf("[malloc] addr=%p, size=%u\n", ret, size);
return ptr;
}
void free(void* ptr)
{
printf("[free] addr=%p", ptr);
void (*__real_free)(void*) = NULL;
__real_free = dlsym(RTLD_NEXT, "free");
return __real_free(ptr);
}
gcc main.c -o test
将main.c编译成test
,gcc -fPIC -shared my.c -o libmy.so
my.c编译成libmy.so
。
LD_PRELOAD=./libmy.so test
后执行test
会发现main()
中执行的malloc()
实际是libmy.so
中的malloc()
,其会输出分配的内存地址和大小。因为没有free的输出,所以我们可以得知test存在内存泄漏。这是一个很简陋的例子。
export使得LD_PRELOAD对所有该终端执行的程序都生效,不使用时还需要unset LD_PRELOAD
,我们可以通过LD_PRELOAD=./libmy.so ./test
使得其只对test生效。
LD_PRELOAD的本质是先加载的先使用。就像我们在编译时会发现如果两个库中有同名函数,那么哪个库在前,就用哪个库中的函数。
可以使用LD_DEBUG=all LD_PRELOAD=./libmy.so ./test
观察链接器是如何绑定符号的。
ld --wrap
ld --wrap是一个编译链接参数,其原理是在编译时将--wrap指定的函数名进行替换,比如gcc main.c -o test -Wl,--wrap=malloc
,编译器会将调用malloc()
的地方替换为__wrap_malloc()
,将__real_malloc()
替换为malloc()
此时my.c的实现就不一样了,我们需要提供__wrap_malloc()
,并在真正需要调用malloc()
的地方使用__real_malloc()
。
void* __wrap_malloc(size_t size)
{
void* ptr = __real_malloc(size);
printf("[malloc] addr=%p, size=%u\n", ret, size);
return ptr;
}
void __wrap_free(void* ptr)
{
printf("[free] addr=%p", ptr);
return __real_free(ptr);
}
如果有比较多的--wrap,那么可以将其放在一个文件中,假设文件名为ld-opt,其内容如下
--wrap=malloc
--wrap=free
使用gcc main.c -o test -Wl,@ld-opt
就能替换malloc和free了。
通过nm
查看可以发现并不存在__real
前缀的函数。