我想通过使用malloc和free hook监视应用程序中mallocs和frees的使用。
以下是文档http://www.gnu.org/s/libc/manual/html_node/Hooks-for-Malloc.html
在示例页面中,您可以看到my_malloc_hook
在重新调用malloc之前暂时关闭malloc挂钩(或链中的前一个挂钩)。
监控多线程应用程序时出现此问题(请参阅问题末尾以获取解释)。
我在互联网上找到的使用malloc钩子的其他例子也存在同样的问题。
有没有办法重写这个函数才能在多线程应用程序中正常工作?
例如,是否有一个内部libc函数,malloc钩子可以调用它完成分配,而不需要停用我的钩子。
由于公司的法律政策,我无法查看libc源代码,所以答案可能很明显。
我的设计规范说我不能用不同的malloc设计替换malloc。
我可以假设没有其他钩子在进行中。
更新
由于在为malloc提供服务时临时删除了malloc钩子,因此另一个线程可能会调用malloc而不是获取钩子。
有人建议malloc有一个很大的锁定来防止这种情况发生,但它没有记录,而且我有效地递归调用malloc的事实表明任何锁必须在钩子之后存在,或者是快乐的聪明:
caller ->
malloc ->
malloc-hook (disables hook) ->
malloc -> # possible hazard starts here
malloc_internals
malloc <-
malloc-hook (enables hook) <-
malloc
caller
答案 0 :(得分:11)
<强>已更新强>
你{@ 3}}不信任__malloc_hooks;我瞥了一眼代码,他们是 - 疯狂地疯狂 - 不是线程安全的。
直接调用继承的钩子,而不是恢复并重新进入malloc,似乎偏离了你引用的文档有点过于让人感觉不舒服。
来自right:
钩子变量不是线程安全的,因此现在不推荐使用它们。程序员应该通过定义和导出“malloc”和“free”等函数来取代对相关函数的调用。
注入调试malloc / realloc / free函数的适当方法是提供自己的库,导出这些函数的“调试”版本,然后将其自身推向真实函数。 C链接以显式顺序完成,因此如果两个库提供相同的功能,则使用第一个指定的。您还可以使用LD_PRELOAD机制在unix上的加载时注入malloc。
http://manpages.sgvulcan.com/malloc_hook.3.php描述了电围栏,详细介绍了这两种方法。
如果需要,可以在这些调试功能中使用自己的锁定。
答案 1 :(得分:3)
我有同样的问题。我用那个例子解决了它。如果我们没有定义THREAD_SAFE,我们有man给出的例子,我们有一个分段错误。 如果我们定义THREAD_SAFE,则没有分段错误。
#include <malloc.h>
#include <pthread.h>
#define THREAD_SAFE
#undef THREAD_SAFE
/** rqmalloc_hook_ */
static void* (*malloc_call)(size_t,const void*);
static void* rqmalloc_hook_(size_t taille,const void* appel)
{
void* memoire;
__malloc_hook=malloc_call;
memoire=malloc(taille);
#ifndef THREAD_SAFE
malloc_call=__malloc_hook;
#endif
__malloc_hook=rqmalloc_hook_;
return memoire;
}
/** rqfree_hook_ */
static void (*free_call)(void*,const void*);
static void rqfree_hook_(void* memoire,const void* appel)
{
__free_hook=free_call;
free(memoire);
#ifndef THREAD_SAFE
free_call=__free_hook;
#endif
__free_hook=rqfree_hook_;
}
/** rqrealloc_hook_ */
static void* (*realloc_call)(void*,size_t,const void*);
static void* rqrealloc_hook_(void* memoire,size_t taille,const void* appel)
{
__realloc_hook=realloc_call;
memoire=realloc(memoire,taille);
#ifndef THREAD_SAFE
realloc_call=__realloc_hook;
#endif
__realloc_hook=rqrealloc_hook_;
return memoire;
}
/** memory_init */
void memory_init(void)
{
malloc_call = __malloc_hook;
__malloc_hook = rqmalloc_hook_;
free_call = __free_hook;
__free_hook = rqfree_hook_;
realloc_call = __realloc_hook;
__realloc_hook = rqrealloc_hook_;
}
/** f1/f2 */
void* f1(void* param)
{
void* m;
while (1) {m=malloc(100); free(m);}
}
void* f2(void* param)
{
void* m;
while (1) {m=malloc(100); free(m);}
}
/** main */
int main(int argc, char *argv[])
{
memory_init();
pthread_t t1,t2;
pthread_create(&t1,NULL,f1,NULL);
pthread_create(&t1,NULL,f2,NULL);
sleep(60);
return(0);
}
答案 2 :(得分:2)
由于对malloc()的所有调用都将通过你的钩子,你可以在信号量上同步(等到它是空闲的,锁定它,玩弄钩子并释放信号量)。
[编辑] IANAL但......如果您可以在代码中使用 glibc,那么您可以查看代码(因为它是LGPL,任何使用它的人都必须被允许有一份来源的副本)。因此,我不确定您是否正确理解了法律状况,或者您可能没有法律允许您的公司使用glibc。
[EDIT2]经过一番思考,我猜这个部分的调用路径必须受到glibc为你创建的某种锁的保护。否则,在多线程代码中使用钩子将无法可靠地工作,我确信文档会提到这一点。由于malloc()
必须是线程安全的,因此钩子也必须是。
如果您仍然担心,我建议编写一个带有两个线程的小测试程序,它们在循环中分配和释放内存。增加钩子中的计数器。经过一百万轮,柜台应该是两百万。如果这种情况成立,那么钩子也会被malloc()
锁保护。
[EDIT3]如果测试失败,那么,由于您的法律情况,无法实施监视器。告诉你的老板让他做出决定。
[EDIT4]谷歌搜索从错误报告中发现了这条评论:
钩子不是线程安全的。期。你想修复什么?
这是自2009年3月关于libc/malloc/malloc.c
中包含修复的错误的讨论的一部分。所以也许在这个日期之后的一个版本的glibc 有效,但似乎没有保证。它似乎也取决于您的GCC版本。
答案 3 :(得分:1)
在递归到malloc时,无法以线程安全的方式使用malloc挂钩。界面设计糟糕,可能无法修复。
即使你在钩子代码中放了一个互斥锁,问题是调用malloc
之前看不到那些锁,直到它们通过钩子机制,并通过钩子机制,它们看起来在全局变量(钩子指针)没有获取你的互斥锁。当你在一个线程中保存,更改和恢复这些指针时,另一个线程中的分配器调用会受到它们的影响。
主要设计问题是默认情况下钩子是空指针。如果接口只提供了非空的默认挂钩,它们是正确的分配器(底层分配器不再调用任何挂钩),那么添加挂钩将是简单而安全的:你可以保存以前的挂钩,并且在新的钩子中,通过调用保持钩子来递归到malloc,而不是摆弄任何全局指针(除了挂钩安装时,可以在任何线程启动之前完成)。
或者,glibc可以提供一个内部malloc接口,它不会调用钩子。
另一个理智的设计是为钩子使用线程局部存储。覆盖和恢复钩子将在一个线程中完成,而不会干扰另一个线程看到的钩子。
就目前而言,你可以安全地使用glibc malloc钩子来避免重复进入malloc。不要更改钩子回调中的钩子指针,只需调用自己的分配器。