понедельник, 8 декабря 2008 г.

Ловушка для меморилика (проблемы)

Основная проблема с перехватыванием функций, работающих с памятью - это бесконечная рекурсия. Десятки функций стандартной библиотек хотят память. Но и нам хочется не просто заменить malloc на свой, но и довести до программиста какую-то полезную информацию. И тогда приходять они... проблемы...

Первая проблема возникает тогда, когда мы пытаемся воспользоваться функцией printf. И заключается она примерно в следующем: первый раз malloc вызывается задолго до вызова функции main, и видимо задолго до инициализации подсиситемы ввода-вывода. В результате попытка дернуть printf из malloc приводит к тому, что printf пытается выделить буфер, и опять таки вызывает наш malloc. Замена оператора printf чем нибудь (sprintf, puts, и тд) ничего путного не принесла. Но наша библиотека в любом случае требует инициализации. Мы просто проинициализируем буфер stdout, можно в _IONBF, и эта проблема себя исчерпывает.

setvbuf(stdout, NULL, _IONBF, 0);

Другая проблема проявляет себя во всей красе, когда мы пытаемся использовать библиотеку в многопоточном приложении. Как ни крути, а медленный менеджер хипа надо защищать блокировками. Но pthread_mutex_lock, опять таки, вызывает malloc для инициализации tls, локов и тд. Надо сказать что libpthread во FreeBSD весьма закрытая вещ. Все структуры у них сугубо приватны, всмысле в инклюды не включены, и выделить и проинициализоровать лок самостоятельно не выходит.

Долго бился над этой проблемой. Пытался прикрутить свои блокировки на ассемблере, не помогает. Еще что-то пытался - сейчас уже не вспомню. Но приложение упорно уходило в кору по исчерпанию стека. В результате поступил следующим образом: если гора не идет к Магомету... в смысле, если pthread_mutex_lock вызывает malloc, тогда мы не будем из malloc вызывать pthread_mutex_lock. Но и совсем от него мы тоже не может отказаться. Мы не будем вызывать блокировку только в том случае, если malloc вызывается из недр libpthread... хотя наверное можно было бы просто сделать проверку на рекурсию, флажек какой нибудь, это наверное тоже сработало бы. Хотя я вроде даже что-то такое пробовал, не помню. Сделал по хитрому. я проверяю адрес возврата на попадание в области критичных к рекурсии функций, и в этом случае не произвожу блокировку. Извращенный метод конечно, но показало себя вполне надежным.

Но это была еще и не последняя проблема. Последняя проблема с трудом поддается моему пониманию. Заключается она в том, что при переключении потоков приложение падало в функции _thr_setcontext, на операции FXRSTOR. Поковырявшись немного в ядре вычитал, что блок данных для хранения контекста FPU должен быть выровнен на 16 байт. Исправилось выравниванием всех блоков в менеджере хипа на 16 байт. Но так и не могу понять каким образом сохранение контекста FPU производится в буфер, выделенный пользовательским malloc, в пользовательском же пространстве? Возможно, там используется какой-то хитрый коллбек из ядра... странно это. Я полагал, что этим должно заниматься ядро.

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

3 коммент.:

Анонимный комментирует...

В результате попытка дернуть printf из malloc приводит к тому, что printf пытается выделить буфер, и опять таки вызывает наш malloc.
Каждый printf вызывает malloc? :-D крутая система. Выделить буфер один раз на поток видимо не судьба...

Надо сказать что libpthread во FreeBSD весьма закрытая вещ. Все структуры у них сугубо приватны, всмысле в инклюды не включены
На самом деле именно ТАК надо делать. Это называется интерфейс, который, как известно, должен скрывать реализацию. А FILE ф топку...

Извращенный метод конечно, но показало себя вполне надежным.
Да, действительно извращенный :-) особенно это понимаешь после работы с критическими секциями в NT. И правильнее было бы написать - "пока показывало себя вполне надежным".

Про FXRSTOR жжошь неподецки. Меня всегда удивляло желание разработчиков *nix системную информацию распределять среди данных пользователя.

Андрей Валяев комментирует...

2coff: Каждый printf вызывает malloc? ...ТАК надо делать. Это называется интерфейс...

Не каждый... первый. :)
Проблема на самом деле в том, что я пытаюсь внедриться в самый базовый уровень приложения, при этом не стоит использовать более высокоуровневые библиотеки.

Сейчас я нашел способ блокировать через встроенные функции, без использования pthreads.

На счет интерфейсов я согласен, но только не тогда, когда я ковыряюсь в недрах системы.

Анонимный комментирует...

Один раз - нормально. Однако в твоем случае действительно будут проблемы. А нельзя ли вызвать printf (вызов выделяет буфер), а потом включить отладочный вывод из malloc через printf?

при этом не стоит использовать более высокоуровневые библиотеки.
+1

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