以线程安全的方式使用glibc malloc钩子

时间:2010-01-07 14:39:37

标签: c malloc glibc

我想通过使用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

4 个答案:

答案 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。不要更改钩子回调中的钩子指针,只需调用自己的分配器。