LINUX.ORG.RU

Адрес функций из разделяемых (shared) библиотек

 ,


0

1

Вопрос относится к C89, posix + может быть к специфическим моментам gcc / linux / BSD.

Хочу написать наипростейший статистический профайлер для программ на C, который бы мог работать в частности без перекомпиляции (и вёл статистику на всём протяжении работы программы в этом случае). Пока сделал так: имеем библиотеку с одной функцией, помеченной __attribute__((constructor)) и другой, помеченной __attribute__((destructor)). В первой функции инициализируется что-то вроде стека для семплов, устанавливается обработчик сигнала таймера и таймер запускается. Во второй результат работы записывается в файл.

Пока делаю даже без анализа стека - просто записываю в стек содержимое регистра rip в качестве семпла.

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

Я примерно представляю, как сделать это для функций, код которых лежит в этом бинарнике в .text. А как узнать фактический адрес, в которые загружены функции из .so библиотек? Более точнее: если у меня есть значение регистра rip, и я знаю, что исполняется код из какой-то библиотеки, можно ли узнать из какой функции именно?

Может есть другие способы отследить выполнение программы?

Делаю больше из-за удовольствия, хотя я хз, есть ли такое для FreeBSD (для linux вроде гуглил и находил).

valgrind умеет считать вызовы функций.

anonymous
()

Сотрим символны из либы, readelf -s /usr/lib/mylib.so. Далее ищем адрес загрузки либы в адресное про-во, можно через procfs maps файл, переводим адрес относительно адреса загрузки и ищем по таблице выше наиближайшее меньшее. Как-то так.

mashina ★★★★★
()
Ответ на: комментарий от mashina

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

В моём примере rip при нахождении в функции log из libm.so равен 0x800a356f6, загружается либа с адреса 0x80081c000 (разница - 0x2196f6), а log начинается с 0x176e8. Наверное, надо читать маны и знать что-то ещё.

hvatitbanit
() автор топика
Ответ на: комментарий от hvatitbanit

Ту ли секцию смотришь, с x атрибутом? И ещё может быть, что не в том месте адрес берёшь, например, это может быть не фактический адрес ф-ии, а адрес её plt записи.

mashina ★★★★★
()
Ответ на: комментарий от mashina

Ой, наверное чего-то не то делал, да и монитор ещё маленький был. Сегодня ещё попробовал, действительно rip где-то внутри log из libm.so

Спасибо!

hvatitbanit
() автор топика
Ответ на: комментарий от mashina

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

1) Как определить, до каких пор разворачивать стек при анализе (т.е. где у него bottom)? Нашел какое-то решение, но оно glibc-специфичное. Может тут можно какие-нибудь расширения GCC/clang припахать?

2) Как узнать, что на стеке храниться именно адрес возврата, а не какие-нибудь локальные данные функции? Самое простое, что приходит в голову - опять смотреть в карте памяти процесса, стоят ли для нужной страницы/диапазона права на исполнение. А может можно иначе?

Написал простую программу, печатающую содержимое стека: http://pastebin.com/nSHaeLb2

Вывод вот:

> ./test 
8
0x80094e6ea
0x400919 (возврат из kill)
0x7fffffffd750
0x80061d000
0x7fffffffd870
0x4009a4 (возврат из lol)
0x1
0xffffd8e8
0x200000000
0x400850
0x44
0x0
0x0
0x0
0x7fffffffd8b0
0x40076f (возврат из main)
0x400620
0x0 (походу, уже мусор)
0x7fffffffd8d0
0x0
0x0
0x0
0x0
0x80061d000
0x0
0x0
0x1
0x7fffffffdb48
0x0
0x7fffffffdb4f
0x7fffffffdb5a

Ах, да, в ucontext_t, который принимает обработчик сигнала есть поле uc_stack типа stack_t, но там все поля обнулены. Судя по всему, это поле заполняется вызовом getcontext, а не механизмом обработки сигналов. Жаль.

Оставлю как рекламу (осторожно, там ещё и CL) ;)

https://github.com/shamazmazum/vsprofiler

hvatitbanit
() автор топика
Ответ на: комментарий от hvatitbanit

1) Как определить, до каких пор разворачивать стек при анализе (т.е. где у него bottom)? Нашел какое-то решение, но оно glibc-специфичное. Может тут можно какие-нибудь расширения GCC/clang припахать?

Вообще это немного нетривиальная процедура, поиск начала завист от компилятора, могут по разному его офрмлять. Лучще взять готовую либу, типа libunwind и пользоваться ей. Или разбираться как так там это делают. Мб пока в качестве возвращаемого адреса не найдёшь 0x0, см. ниже.

2) Как узнать, что на стеке храниться именно адрес возврата, а не какие-нибудь локальные данные функции?

Нужно искать и декодировать стековые кадры. Как уже выше написал, не всегда это простая процедура. В самом простом случае для x86_64 ф-я держит начало кадра в $rbp регистре и сохраняет $rbp вызывающей ф-ии вначале своего када. Соответственно адрес вызывающей ф-ии будет в $rbp + 8 и предыдущий кадр в ($rbp) если нчиего не путаю. В x86-64 Instructions and ABI можно почитать более детально как принято оформлять кадры в стеке. Но с оптимизациями $rbp может не использоваться и может не сохраняться $rbp в стеке для тривиальных ф-ий. Ещё стоит учитывать, что на разных платформах разные правила оформления стековых кадров.

Самое простое, что приходит в голову - опять смотреть в карте памяти процесса, стоят ли для нужной страницы/диапазона права на исполнение. А может можно иначе?

Этот вариант подходит если хочется восстановить попорченный стек. На самом деле это очень сложный подход, т.к. в стеке остаётся значительно больше ссылок на адреса в исполняемой памяти чем есть на самом деле в каждом конкретном случае (~ мусор от прерыдущих стеков). Нужно ещё городить кучу логики по выкидыванию неподходящих кандидатов и склеиванию оставшихся в стек. Если стек не испорчен, то проще пытаться декодировать кадры по ABI.

mashina ★★★★★
()
Ответ на: комментарий от mashina

Спасибо, значит почитаю многотекста + посмотрю либу

hvatitbanit
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.