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

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