Методы динамического анализа
Здесь я описываю несколько методов динамического анализа.
Динамические методы фактически запускают программу для определения графа вызовов.
Противоположностью динамическим методам являются статические методы, которые пытаются определить его только из источника, не запуская программу.
Преимущества динамических методов:
- перехватывает указатели на функции и виртуальные вызовы C ++. Они присутствуют в большом количестве в любом нетривиальном программном обеспечении.
Недостатки динамических методов:
- вам нужно запустить программу, которая может быть медленной или требовать установки, которой у вас нет, например кросс-компиляция
- будут показаны только те функции, которые действительно были вызваны. Например, некоторые функции могут быть вызваны или нет, в зависимости от аргументов командной строки.
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 выглядит так:
Из этого мы видим, что:
- корневой узел is
_start
, который является фактической точкой входа ELF и содержит шаблон инициализации glibc
f0
, f1
и f2
вызываются, как и ожидалось друг от друга
pointed
также отображается, хотя мы вызывали его с помощью указателя на функцию. Он мог бы не быть вызван, если бы мы передали аргумент командной строки.
not_called
не отображается, потому что он не был вызван при выполнении, потому что мы не передали дополнительный аргумент командной строки.
Крутая вещь о valgrind
что он не требует никаких специальных параметров компиляции.
Таким образом, вы можете использовать его, даже если у вас нет исходного кода, только исполняемый файл.
valgrind
удается сделать это, запустив ваш код через легкую «виртуальную машину». Это также делает выполнение чрезвычайно медленным по сравнению с собственным исполнением.
Как видно на графике, также получается информация о времени для каждого вызова функции, и ее можно использовать для профилирования программы, что, вероятно, является исходным вариантом использования этой настройки, а не только для просмотра графиков вызовов: Как я могу профилировать Код на C ++ работает в Linux?
Проверено на Ubuntu 18.04.
gcc -finstrument-functions
+ etrace
https://github.com/elcritch/etrace
-finstrument-functions
добавляет обратные вызовы , etrace анализирует файл ELF и реализует все обратные вызовы.
К сожалению, я не смог заставить его работать: почему у меня не работает `-finstrument-functions`?
Заявленный вывод имеет формат:
\-- 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
Вероятно, это наиболее эффективный метод, помимо поддержки конкретной аппаратной трассировки, но имеет обратную сторону: вам придется перекомпилировать код.