14

我想通过使用 malloc 和 free 挂钩来监控应用程序中 malloc 和 free 的使用。

这是文档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

4 回答 4

13

更新

你不信任 __malloc_hooks是对的;我浏览了代码,它们 - 非常疯狂- 不是线程安全的。

直接调用继承的钩子,而不是恢复并重新进入 malloc,似乎偏离了你引用的文档有点太多,感觉不舒服。

来自http://manpages.sgvulcan.com/malloc_hook.3.php

挂钩变量不是线程安全的,因此现在已弃用。相反,程序员应该通过定义和导出“malloc”和“free”等函数来抢占对相关函数的调用。

注入调试 malloc/realloc/free 函数的适当方法是提供您自己的库,该库导出这些函数的“调试”版本,然后将自己推迟到真实版本。C 链接以显式顺序完成,因此如果两个库提供相同的功能,则使用第一个指定的功能。您还可以使用 LD_PRELOAD 机制在 unix 上的加载时注入您的 malloc。

http://linux.die.net/man/3/efence描述了 Electric Fence,详细介绍了这两种方法。

如果有必要,您可以在这些调试功能中使用自己的锁定。

于 2010-01-07T14:54:08.010 回答
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);
 }
于 2010-01-20T07:51:29.567 回答
2

由于对 malloc() 的所有调用都将通过您的钩子,因此您可以在信号量上进行同步(等到它空闲,锁定它,调整钩子并释放信号量)。

[编辑] IANAL 但是...如果您可以在代码中使用glibc,那么您可以查看代码(因为它是 LGPL,所以必须允许任何使用它的人拥有源代码的副本)。因此,我不确定您是否正确理解了法律情况,或者您的公司可能在法律上不允许您使用 glibc。

[EDIT2] 经过一番思考,我想这部分调用路径必须受到 glibc 为您创建的某种锁的保护。否则,在多线程代码中使用钩子永远不会可靠地工作,我相信文档会提到这一点。由于malloc()必须是线程安全的,所以钩子也必须是线程安全的。

如果您仍然担心,我建议编写一个带有两个线程的小型测试程序,它们在循环中分配和释放内存。在钩子中增加一个计数器。一百万轮之后,计数器应该正好是两百万。如果这成立,那么钩子也受到malloc()锁的保护。

[EDIT3] 如果测试失败,那么,由于您的法律情况,无法实施监视器。告诉你的老板,让他做出决定。

[EDIT4] 谷歌搜索从错误报告中找到了这条评论:

钩子不是线程安全的。时期。你想解决什么问题?

这是 2009 年 3 月关于libc/malloc/malloc.c包含修复的错误的讨论的一部分。所以也许这个日期之后的 glibc 版本可以工作,但似乎不能保证。它似乎也取决于您的 GCC 版本。

于 2010-01-07T14:51:20.213 回答
2

在递归到 malloc 时,无法以线程安全的方式使用 malloc 钩子。界面设计糟糕,可能无法修复。

即使你在你的钩子代码中放置了一个互斥锁,问题是调用malloc不会看到这些锁,直到它们通过钩子机制,并且通过钩子机制,它们查看全局变量(钩子指针)无需获取您的互斥锁。当您在一个线程中保存、更改和恢复这些指针时,另一个线程中的分配器调用会受到它们的影响。

主要的设计问题是钩子默认是空指针。如果接口只是提供了非空默认钩子,它们是适当的分配器(底层分配器不再调用任何钩子),那么添加钩子将是简单且安全的:您可以保存以前的钩子,并且在新的钩子中,通过调用保持钩子递归到 malloc,而不用摆弄任何全局指针(除了在钩子安装时,这可以在任何线程启动之前完成)。

或者,glibc 可以提供一个不调用钩子的内部 malloc 接口。

另一种合理的设计是使用线程本地存储来存储挂钩。覆盖和恢复钩子将在一个线程中完成,而不会干扰另一个线程看到的钩子。

就目前而言,要安全地使用 glibc malloc 挂钩,您可以做的是避免递归到 malloc。不要更改挂钩回调中的挂钩指针,只需调用您自己的分配器即可。

于 2014-03-06T01:51:54.910 回答