используя glibc malloc hooks потокобезопасным способом - PullRequest
12 голосов
/ 07 января 2010

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

Вот документация 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.

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


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

Ответы [ 4 ]

11 голосов
/ 07 января 2010

ОБНОВЛЕНО

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

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

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

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

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

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

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

3 голосов
/ 20 января 2010

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

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

[РЕДАКТИРОВАТЬ] IANAL, но ... Если вы можете использовать glibc в своем коде, тогда вы можете посмотреть на код (поскольку это LGPL, любой, кто его использует , должен быть разрешенным иметь копию источника). Поэтому я не уверен, что вы правильно поняли правовую ситуацию или, возможно, вам не разрешено юридически использовать glibc вашей компанией.

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

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

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

[EDIT4] Googling включил этот комментарий из сообщения об ошибке:

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

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

1 голос
/ 06 марта 2014

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

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

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

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

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

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

...