【问题标题】:using glibc malloc hooks in a thread safe manner以线程安全的方式使用 glibc malloc 钩子
【发布时间】:2011-01-02 12:31:33
【问题描述】:

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

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

从示例页面中您可以看到my_malloc_hook 在重新调用 malloc 之前会暂时关闭 malloc 挂钩(或切换到链中的前一个挂钩)。

这是监控多线程应用程序时的问题(请参阅问题末尾的解释)。

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

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

例如,是否有一个内部 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

【问题讨论】:

  • 如果我们中的任何一个人查看了 libc 源并根据它为您提供信息,那么您将在法律上处于相同的位置。
  • 为什么你不能看看 libc源代码?
  • 因为我可能会用 GPL 代码污染我们的专有代码。只是被告知一个特定的功能会做我想做的事情并没有这个问题。
  • 这是一个疯狂的偏执律师的事情,但我必须遵守规则,或者在其他地方工作。
  • 当线程冷却后,我会在一天左右的时间内接受其中一个答案。

标签: c malloc glibc


【解决方案1】:

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

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

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

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

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

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

【讨论】:

    【解决方案2】:

    更新

    你是 right 不信任 __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,其中详细介绍了这两种方法。

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

    【讨论】:

    • 问题可能是:在调用钩子之前是否会获得锁,还是会在 malloc() 中发生这种情况?我猜如果没有在外面发生锁定,钩子将毫无用处,但我想知道递归调用是如何工作的。
    • 递归调用可以使用递归锁——一旦线程拥有锁,就可以多次获取它。
    • 嗯,这可能是真的,但除非我知道这是真的,否则我不能使用它,因为它可能会损坏。另外我不知道未来的 malloc 实现是否允许多个 malloc 区域使用单独的锁来增强多线程性能。
    • 当它坏了,报告错误!在其他新闻中,您知道 longjmp 不是线程安全的吗?
    • 如果 1) 它不是一个错误,它是如何工作的 2) 错误修复不会及时到达以帮助我的客户,那么报告这个错误是没有好处的。你有longjmp问题的参考吗?我广泛使用它。
    【解决方案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);
     }
    

    【讨论】:

      【解决方案4】:

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

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

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

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

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

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

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

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

      【讨论】:

      • 我的公司不允许我查看 GPL 代码。这是规则。
      • 由于钩子代码必须在重新调用 malloc 之前删除钩子代码,所以在我取消钩子时调用 malloc 的第二个线程将不会使用钩子。
      • @Alex 我猜这意味着您不允许查看或使用 GPL 代码?
      • “公司律师这么说”是您可能会遇到的问题,但您需要确保让您的工作人员知道这会给您的工作能力带来负担,并且作为一个业务流程您不能指望互联网上的随机人员每次遇到问题时都会为您查看源代码,并观察其他公司能够管理此问题以一种不会过度削弱开发过程的方式解决问题。
      • .... 并不是说​​我们一般不喜欢提供帮助,而是“我完全有能力阅读源代码但我不会" 可能会在社区(并引发 IANAL 对此程度的讨论)、公司政策或没有公司政策方面产生一点点不满。如果你的公司(隐喻地)蒙住你的眼睛,把你的手绑在背后,那是他们的特权,但他们不能指望我们为你做所有的工作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-11
      • 2011-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多