基于预加载器的锁跟踪工具中的神秘内存泄漏

Mysterious memory leaks in preloader-based lock tracing tool

我正在开发一个锁定跟踪工具,该工具旨在使用 LD_PRELOAD 附加到基于 Pthreads 的应用程序,但我遇到了一个奇怪的问题。当一个测试应用程序在 valgrind 下 运行 并附加了我的跟踪器时,它报告了几个源自 libpthread 的 pthread_cond_signal()/wait() 的内存泄漏(我的工具隐藏了这些函数以实现跟踪功能)。当我的工具未连接时,不会发生这些泄漏。泄漏报告样本:

==12993== 48 bytes in 1 blocks are definitely lost in loss record 1 of 6                       
==12993==    at 0x483DD99: calloc (vg_replace_malloc.c:762)                                    
==12993==    by 0x48C8629: pthread_cond_wait@GLIBC_2.2.5 (old_pthread_cond_wait.c:34)          
==12993==    by 0x48775EF: pthread_cond_wait (pthread_trace.cpp:39)                            
==12993==    by 0x10C060: shard_get (shard.c:68)                                               
==12993==    by 0x10BC38: resolver_thread (req_res.c:74)                                                                                                                                       
==12993==    by 0x487789A: inject_thread_registration(void*) (pthread_trace.cpp:85)                                                                                                            
==12993==    by 0x48C0608: start_thread (pthread_create.c:477)                                 
==12993==    by 0x49FC292: clone (clone.S:95)  

我不知道为什么会这样,因为我的代码根本不与 Pthreads 对象交互,除了获取它们的地址进行日志记录。这是我的包装函数的代码:

int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* lk) {
        // log arrival at wait
        the_tracer.add_event(lktrace::event::COND_WAIT, (size_t) cond);
        // run pthreads function
        GET_REAL_FN(pthread_cond_wait, int, pthread_cond_t*, pthread_mutex_t*);
        int e = REAL_FN(cond, lk);
        if (e == 0) the_tracer.add_event(lktrace::event::COND_LEAVE, (size_t) cond);
        else the_tracer.add_event(lktrace::event::COND_ERR, (size_t) cond);
        return e;
}

int pthread_cond_signal(pthread_cond_t* cond) {
        // log cond signal
        the_tracer.add_event(lktrace::event::COND_SIGNAL, (size_t) cond);
        // run pthreads function
        GET_REAL_FN(pthread_cond_signal, int, pthread_cond_t*);
        return REAL_FN(cond);
}

// GET_REAL_FN definition:
#define GET_REAL_FN(name, rtn, params...) \
        typedef rtn (*real_fn_t)(params); \
        static const real_fn_t REAL_FN = (real_fn_t) dlsym(RTLD_NEXT, #name); \
        assert(REAL_FN != NULL) // semicolon absence intentional

并且,为了完整起见,这里是 pthread_cond_signal 的相关 glibc 代码(与 pthread_cond_wait 相同,除了 return 函数调用):

int
__pthread_cond_signal_2_0 (pthread_cond_2_0_t *cond)
{
  if (cond->cond == NULL)
    {
      pthread_cond_t *newcond;

      newcond = (pthread_cond_t *) calloc (sizeof (pthread_cond_t), 1); // leak alloc'd here
      if (newcond == NULL)
        return ENOMEM;

      if (atomic_compare_and_exchange_bool_acq (&cond->cond, newcond, NULL))
        /* Somebody else just initialized the condvar.  */
        free (newcond);
    }

  return __pthread_cond_signal (cond->cond);
}

测试程序(我的工具附加到)在退出之前确实清理了它的 condvars(并且如前所述,在 运行 没有该工具时没有内存泄漏)。我对此很困惑,你们有什么想法吗?我敢肯定这是一件很简单的事情,它一直盯着我看,它总是...

我打赌如果你:

  1. 将您的函数名称更改为 pthread_cond_wait 以外的名称,例如 pthread_cond_wait_my

  2. 创建一个调用 _my 变体的小测试片段,

  3. 创建一个最小的共享库,它有一个 pthread_cond_wait_my 的虚拟实现,link 带有它的片段(就像 link 带有 libpthread),

  4. 运行 那个片段,你的跟踪库通过 LD_PRELOAD 引入,就像以前一样,

...不会有泄漏报告,即使您的库仍然完全相同,只是名称不同:)

如果是这种情况,那么泄漏对您来说是“新的”,但实际上并不是新的:它是 pthreads 库中的真正泄漏,通常从诊断输出中抑制。 Valgrind 对运行时库进行了一大堆抑制——否则它会非常嘈杂。但是您的工具提供了 pthread_cond_wait 符号,因此 Valgrind 错误地将抑制应用于 您的 函数,而不是它原本打算(在运行时库中)的函数。