Инструмент для отслеживания вызовов локальных функций в Linux
Я ищу инструмент, как ltrace или strace который может отслеживать локально определенные функции в исполняемом файле. ltrace отслеживает только вызовы динамической библиотеки и strace отслеживает только системные вызовы. Например, учитывая следующую программу C:
#include <stdio.h>
int triple ( int x )
{
return 3 * x;
}
int main (void)
{
printf("%dn", triple(10));
return 0;
}
запуск программы с ltrace покажет вызов printf так как это стандартная библиотечная функция (которая является динамической библиотекой в моей системе) и strace покажет все системные вызовы от код запуска, системные вызовы, используемые для реализации printf, и код завершения работы, но я хочу что-то, что покажет мне, что функция triple называлась. Предполагая, что локальные функции не были встроены оптимизирующим компилятором и что двоичный файл не был удален (символы удалены), есть ли инструмент, который может это сделать?
Edit
несколько уточнений:
- это нормально, если инструмент также обеспечивает трассировку информация для нелокальных функций.
- я не хочу перекомпилировать программу(ы) с поддержкой определенных инструментов, информации о символах в исполняемом файле должно быть достаточно.
- мне было бы очень приятно, если бы я мог использовать инструмент для присоединения к существующим процессам, как я могу с помощью ltrace/strace.
13 ответов:
предполагая, что вы хотите получать уведомления только для определенных функций, вы можете сделать это следующим образом:
компиляция с отладочной информацией (поскольку у вас уже есть информация о символах, у вас, вероятно, также достаточно отладок)
дано
#include <iostream> int fac(int n) { if(n == 0) return 1; return n * fac(n-1); } int main() { for(int i=0;i<4;i++) std::cout << fac(i) << std::endl; }используйте gdb для трассировки:
[js@HOST2 cpp]$ g++ -g3 test.cpp [js@HOST2 cpp]$ gdb ./a.out (gdb) b fac Breakpoint 1 at 0x804866a: file test.cpp, line 4. (gdb) commands 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >silent >bt 1 >c >end (gdb) run Starting program: /home/js/cpp/a.out #0 fac (n=0) at test.cpp:4 1 #0 fac (n=1) at test.cpp:4 #0 fac (n=0) at test.cpp:4 1 #0 fac (n=2) at test.cpp:4 #0 fac (n=1) at test.cpp:4 #0 fac (n=0) at test.cpp:4 2 #0 fac (n=3) at test.cpp:4 #0 fac (n=2) at test.cpp:4 #0 fac (n=1) at test.cpp:4 #0 fac (n=0) at test.cpp:4 6 Program exited normally. (gdb)вот что я делаю, чтобы собрать все адреса функции:
tmp=$(mktemp) readelf -s ./a.out | gawk ' { if( == "FUNC" && != 0) { print "# code for " $NF; print "b *0x" ; print "commands"; print "silent"; print "bt 1"; print "c"; print "end"; print ""; } }' > $tmp; gdb --command=$tmp ./a.out; rm -f $tmpобратите внимание, что вместо того, чтобы просто печать текущего кадра(
bt 1), вы можете сделать все, что вы например, печать значения некоторого глобального, выполнение некоторой команды оболочки или отправка чего-то по почте, если она попадает вfatal_bomb_explodedфункция :) к сожалению, gcc выводит некоторые сообщения "текущий язык изменен" между ними. Но это легко вылезло. Ничего страшного.
используя Uprobes (начиная с Linux 3.5)
предполагая, что вы хотите отслеживать все функции в
~/Desktop/datalog-2.2/datalogпри вызове его с параметрами-l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl
cd /usr/src/linux-`uname -r`/tools/perffor i in `./perf probe -F -x ~/Desktop/datalog-2.2/datalog`; do sudo ./perf probe -x ~/Desktop/datalog-2.2/datalog $i; donesudo ./perf record -agR $(for j in $(sudo ./perf probe -l | cut -d' ' -f3); do echo "-e $j"; done) ~/Desktop/datalog-2.2/datalog -l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dlsudo ./perf report -G
предполагая, что вы можете повторно скомпилировать (не требуется изменение источника) код, который вы хотите отслеживать с помощью опции gcc
-finstrument-functions, вы можете использовать etrace чтобы получить график функции вызова.вот как выглядит вывод:
\-- main | \-- Crumble_make_apple_crumble | | \-- Crumble_buy_stuff | | | \-- Crumble_buy | | | \-- Crumble_buy | | | \-- Crumble_buy | | | \-- Crumble_buy | | | \-- Crumble_buy | | \-- Crumble_prepare_apples | | | \-- Crumble_skin_and_dice | | \-- Crumble_mix | | \-- Crumble_finalize | | | \-- Crumble_put | | | \-- Crumble_put | | \-- Crumble_cook | | | \-- Crumble_put | | | \-- Crumble_bakeна Solaris, ферменная конструкция (strace эквивалент) имеет возможность фильтровать библиотеку для отслеживания. Я был удивлен, когда обнаружил, что у strace нет такой возможности.
$ sudo yum install frysk $ ftrace -sym:'*' -- ./a.outПодробнее: ftrace.1
Если ты воплощаешь эту функцию во внешнюю библиотеку, вы также должны быть в состоянии видеть его назвали, ( с ltrace ).
причина, по которой это работает, заключается в том, что ltrace помещает себя между вашим приложением и библиотекой, и когда весь код интернализируется с одним файлом, он не может перехватить вызов.
ie: ltrace xterm
извергает материал из библиотек X, и X вряд ли является системой.
вне этого, единственный реальный способ сделать это перехват во время компиляции с помощью флагов prof или символов отладки.
Я только что пробежал это приложение, которое выглядит интересно:
http://www.gnu.org/software/cflow/
но я не думаю, что то, что вы хотите.
если функции не встроены, вам может даже повезти с помощью
objdump -d <program>.для примера, давайте возьмем бабло в начале распорядок:
$ objdump `which gcc` -d | grep '\(call\|main\)' 08053270 <main>: 8053270: 8d 4c 24 04 lea 0x4(%esp),%ecx -- 8053299: 89 1c 24 mov %ebx,(%esp) 805329c: e8 8f 60 ff ff call 8049330 <strlen@plt> 80532a1: 8d 04 03 lea (%ebx,%eax,1),%eax -- 80532cf: 89 04 24 mov %eax,(%esp) 80532d2: e8 b9 c9 00 00 call 805fc90 <xmalloc_set_program_name> 80532d7: 8b 5d 9c mov 0xffffff9c(%ebp),%ebx -- 80532e4: 89 04 24 mov %eax,(%esp) 80532e7: e8 b4 a7 00 00 call 805daa0 <expandargv> 80532ec: 8b 55 9c mov 0xffffff9c(%ebp),%edx -- 8053302: 89 0c 24 mov %ecx,(%esp) 8053305: e8 d6 2a 00 00 call 8055de0 <prune_options> 805330a: e8 71 ac 00 00 call 805df80 <unlock_std_streams> 805330f: e8 4c 2f 00 00 call 8056260 <gcc_init_libintl> 8053314: c7 44 24 04 01 00 00 movl x1,0x4(%esp) -- 805331c: c7 04 24 02 00 00 00 movl x2,(%esp) 8053323: e8 78 5e ff ff call 80491a0 <signal@plt> 8053328: 83 e8 01 sub x1,%eaxтребуется немного усилий, чтобы пробраться через весь ассемблер, но вы можете увидеть все возможные вызовы из данной функции. Это не так просто в использовании, как
gprofили некоторые другие утилиты сказано, но у него есть несколько явных преимуществ:
- вы вообще не нужно перекомпилировать приложение, чтобы использовать его
- он показывает все возможные вызовы функций, в то время как что-то вроде
gprofбудут показаны только выполненные вызовы функций.
существует сценарий оболочки для автоматизации вызовов функций трассировки с помощью gdb. Но он не может подключиться к запущенному процессу.
blog.superadditive.com/2007/12/01/call-graphs-using-the-gnu-project-debugger/
копия страницы - http://web.archive.org/web/20090317091725/http://blog.superadditive.com/2007/12/01/call-graphs-using-the-gnu-project-debugger/
копия инструмента - диаграмму вызовов.смола.ГЗ
http://web.archive.org/web/20090317091725/http://superadditive.com/software/callgraph.tar.gz
он сбрасывает все функции из программы и генерирует командный файл gdb с точками останова для каждой функции. В каждой точке останова выполняются команды "backtrace 2" и "continue".
этот скрипт довольно медленный на большом porject (~ тысячи функций), поэтому я добавляю фильтр в список функций (через egrep). Это было очень легко, и я использую это сценарий почти каждый день.
Gprof может быть то, что вы хотите
см. трассировки, структура трассировки для приложений Linux C / C++ : https://github.com/baruch/traces#readme
Он требует перекомпиляции кода с его instrumentor, но будет предоставлять список всех функций, их параметров и возвращаемых значений. Существует интерактивная возможность удобной навигации по большим образцам данных.
надеюсь callgrind или cachegrind tools на отчет даст вам информацию, которую вы ищете.
Примечание: это не ftrace на основе ядра linux, а скорее инструмент, который я недавно разработал для выполнения локальной функции трассировки и управления потоком. Linux ELF x86_64 / x86_32 поддерживаются публично.
KcacheGrind
https://kcachegrind.github.io/html/Home.html
тестовая программа:
int f2(int i) { return i + 2; } int f1(int i) { return f2(2) + i + 1; } int f0(int i) { return f1(1) + f2(2); } int pointed(int i) { return i; } int not_called(int i) { return 0; } int main(int argc, char **argv) { int (*f)(int); f0(1); f1(1); f = pointed; if (argc == 1) f(1); if (argc == 2) not_called(1); return 0; }использование:
sudo apt-get install -y kcachegrind valgrind # Compile the program as usual, no special flags. gcc -ggdb3 -O0 -o main -std=c99 main.c # Generate a callgrind.out.<PID> file. valgrind --tool=callgrind ./main # Open a GUI tool to visualize callgrind data. kcachegrind callgrind.out.1234теперь вы остались внутри удивительной программы GUI, которая содержит много интересных данных о производительности.
в правом нижнем углу, выберите вкладку "график". Это показывает интерактивный график вызовов, который коррелирует с показателями производительности в других окнах при нажатии кнопки функция.
чтобы экспортировать график, щелкните его правой кнопкой мыши и выберите "Экспорт графика". Экспортированный PNG выглядит так:
из этого мы видим, что:
- корневой узел
_start, который является фактической точкой входа ELF и содержит шаблон инициализации glibcf0,f1иf2вызываются, как и ожидалось друг от другаpointedтоже показано, даже если мы вызвали его с помощью указателя функции. Возможно, он не был бы вызван, если бы мы передали аргумент командной строки.not_calledне отображается, потому что он не вызывался во время выполнения, потому что мы не передали дополнительный аргумент командной строки.крутая вещь о
valgrindзаключается в том, что он не требует никаких специальных опций компиляции.поэтому вы можете использовать его, даже если у вас нет исходного кода, только выполнимый.
valgrindудается сделать это, запустив свой код через легкую "виртуальную машину".протестировано на Ubuntu 18.04.



Comments