2010-01-07 61 views
11

我想通过使用malloc和自由钩来监视应用程序中malloc和free的使用。使用glibc malloc挂钩以线程安全的方式

这里的文档http://www.gnu.org/s/libc/manual/html_node/Hooks-for-Malloc.html

从示例页面,你可以看到my_malloc_hook瞬时切换的malloc重新调用malloc的前摘机(或在链中前钩)。

当监视多线程应用程序时,这是一个问题(请参阅问题的结尾处的解释)。

我在互联网上找到的使用malloc挂钩的其他例子也有同样的问题。

有没有办法重写这个函数在多线程应用程序中正常工作?

例如,是否有malloc挂钩可以调用的内部libc函数来完成分配,而无需停用我的挂钩。

由于公司法律政策,我无法看看libc源代码,所以答案可能很明显。

我的设计规范说我不能用一个不同的malloc设计替换malloc。

我可以假设没有其他钩子在场。


UPDATE

由于在维修中的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

如果我们其中一个人查看libc源代码并根据它给出信息,您将合法地处于相同的位置。 – 2010-01-07 14:48:28

+0

为什么你不看* libc源代码? – Will 2010-01-07 14:55:01

+0

因为我可能会用GPL代码污染我们的专有代码。简单地被告知一个特定的功能会做我想做的事情,但没有这个问题。 – 2010-01-07 15:13:15

回答

8

修订

您是right不信任__malloc_hooks;我已经在代码扫了一眼,他们是 - 惊人的疯狂 - 不是线程安全的。

直接调用继承挂钩,而不是恢复并重新进入的malloc,似乎是从文档偏离你举一个有点太多感觉舒适暗示。

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

挂钩变量不是线程安全的所以他们现在已经过时。程序员应该通过定义和导出像“malloc”和“free”这样的函数来抢占对相关函数的调用。

适当的方式注入调试的malloc/realloc的/自由的功能是提供自己的库,出口这些功能你的“调试”版本,然后推迟自己的真正所在。 C链接按照明确的顺序完成,因此如果两个库提供相同的功能,则使用第一个指定的功能。您也可以使用LD_PRELOAD机制在unix上的加载时注入您的malloc。

http://linux.die.net/man/3/efence介绍了电子围栏,里面详细介绍这两种方法。

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

+0

问题可能是:在调用挂接之前获取锁还是在malloc()内部发生?我猜这些钩子在外面没有发生锁定的情况下是没用的,但是我想知道递归调用是如何工作的。 – 2010-01-07 14:56:12

+0

递归调用可以使用递归锁定 - 一旦线程拥有锁定,就可以多次获取它。 – Novelocrat 2010-01-07 15:08:47

+0

那么,这可能是真的,但除非我知道这是真的,否则我不能使用它,因为它可能会破坏。此外,我不知道未来的malloc实现是否可以允许具有单独锁定的多个malloc区域来增强多线程性能。 – 2010-01-07 15:15:58

2

由于所有调用malloc()会经过你的钩子,你可以在同步信号(等到它是免费的,将其锁定,忙里忙外的挂钩和释放的信号)。

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

经过一番思考,我想这部分调用路径必须由glibc为您创建的某种类型的锁保护。否则,在多线程代码中使用钩子将无法可靠地工作,我相信这些文档会提到这一点。由于malloc()必须是线程安全的,因此钩子也必须是一样的。

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

[EDIT3如果测试失败,那么,因为你的法律地位,其实施监控是不可能的。告诉你的老板,让他做出决定。

[EDIT4]谷歌搜索从一个bug报告止跌回升此评论:

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

这是从2009年3月约一个错误libc/malloc/malloc.c其中包含一个修复的讨论的一部分。所以也许在这个日期后glibc 的版本有效,但似乎没有保证。它似乎也取决于你的GCC版本。

+0

我不允许我的公司查看GPL代码。他们的规则。 – 2010-01-07 15:12:29

+0

因为在重新调用malloc之前,钩子代码必须删除钩子代码,所以在我已经解除挂钩的情况下调用malloc的第二个线程将不会使用该钩子。 – 2010-01-07 15:14:44

+0

@Alex我猜这意味着您不允许查看或使用GPL代码? – 2010-01-07 15:17:46

3

我有同样的问题。我用这个例子解决了它。如果我们没有定义THREAD_SAFE,我们就给出了这个人给出的例子,并且我们有一个分割错误。 如果我们定义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); 
} 
0

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

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

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

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

另一个理智的设计是使用线程本地存储的钩子。重写和恢复一个钩子将在一个线程中完成,而不会干扰另一个线程看到的钩子。

现在,您可以安全地使用glibc malloc钩子,以避免递归到malloc中。不要更改钩子回调中的钩子指针,只需调用你自己的分配器。