在尝试了一些事情之后,我终于弄清楚了如何做到这一点。
首先,在glibc
, malloc
被定义为弱符号,这意味着它可以被应用程序或共享库覆盖。因此,LD_PRELOAD
不一定需要。相反,我在共享库中实现了以下函数:
void*
malloc (size_t size)
{
[ ... ]
}
由应用程序调用而不是glibc
s malloc
.
现在,相当于__malloc_hook
的功能,仍然缺少一些东西。
1.) 调用者地址
除了原来的参数malloc
, glibc
s __malloc_hook
s还提供了调用函数的地址,实际上就是where的返回地址malloc
会回到。为了达到同样的目的,我们可以使用__builtin_return_address
gcc 中可用的函数。我没有研究过其他编译器,因为无论如何我都仅限于 gcc,但是如果您碰巧知道如何可移植地执行此类操作,请给我留言:)
Our malloc
函数现在看起来像这样:
void*
malloc (size_t size)
{
void *caller = __builtin_return_address(0);
[ ... ]
}
2.) 访问glibc
s malloc 从你的钩子中
由于我的应用程序仅限于 glibc,因此我选择使用__libc_malloc
访问原始的 malloc 实现。或者,dlsym(RTLD_NEXT, "malloc")
可以使用,但该函数使用可能存在陷阱calloc
第一次调用时,可能会导致无限循环,从而导致段错误。
完整的 malloc 钩子
我的完整挂钩函数现在如下所示:
extern void *__libc_malloc(size_t size);
int malloc_hook_active = 0;
void*
malloc (size_t size)
{
void *caller = __builtin_return_address(0);
if (malloc_hook_active)
return my_malloc_hook(size, caller);
return __libc_malloc(size);
}
where my_malloc_hook
看起来像这样:
void*
my_malloc_hook (size_t size, void *caller)
{
void *result;
// deactivate hooks for logging
malloc_hook_active = 0;
result = malloc(size);
// do logging
[ ... ]
// reactivate hooks
malloc_hook_active = 1;
return result;
}
当然,钩子calloc
, realloc
and free
工作方式类似。
动态和静态链接
有了这些函数,动态链接就可以开箱即用。链接包含 malloc 挂钩实现的 .so 文件将导致所有调用malloc
来自应用程序以及所有要通过我的钩子路由的库调用。但静态链接是有问题的。我还没有完全理解它,但在静态链接中 malloc 不是一个弱符号,导致链接时出现多重定义错误。
如果您出于某种原因需要静态链接,例如通过调试符号将第 3 方库中的函数地址转换为代码行,那么您可以静态链接这些第 3 方库,同时仍然动态链接 malloc 挂钩,从而避免多重定义问题。我还没有找到更好的解决方法,如果你知道,请随时给我留言。
这是一个简短的例子:
gcc -o test test.c -lmalloc_hook_library -Wl,-Bstatic -l3rdparty -Wl,-Bdynamic
3rdparty
将被静态链接,同时malloc_hook_library
将动态链接,从而产生预期的行为以及函数的地址3rdparty
可通过调试符号进行翻译test
。很整洁,是吧?
结论
上面的技术描述了一种未弃用的、几乎等效的方法__malloc_hook
s,但有一些明显的限制:
__builtin_caller_address
仅适用于gcc
__libc_malloc
仅适用于glibc
dlsym(RTLD_NEXT, [...])
是一个 GNU 扩展glibc
链接器标志-Wl,-Bstatic
and -Wl,-Bdynamic
特定于 GNU binutils。
换句话说,这个解决方案是完全不可移植的,如果要将钩子库移植到非 GNU 操作系统,则必须添加替代解决方案。