вторник, 17 апреля 2012 г.

Запускаем valgrind в Gentoo.

Есть в gentoo одна довольно неприятная бага: в ней не работает valgrind. Причём с довольно интересной формулировкой:
valgrind:  A must-be-redirected function
valgrind:  whose name matches the pattern:      strlen
valgrind:  in an object with soname matching:   ld-linux-x86-64.so.2
valgrind:  was not found whilst processing
valgrind:  symbols from the object with soname: ld-linux-x86-64.so.2


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

Итак, что мы имеем: есть функция strlen, но valgrind почему-то её не находит. Об этом известно на багтрекере, известна причина - данная функция встроена в целях оптимизации и для исправления этого нужно всего ничего: добавить splitdebug в переменную FEATURES и пересобрать glibc. Сказано - сделано. Только не сработало. Странно конечно, но ладно. Есть ещё один совет: явно запретить inline функцию. Это делается ключом -fno-builtin-strlen. Добавляем его в CFLAGS, пересобираем и... нифига. Добавляю USE-флаг debug - тоже не помогает, но во время пересборки замечаю странную особенность: среди опций компилятора отсутствует -fno-builtin-strlen. Это крайне странно. Получается, что glibc проигнорировал мой make.conf. Ну ладно, попробую прямо перед командой emerge ему прописать, а заодно уменьшу количество оптимизаций:
CFLAGS="-O0 -fno-builtin-strlen" emerge glibc -1

И что я вижу:
 * Building glibc for ABIs:

 *             ABI:   default
 *          CBUILD:   i686-pc-linux-gnu
 *           CHOST:   i686-pc-linux-gnu
 *         CTARGET:   i686-pc-linux-gnu
 *      CBUILD_OPT:  
 *     CTARGET_OPT:  
 *              CC:   i686-pc-linux-gnu-gcc
 *         ASFLAGS:  
 *          CFLAGS:   -O2 -fno-strict-aliasing
 *        CPPFLAGS:   -U_FORTIFY_SOURCE -U_FORTIFY_SOURCE
 *        CXXFLAGS:   -march=native -mtune=native -pipe -O2 -fno-strict-aliasing
 *         LDFLAGS:   -Wl,-O1 -Wl,--as-needed
 *       Manual CC:   i686-pc-linux-gnu-gcc 


Т.е. он вообще полностью игнорирует пользовательские настройки. Это уже довольно нестандартная ситуация. Ну ничего не поделаешь, если скрипт сборки считает себя умнее пользователя, то нужно ему показать, что это не так. Распаковываем ebuild и правим скрипт сборки:
ebuild /usr/portage/sys-libs/glibc/glibc-2.14.1-r2.ebuild unpack
cd /var/tmp/portage/sys-libs/glibc-2.14.1-r2/work/glibc-2.14.1
grep fno-strict-aliasing -rl | sort | uniq | xargs sed "s/-fno-strict-aliasing/-fno-strict-aliasing -ggdb -fno-builtin-strlen/" -i
ebuild /usr/portage/sys-libs/glibc/glibc-2.14.1-r2.ebuild merge


Пояснение к страшному конвейеру с grep. Т.к. при проверки настроек сборки опция -fno-strict-aliasing всегда фигурирует, то логично предположить, что добавление ещё нескольких опций должно быть записано в переменную CFLAGS и применено при сборке. Должно быть, но нет, оно не записано. Результат тот же. Хм, ладно есть один железный способ подсунуть нужные ключи в сборку:
alias i686-pc-linux-gnu-gcc="i686-pc-linux-gnu-gcc -ggdb -fno-builtin-strlen"

Это даёт уверенность в том, что компилятор не сможет взять и выкинуть поданные ключи. Пересобираем. Valgrind не работает... Формулировка всё та же: нет функции strlen.

Тут уже закрадывается сомнение: "А был ли мальчик?". Ведь если мы явно запретили встраивание функции, но её всё равно нет... А есть ли функция? В погоне за ответом ищем в исходниках glibc определение функции strlen
grep -e "size_t[^s]*strlen[^{]*{" -r

и не находим (на самом деле оно есть, но об этом я узнаю чуть позже). А пока подозрения подтверждаются: очень похоже, что strlen действительно не функция, а макрос. Проверяем:
grep -e "define.*strlen" -r

и да, среди большого количества результатов есть то, что мы ищем:
sysdeps/i386/i486/bits/string.h:#define strlen(str) \
это действительно макрос, и довольно сложный. Попытки превратить макрос в функцию успехом не увенчались - glibc даже не скомпилился. Но возникает вопрос: если strlen не функция, то как valgrind работает в остальных дистрибутивах? Да и почему адрес у определения макроса такой странный? В комментарии к файлу сказано:
/* We only provide optimizations if the user selects them and if
   GNU CC is used.  */


Учитывая его адрес, скорей всего оптимизация выбирается в зависимости от целевой платформы. Интересно. У меня в переменной CHOST прописано i686. Т.к. я уже имел печальный опыт замены этой переменной, то в этот раз решил действовать осторожней:
CFLAGS="-O2 -march=i368 -mtune=i386" emerge glibc -1

результат меня порадовал:
make -j3 -s glibc-test
glibc-test.c:1:0: ошибка: bad value (i368) for -march= switch
make: *** [glibc-test] Ошибка 1
emake failed
 * Simple build failed ... assuming this is desired #324685
make -j3 -s glibc-test
glibc-test.c:1:0: ошибка: bad value (i368) for -march= switch
make: *** [glibc-test] Ошибка 1


т.е. скрипт сборки проверил меня на вшивость, не поверил, после чего стал всё делать с -march=native! Разработчики, браво! В ходе дальнейшего копания я всё же нашел реализацию функции strlen (файл sysdeps/i386/strlen.c), и это вселило надежду, что её таки возможно скомпилировать как функцию. С самого начала ковыряния в исходниках меня очень смущал макрос libc_hidden_builtin_def (strlen). Что конкретно он делает я так и не понял, но судя по имени вполне может быть, что именно он отвечает за подстановку оптимизированной версии. Я решил рискнуть и убрал его из файла include/string.h, пересобрал и... Оно заработало!!!

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

Чтобы заставить работать valgrind в gentoo при условии, что все рекомендуемые разработчиками способы Вам не помогли можно сделать следующее (пути в зависимости от версии и архитектуры Вашей системы могут меняться, решение работает для архитектуры i686):
ebuild /usr/portage/sys-libs/glibc/glibc-2.14.1-r2.ebuild unpack
sed "s|libc_hidden_builtin_proto (strlen)|//libc_hidden_builtin_proto (strlen)|" -i /var/tmp/portage/sys-libs/glibc-2.14.1-r2/work/glibc-2.14.1/include/string.h

vim /var/tmp/portage/sys-libs/glibc-2.14.1-r2/work/glibc-2.14.1/sysdeps/i386/i486/bits/string.h     # здесь добавляем в файл строчку #define __NO_STRING_INLINES 1
ebuild /usr/portage/sys-libs/glibc/glibc-2.14.1-r2.ebuild merge


Комментариев нет:

Отправить комментарий