Загадочные утечки памяти в инструменте отслеживания блокировок на основе предварительного загрузчика

Я работаю над инструментом отслеживания блокировок, предназначенным для подключения к приложениям на основе Pthreads с помощью LD_PRELOAD, и столкнулся со странной проблемой. Когда тестовое приложение запускается под управлением valgrind с подключенным моим трассировщиком, оно сообщает о нескольких утечках памяти, происходящих в pthread_cond_signal()/wait() libpthread (мой инструмент скрывает эти функции для реализации функции трассировки). Эти утечки не происходят, когда мой инструмент не прикреплен. Образец отчета об утечке:

==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

И, для полноты, вот соответствующий код glibc для pthread_cond_signal (идентичный pthread_cond_wait, за исключением вызова функции возврата):

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);
}

Тестовая программа (к которой подключается мой инструмент) очищает свои условные переменные перед выходом (и, как уже упоминалось, не имеет утечек памяти при запуске без инструмента). Я довольно озадачен этим, у вас есть идеи? Я уверен, что это что-то простое, что смотрит мне в лицо, это всегда так...


person George Hodgkins    schedule 13.11.2020    source источник
comment
Престижность за отличный вопрос, с правильным количеством информации ИМХО. ????????????   -  person Kuba hasn't forgotten Monica    schedule 13.11.2020


Ответы (1)


Держу пари, что если вы:

  1. Измените имя вашей функции на другое, кроме pthread_cond_wait, скажем, pthread_cond_wait_my,

  2. Создайте небольшой тестовый фрагмент, который вызывает вариант _my,

  3. Создайте минимальную разделяемую библиотеку с фиктивной реализацией pthread_cond_wait_my и свяжите с ней фрагмент (точно так же, как он свяжется с libpthread),

  4. Запустите этот фрагмент с вашей библиотекой трассировки, полученной через LD_PRELOAD, как и раньше,

... не будет сообщений об утечке, даже если ваша библиотека будет делать то же самое, только под другим именем :)

Если это так, то утечка для вас нова, но на самом деле она не нова: это подлинная утечка в библиотеке pthreads, которая обычно подавляется в диагностическом выводе. Valgrind поставляется с целой кучей подавлений для библиотек времени выполнения - иначе это было бы безумно шумно. Но ваш инструмент предоставляет символ pthread_cond_wait, и поэтому Valgrind неправильно применяет подавление к вашей функции, а не к той, для которой он предназначался (в библиотеке времени выполнения).

person Kuba hasn't forgotten Monica    schedule 13.11.2020
comment
Да, это было оно. Большое спасибо! Будущим читателям очень легко создавать свои собственные подавления, чтобы избежать подобных вещей. Я использовал это руководство: wiki.wxwidgets.org/Valgrind_Suppression_File_Howto. - person George Hodgkins; 13.11.2020