использование хуков glibc malloc в потокобезопасном режиме

Я хотел бы отслеживать использование mallocs и frees в приложении с помощью malloc и free хуков.

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

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

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

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

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

Например, существует ли внутренняя функция 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

person Alex Brown    schedule 07.01.2010    source источник
comment
Если кто-то из нас посмотрит на исходный код libc и предоставит вам основанную на нем информацию, с юридической точки зрения вы окажетесь в таком же положении.   -  person    schedule 07.01.2010
comment
Почему вы не можете посмотреть исходный код libc?   -  person Will    schedule 07.01.2010
comment
Потому что я могу загрязнить наш проприетарный код кодом GPL. Простое сообщение о том, что конкретная функция будет делать то, что я хочу, не вызывает этой проблемы.   -  person Alex Brown    schedule 07.01.2010
comment
Это сумасшедший параноидальный адвокат, но я должен соблюдать правила или работать в другом месте.   -  person Alex Brown    schedule 07.01.2010
comment
Я приму один из этих ответов примерно через день, когда поток остынет.   -  person Alex Brown    schedule 07.01.2010


Ответы (4)


ОБНОВЛЕНО

Вы имеете право не доверять __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 описывает электрическое ограждение, который подробно описывает оба этих подхода.

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

person Will    schedule 07.01.2010
comment
Вероятно, возникает вопрос: будет ли получена блокировка до вызова ловушки или это происходит внутри malloc ()? Я предполагаю, что хуки были бы бесполезны без блокировки, происходящей снаружи, но мне интересно, как тогда работает рекурсивный вызов. - person Aaron Digulla; 07.01.2010
comment
Рекурсивный вызов может работать с рекурсивными блокировками - как только поток получает блокировку, ему разрешается получать ее несколько раз. - person Phil Miller; 07.01.2010
comment
Что ж, это может быть правдой, но если я не знаю, что это правда, я не могу его использовать, так как он может сломаться. Также я не могу сказать, может ли будущая реализация malloc разрешить использование нескольких зон malloc с отдельными блокировками для повышения многопоточной производительности. - person Alex Brown; 07.01.2010
comment
когда он сломается, сообщите об ошибке! Из других новостей, знаете ли вы, что longjmp не является потокобезопасным? - person Will; 07.01.2010
comment
Сообщение об ошибке бесполезно, если 1) это не ошибка, а только то, как она работает 2) исправление ошибки не приходит вовремя, чтобы помочь моим клиентам. У вас есть ссылка на проблему с longjmp? Я широко им пользуюсь. - person Alex Brown; 07.01.2010
comment
re longjmp; вот почему был создан siglongjmp. - person Will; 07.01.2010
comment
Несмотря на то, что я чувствую проблему борьбы со сломанными маллоками, и это, конечно, часть malloc, которая, вероятно, выполняется только в тестовом коде и, следовательно, не является самой надежной, она должна быть потокобезопасной т.е. внутри замка. Так что не аварийно завершайте работу, не прерывайте работу и не используйте незащищенную общую переменную. - person Will; 07.01.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);
 }
person François Paffoni    schedule 20.01.2010

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

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

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

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

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

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

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

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

person Aaron Digulla    schedule 07.01.2010
comment
Мне не разрешено смотреть код GPL моей компанией. Это правила. - person Alex Brown; 07.01.2010
comment
Поскольку код перехвата должен удалить код перехвата перед повторным вызовом malloc, второй поток, который вызывает malloc, пока я отключил перехватчик, не будет использовать перехватчик. - person Alex Brown; 07.01.2010
comment
@Alex Полагаю, это означает, что вам не разрешено смотреть или использовать код GPL? - person Fire Crow; 07.01.2010
comment
Я могу использовать (авторизованный набор) кода LGPL, который уже размещен в системах, на которых развернуты наши приложения, например, в glibc. - person Alex Brown; 07.01.2010
comment
@Alex: Тогда вам необходимо предоставить доступ к этой части исходного кода (согласно LGPL). В противном случае ваша компания нарушает лицензию. - person Aaron Digulla; 07.01.2010
comment
@Alex: Кроме того, я почти уверен, что крючок защищен замком. Смотрите мои правки. - person Aaron Digulla; 07.01.2010
comment
@Aaron Компания может помешать своим сотрудникам делать что угодно. Я не верю, что внутреннее использование библиотеки LGPL считается распространением в рамках GPL. Я согласен, что это просто смехотворно. - person ; 07.01.2010
comment
спасибо, я пойду проверю, если он не удастся, тогда я буду знать, что у меня проблема, но если это сработает, это может быть просто алгоритм планирования на моем ноутбуке !. Тот факт, что автор предоставил мне право просматривать код (через GPL), не означает, что я имею право соглашаться с условиями GPL от имени моей компании, и это совершенно отдельный вопрос от того, Компания продолжала бы разрешать мне работать там после того, как я «заражен вирусными лицензиями», что, очевидно, и произойдет, если вы хотя бы взглянете на код GPL. Пожалуйста, помните ШОС! - person Alex Brown; 07.01.2010
comment
@Alex: Интересная проблема, юридически. Сначала я предполагаю, что в этом случае ваша компания нарушает GPL и, следовательно, никому не разрешено использовать какой-либо код GPLd. Таким образом, первый недовольный сотрудник может подать в суд, и суд может постановить, что вам запрещено продавать какие-либо из ваших продуктов. Но опять же, IANAL и ваша компания, вероятно, заплатили много денег настоящему юристу, а это значит, что есть некоторая тонкая проблема, о которой я не знаю :) - person Aaron Digulla; 07.01.2010
comment
@Alex: Что касается алгоритма планирования: после миллиона циклов один поток должен был повредить счетчик, независимо от планировщика. Просто убедитесь, что у каждого потока есть возможность запуститься. - person Aaron Digulla; 07.01.2010
comment
Да, и из-за потери скорости и возможной небезопасности потоков я предлагаю не использовать это в конечном продукте. - person Aaron Digulla; 07.01.2010
comment
@ Нил: это раздача сотруднику, который может подать в суд. - person Aaron Digulla; 07.01.2010
comment
Юристы компании сказали, что это то, с чем вы можете застрять, но вам нужно убедиться, что вы даете людям понять, что это является бременем для вашей способности выполнять свою работу и что как бизнес-процесс вы нельзя ожидать, что случайные люди в Интернете будут смотреть на источник вместо вас каждый раз, когда вы сталкиваетесь с проблемой с ним, и наблюдают, что другие компании могут решить эту проблему таким образом, чтобы не нанести чрезмерного вреда процесс разработки. - person ; 08.01.2010
comment
.... не то чтобы мы не хотели помогать в целом, но есть что-то в ситуации, когда я вполне способен прочитать исходный текст но не буду может вызвать легкое негодование в сообществе (и порождают обсуждения в IANAL этой степени), политика компании или отсутствие политики компании. Если ваша компания (образно) завязывает вам глаза и связывает вам руки за спиной, это их прерогатива, но они не могут ожидать, что мы сделаем всю вашу работу за вас. - person ; 08.01.2010

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

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

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

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

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

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

person Kaz    schedule 06.03.2014