Я ищу такой инструмент, как ltrace или Strace, который может отслеживать локально определенные функции в исполняемом файле. ltrace отслеживает только вызовы динамических библиотек, а strace отслеживает только системные вызовы. Например, учитывая следующую программу на C:
#include <stdio.h>
int triple ( int x )
{
return 3 * x;
}
int main (void)
{
printf("%d\n", triple(10));
return 0;
}
Запуск программы с ltrace покажет вызов printf, поскольку это стандартная библиотечная функция (которая является динамической библиотекой в моей системе), а strace покажет все системные вызовы из кода запуска, системные вызовы, используемые для реализации printf, и код выключения, но мне нужно что-то, что покажет мне, что функция triple была вызвана. Предполагая, что локальные функции не были встроены оптимизирующим компилятором и что двоичный файл не был удален (символы удалены), существует ли инструмент, который может это сделать?
Редактировать
Пара уточнений:
Я не хочу прерывать выполнение программы, если GDB может ненавязчиво отследить такую программу, как ltrace can, я бы хотел попробовать ее, если кто-нибудь скажет мне, как это сделать.
В частности, с GDB: stackoverflow.com/questions/9549693/…





Надеюсь, инструменты callgrind или cachegrind для Валгринд предоставит вам информацию, которую вы ищете.
Я уже изучил все инструменты для valgrind, ничего не делает то, что я ищу.
Gprof может быть тем, что вы хотите
Я не собираюсь профилировать код, просто отслеживаю его. Я хочу знать каждый раз, когда вызывалась локальная функция, какие были аргументы и какое возвращаемое значение. Я также не хочу перекомпилировать программу со специальной поддержкой определенного инструмента, как того требует gprof.
Предполагая, что вы хотите получать уведомления только об определенных функциях, вы можете сделать это следующим образом:
скомпилировать с отладочной информацией (поскольку у вас уже есть символьная информация, у вас, вероятно, также есть достаточно отладок)
дано
#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 ($4 == "FUNC" && $2 != 0) {
print "# code for " $NF;
print "b *0x" $2;
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 выводит какой-то «Текущий язык» изменены "сообщения между ними. Но это легко найти. Ничего страшного.
Я хочу иметь возможность отслеживать все локальные функции и не хочу прерывать программу, явно устанавливая точки останова.
вы можете использовать objdump для получения функций и их адресов, а затем использовать параметр --command, чтобы указать gdb на сгенерированный файл, который автоматически устанавливает точку останова.
@litb, да, это то, что я пытаюсь сделать сейчас, это может сработать, спасибо за понимание.
@litb, похоже, это работает, я могу подключиться к запущенному процессу, мне не нужны дополнительные символы отладки, и прерывание программы разумное. Мне просто нужно выяснить, как запустить gdb из скрипта и отправить вывод в файл, мне нужно больше времени проводить с GDB :)
следующая версия gdb будет иметь поддержку python: tromey.com/blog/?p=412, тогда мы повеселимся: p
@litb, мне нужна была эта функция, поэтому я написал скрипт Python, который делает то, что вы предлагаете, генерируя вывод для OpenGrok и GraphViz dot. Если кому-то интересно, вы можете получить его по адресу github.com/EmmetCaulfield/ftrace. Он делает то, что мне нужно, но я сомневаюсь, что он очень стабилен. YMMV.
Мне пришлось добавить новую строку в конце $ tmp с просто 'c' в ней и запустить gdb таким образом, чтобы полностью автоматизировать трассировку запущенного процесса: echo c >> $tmp, за которым следует gdb -batch --command=$tmp -p 15414 < /dev/null &> /tmp/gdb.log &
Теперь у нас есть rbreak, поэтому ответ можно обновить?
Если вы перенесете эту функцию во внешнюю библиотеку, вы также сможете увидеть, как она вызывается (с помощью ltrace).
Причина, по которой это работает, заключается в том, что ltrace помещается между вашим приложением и библиотекой, и когда весь код встроен в один файл, он не может перехватить вызов.
то есть: ltrace xterm
извергает материал из X-библиотек, а X вряд ли является системным.
В остальном единственный реальный способ сделать это - перехватить во время компиляции с помощью флагов prof или символов отладки.
Я только что пробежался по этому приложению, оно выглядит интересно:
http://www.gnu.org/software/cflow/
Но я не думаю, что ты этого хочешь.
Я понимаю, почему ltrace может делать то, что делает, и что трассировка локальных функций сложнее, но было бы неплохо, если бы существовал инструмент, который мог бы подключаться к процессу и автоматически устанавливать точки останова для всех локальных функций автоматически, чтобы отслеживать их, если это что требуется.
$ sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out
Подробнее: ftrace.1
Из справочной страницы мне не ясно, будет ли это делать то, что я хочу, но этот проект, похоже, находится в стадии бета-тестирования и не поддерживается какой-либо платформой, кроме Fedora. Я использую несколько дистрибутивов, ни один из которых не является Fedora, и похоже, что попытка заставить это работать с любым из них будет проблемой.
Предполагая, что вы можете повторно скомпилировать (без изменения исходного кода) код, который хотите отслеживать, с помощью опции 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 truss (эквивалент strace) может фильтровать библиотеку, которую нужно трассировать. Я был удивлен, когда обнаружил, что strace не имеет такой возможности.
Разве вам не нужно компилировать + связывать ptrace.c с вашим кодом, чтобы это работало? Не всегда разумная задача, когда у вас большая кодовая база с гигантским make-файлом :)
@philant Я забыл про эту опцию. Действительно мило.
Системный кран можно использовать в современном Linux-сервере (Fedora 10, RHEL 5 и т. д.).
Сначала загрузите сценарий para-callgraph.stp.
Затем запустите:
$ sudo stap para-callgraph.stp 'process("/bin/ls").function("*")' -c /bin/ls
0 ls(12631):->main argc=0x1 argv=0x7fff1ec3b038
276 ls(12631): ->human_options spec=0x0 opts=0x61a28c block_size=0x61a290
365 ls(12631): <-human_options return=0x0
496 ls(12631): ->clone_quoting_options o=0x0
657 ls(12631): ->xmemdup p=0x61a600 s=0x28
815 ls(12631): ->xmalloc n=0x28
908 ls(12631): <-xmalloc return=0x1efe540
950 ls(12631): <-xmemdup return=0x1efe540
990 ls(12631): <-clone_quoting_options return=0x1efe540
1030 ls(12631): ->get_quoting_style o=0x1efe540
См. Также: Наблюдать, системные теги и обновления профилей
Просто хотел отметить, что это может зависеть от параметров компиляции ядра; например Получил по той же команде: "semantic error: process probes not available without kernel CONFIG_UTRACE while resolving probe point process("/bin/ls").function("*").call"
У меня это не работает с semantic error: while resolving probe point: identifier 'process' at a.stp:23:7 на Ubuntu 14.04. Каков принцип работы системного крана?
Нет необходимости указывать полный путь в качестве аргумента к process(), sudo stap para-callgraph.stp 'process.function("*")' -c /bin/ls работает точно так же. Чтобы уменьшить шум от функций библиотеки, для которых отсутствуют символы отладки, вы можете использовать: 'process.function("*@*")'.
Если функции не встроены, возможно, вам даже повезет с использованием objdump -d <program>.
Для примера возьмем добычу в начале процедуры main GCC 4.3.2:
$ 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 $0x1,0x4(%esp)
--
805331c: c7 04 24 02 00 00 00 movl $0x2,(%esp)
8053323: e8 78 5e ff ff call 80491a0 <signal@plt>
8053328: 83 e8 01 sub $0x1,%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/
Копия инструмента - callgraph.tar.gz
http://web.archive.org/web/20090317091725/http://superadditive.com/software/callgraph.tar.gz
Он выгружает все функции из программы и генерирует командный файл gdb с точками останова для каждой функции. В каждой точке останова выполняется «backtrace 2» и «continue».
Этот скрипт довольно медленный на большом объекте (~ тысячи функций), поэтому я добавляю фильтр в список функций (через egrep). Это было очень просто, и я использую этот скрипт почти каждый день.
Ссылка, которую вы предоставили, сейчас не работает.
Alex Reinking, спасибо, обновился архивной версией.
Я написал аналогичный инструмент на Python, используя скрипты GDB python + graphviz: github.com/tarun27sh/Python_gdb_networkx_graphs
См. Traces, фреймворк трассировки для приложений Linux C / C++: https://github.com/baruch/traces#readme
Это требует перекомпиляции вашего кода с помощью инструментария, но при этом будет предоставлен список всех функций, их параметров и возвращаемых значений. Есть интерактивный интерфейс, позволяющий легко перемещаться по большим выборкам данных.
Предполагая, что вы хотите отследить все функции в ~/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

ПРИМЕЧАНИЕ. Это не ftrace на основе ядра Linux, а инструмент, который я недавно разработал для выполнения локальной трассировки функций и управления потоком. Linux ELF x86_64 / x86_32 поддерживаются публично.
https://github.com/leviathansecurity/ftrace
Не могли бы вы здесь резюмировать принцип работы? Почему бы вместо этого не использовать, например, Python API от GDB?
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
Теперь вы остались внутри великолепной программы с графическим интерфейсом, которая содержит много интересных данных о производительности.
В правом нижнем углу выберите вкладку «График звонков». Это показывает интерактивный график вызовов, который коррелирует с показателями производительности в других окнах, когда вы щелкаете по функциям.
Чтобы экспортировать график, щелкните его правой кнопкой мыши и выберите «Экспорт графика». Экспортированный PNG выглядит так:
Из этого мы видим, что:
_start, который является фактической точкой входа ELF и содержит шаблон инициализации glibcf0, f1 и f2 вызываются, как и ожидалось друг от другаpointed, хотя мы вызвали его с помощью указателя на функцию. Возможно, он не был бы вызван, если бы мы передали аргумент командной строки.not_called не отображается, потому что он не был вызван при запуске, потому что мы не передали дополнительный аргумент командной строки.Самое замечательное в valgrind то, что он не требует каких-либо специальных опций компиляции.
Следовательно, вы можете использовать его, даже если у вас нет исходного кода, только исполняемый файл.
valgrind удается это сделать, запустив ваш код через легкую «виртуальную машину».
Проверено на Ubuntu 18.04.
вы искали трассировку с помощью GDB? однажды мне сказали, что это работает только для удаленных целей. может быть, вы можете заставить gdb работать с удаленной целью и подключаться к localhost? не уверен, просто какая-то случайная идея.