Инструмент для отслеживания вызовов локальных функций в 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.

668   13  

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

  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 binarycall 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

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

https://github.com/leviathansecurity/ftrace

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 и содержит шаблон инициализации glibc
  • f0,f1 и f2 вызываются, как и ожидалось друг от друга
  • pointed тоже показано, даже если мы вызвали его с помощью указателя функции. Возможно, он не был бы вызван, если бы мы передали аргумент командной строки.
  • not_called не отображается, потому что он не вызывался во время выполнения, потому что мы не передали дополнительный аргумент командной строки.

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

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

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

протестировано на Ubuntu 18.04.

Comments

    Ничего не найдено.