Инструмент для отслеживания локальных вызовов функций в Linux - PullRequest
59 голосов
/ 23 ноября 2008

Я ищу инструмент типа ltrace или strace , который может отслеживать локально определенные функции в исполняемом файле. ltrace отслеживает только динамические библиотечные вызовы, а strace only отслеживает системные вызовы. Например, для следующей программы на 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. Предполагая, что локальные функции не были встроены оптимизирующим компилятором и что двоичный файл не был удален (символы удалены), есть ли инструмент, который может это сделать?

Редактировать

Пара уточнений:

  • Это нормально, если инструмент также предоставляет информацию трассировки для нелокальных функций.
  • Я не хочу перекомпилировать программу (ы) с поддержкой определенных инструментов, информации о символах в исполняемом файле должно быть достаточно.
  • Было бы здорово, если бы я мог использовать этот инструмент для присоединения к существующим процессам, как я могу с помощью ltrace / strace.

Ответы [ 13 ]

53 голосов
/ 23 ноября 2008

Если вы хотите получать уведомления только о конкретных функциях, вы можете сделать это следующим образом:

компилировать с отладочной информацией (поскольку у вас уже есть символьная информация, у вас, вероятно, также достаточно отладок)

данный

#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 выводит некоторые сообщения «Текущий язык изменен» между ними. Но это легко сгладить. Ничего страшного.

20 голосов
/ 28 ноября 2008

Системный сигнал можно использовать в современной 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

См. Также: Наблюдать, обновления systemtap и oprofile

11 голосов
/ 12 сентября 2012

Использование Uprobes (начиная с Linux 3.5)

Предполагая, что вы хотите отследить все функции в ~/Desktop/datalog-2.2/datalog при вызове его с параметрами -l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl

  1. cd /usr/src/linux-`uname -r`/tools/perf
  2. for i in `./perf probe -F -x ~/Desktop/datalog-2.2/datalog`; do sudo ./perf probe -x ~/Desktop/datalog-2.2/datalog $i; done
  3. sudo ./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.dl
  4. sudo ./perf report -G

list of functions in datalog binary call tree when selecting dl_pushlstring, showing how main called loadfile called dl_load called program called rule which called literal which in turn called other functions that ended up calling dl_pushlstring, scan (parent: program, that is, the third scan from the top) which called dl_pushstring and so on

9 голосов
/ 23 ноября 2008

Если вы можете перекомпилировать (без изменения исходного кода) код, который вы хотите отследить, с помощью опции 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 нет такой возможности.

4 голосов
/ 23 ноября 2008
$ sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out

Подробнее: ftrace.1

2 голосов
/ 30 декабря 2009

Существует скрипт оболочки для автоматизации вызовов функций трассировки с помощью gdb. Но он не может присоединиться к запущенному процессу.

blog.superadditive.com / 2007/12/01 / Call-графики, используя-The-гну-проект-отладчик /

Копия страницы - 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) Это было очень легко, и я использую этот скрипт почти каждый день.

2 голосов
/ 28 ноября 2008

Если функции не встроены, возможно, вам даже повезет, если использовать 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 будет отображать только выполненные вызовы функций.
2 голосов
/ 23 ноября 2008

Если вы перенесете эту функцию во внешнюю библиотеку, вы также сможете увидеть, как она вызывается (с помощью ltrace).

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

то есть: ltrace xterm

извергает вещи из библиотек X, и X вряд ли системный.

Кроме этого, единственный реальный способ сделать это - перехват во время компиляции с помощью флагов prof или символов отладки.

Я только что запустил это приложение, которое выглядит интересно:

http://www.gnu.org/software/cflow/

Но я не думаю, что ты этого хочешь.

1 голос

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 выглядит так:

image

Отсюда видно, что:

  • корневой узел _start, который является фактической точкой входа ELF и содержит шаблон инициализации glibc
  • f0, f1 и f2 вызываются как положено друг от друга
  • pointed также отображается, хотя мы вызывали его с указателем на функцию. Возможно, он не был вызван, если мы передали аргумент командной строки.
  • not_called не отображается, потому что он не вызывался во время выполнения, потому что мы не передали дополнительный аргумент командной строки.

Самое замечательное в valgrind в том, что он не требует каких-либо специальных параметров компиляции.

Следовательно, вы можете использовать его, даже если у вас нет исходного кода, только исполняемый файл.

valgrind удается это сделать, запустив код через облегченную «виртуальную машину».

Проверено на Ubuntu 18.04.

1 голос
/ 13 апреля 2012

См. Трассировки, инфраструктура трассировки для приложений Linux C / C ++: https://github.com/baruch/traces#readme

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

...