Использование крючков glibc malloc в потоковом режиме

Я хотел бы следить за использованием mallocs и frees в приложении, используя malloc и free hooks.

Здесь документация http://www.gnu.org/s/libc/manual/html_node/Hooks-for-Malloc.html

На странице примера вы можете видеть, что my_malloc_hook временно переключает галочку malloc (или на предыдущий крючок в цепочке) перед повторным вызовом malloc.

Это проблема при мониторинге многопоточных приложений (см. конец вопроса для объяснения).

Другие примеры использования крюка malloc, который я нашел в Интернете, имеют одинаковую проблему.

Есть ли способ перезаписать эту функцию для правильной работы в многопоточном приложении?

Например, есть ли встроенная функция libc, которую может вызвать hook-ключ malloc, который завершает выделение, без необходимости деактивировать мой крючок.

Я не могу смотреть на исходный код libc из-за корпоративной правовой политики, поэтому ответ может быть очевиден.

Моя спецификация дизайна говорит, что я не могу заменить malloc другим дизайном malloc.

Я могу предположить, что в игре нет других крючков.


ОБНОВЛЕНИЕ

Так как malloc-крючок временно удаляется во время обслуживания malloc, другой поток может вызывать malloc и NOT get hook.

Было высказано предположение, что malloc имеет большой замок вокруг него, который предотвращает это, но он не задокументирован, и тот факт, что я фактически рекурсивно называю malloc, предполагает, что любая блокировка должна существовать после крючка или быть умно умной:

caller -> 
  malloc -> 
    malloc-hook (disables hook) -> 
      malloc -> # possible hazard starts here
        malloc_internals
      malloc <-
    malloc-hook (enables hook) <-
  malloc
caller

Ответ 1

ОБНОВЛЕНО

Вы right, чтобы не доверять __malloc_hooks; Я взглянул на код, и они - ошеломляюще безумно - не потокобезопасны.

Вызов унаследованных перехватчиков напрямую, а не восстановление и повторный ввод malloc, кажется, отклоняется от документа, который вы цитируете немного, чтобы чувствовать себя комфортно.

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

Крюковые переменные не являются потокобезопасными, поэтому они устарели. Программисты должны вместо этого вызывать вызовы для соответствующих функций, определяя и экспортируя такие функции, как "malloc" и "free".

Соответствующий способ введения функций debug malloc/realloc/free состоит в том, чтобы предоставить свою собственную библиотеку, которая экспортирует ваши "отладочные" версии этих функций, а затем отказывается от реальных. C выполняется в явном порядке, поэтому, если две библиотеки предлагают одну и ту же функцию, используется первая указанная. Вы также можете ввести свой malloc во время загрузки в unix с помощью механизмов LD_PRELOAD.

http://linux.die.net/man/3/efence описывает электрический забор, в котором подробно описываются оба этих подхода.

Вы можете использовать свою собственную блокировку, если в этих функциях отладки, если это необходимо.

Ответ 2

У меня та же проблема. Я решил это на этом примере. Если мы не определяем 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);
 }

Ответ 3

Так как все вызовы malloc() пройдут через ваш крючок, вы можете синхронизировать их на семафоре (подождите, пока он не станет свободным, заблокируйте его, жонглируйте крючками и освободите семафор).

[EDIT] IANAL, но... Если вы можете использовать glibc в своем коде, вы можете посмотреть на код (так как LGPL, любому, кто его использует, должно быть разрешено иметь копию источника). Поэтому я не уверен, что вы правильно поняли правовую ситуацию или, возможно, вам не разрешено юридически использовать glibc вашей компанией.

[EDIT2] После некоторого размышления, я полагаю, что эта часть пути вызова должна быть защищена какой-то замкой, созданной для вас glibc. В противном случае использование перехватов в многопоточном коде никогда не будет работать надежно, и я уверен, что документы будут упоминать об этом. Поскольку malloc() должен быть потокобезопасным, крючки должны быть также.

Если вы все еще беспокоитесь, я предлагаю написать небольшую тестовую программу с двумя потоками, которые выделяют и освобождают память в цикле. Увеличьте счетчик в крюке. После миллиона раундов счетчик должен быть ровно в два миллиона. Если это выполняется, то крючок также защищен блокировкой malloc().

[EDIT3] Если тест не удался, тогда из-за вашей юридической ситуации невозможно реализовать монитор. Расскажите своему боссу и дайте ему принять решение об этом.

[EDIT4] Googling поднял этот комментарий из отчета об ошибке:

Крюки не являются потокобезопасными. Период. Что вы пытаетесь исправить?

Это часть обсуждения с марта 2009 года об ошибке в libc/malloc/malloc.c, которая содержит исправление. Так что, возможно, версия glibc после этой даты работает, но, похоже, нет гарантии. Это также зависит от вашей версии GCC.

Ответ 4

Невозможно использовать перехватчики malloc в потокобезопасном режиме при рекурсии в malloc. Интерфейс плохо разработан, вероятно, не подлежит ремонту.

Даже если вы помещаете мьютексы в код вашего крючка, проблема в том, что вызовы в malloc не видят эти блокировки до тех пор, пока они не пройдут через механизм крючка и не пройдут через механизм hook, они переменные (указатели на крючки), не приобретая ваш мьютекс. Когда вы сохраняете, изменяете и восстанавливаете эти указатели в одном потоке, вызовы распределителей в другом потоке затрагиваются ими.

Основная проблема дизайна заключается в том, что по умолчанию крючки являются нулевыми указателями. Если интерфейс просто предоставил ненулевые крючки по умолчанию, которые являются собственно распределителем (распределитель нижнего уровня, который больше не вызывает перехваты), тогда было бы просто и безопасно добавлять крючки: вы могли бы просто сохранить предыдущие перехватчики, и в новых крючках, перейдите в malloc, вызвав крюки для удержания, не путаясь с любыми глобальными указателями (кроме времени установки на крючок, которое может быть выполнено до начала любого потока).

В качестве альтернативы glibc может предоставить внутренний интерфейс malloc, который не вызывает крючки.

Другим разумным дизайном будет использование локального хранилища потоков для крючков. Переопределение и восстановление крюка будет выполняться в одном потоке, не нарушая крючки, видимые другим потоком.

Как бы то ни было, вы можете безопасно использовать glibc malloc-крюк, чтобы избежать рекурсии в malloc. Не меняйте указатели на крючки внутри обратных вызовов крюка и просто вызывайте свой собственный распределитель.