Инструменты для получения графического графического вызова функции
У меня есть большое рабочее пространство, в котором есть много исходных файлов кода Си. Хотя я вижу функции, вызываемые из функции в MS VS2005 с помощью обозревателя объектов, а также в MSVC 6.0, в нем отображаются только функции, вызываемые из определенной функции, в неграфическом виде. Кроме того, он не показывает функцию, вызванную, скажем, с main()
и затем вызываемые из него функции, и так далее, глубже внутри функции конечного уровня.
Мне нужен инструмент, который наглядно покажет мне график вызова функций с функциями callee
а также caller
связаны стрелками или что-то в этом роде, начиная с main()
до последнего уровня функции или, по крайней мере, графическое отображение графика вызовов всех функций в одном исходном файле языка C. Было бы здорово, если бы я мог напечатать этот график.
Любые хорошие инструменты для этого (не обязательно быть бесплатными инструментами)?
7 ответов
Египет (бесплатное программное обеспечение)
KcacheGrind (GPL)
Графвиз (CPL)
CodeViz (GPL)
Методы динамического анализа
Здесь я опишу несколько методов динамического анализа.
Динамические методы фактически запускают программу для определения графа вызовов.
Противоположностью динамических методов являются статические методы, которые пытаются определить его из одного источника без запуска программы.
Преимущества динамических методов:
- ловит указатели на функции и виртуальные вызовы 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 выглядит так:
Из этого мы можем видеть, что:
- корневой узел
_start
, которая является фактической точкой входа ELF и содержит шаблон инициализации glibc f0
,f1
а такжеf2
называются, как и ожидалось друг от другаpointed
также отображается, хотя мы вызывали его с указателем на функцию. Возможно, он не был вызван, если мы передали аргумент командной строки.not_called
не отображается, потому что он не вызывался во время выполнения, потому что мы не передали дополнительный аргумент командной строки.
Классная вещь о valgrind
является то, что он не требует каких-либо специальных параметров компиляции.
Следовательно, вы можете использовать его, даже если у вас нет исходного кода, только исполняемый файл.
valgrind
удается сделать это, запустив ваш код через легкую "виртуальную машину".
Проверено на Ubuntu 18.04.
gcc -finstrument-functions
+ этрасс
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
Вероятно, это самый эффективный метод, помимо специальной поддержки аппаратного отслеживания, но имеет недостаток, что вам придется перекомпилировать код.
Понимание делает очень хорошую работу по созданию графов вызовов.
Наш инструментарий реинжиниринга программного обеспечения DMS содержит статический анализ графов управления потоками данных / потоком данных / точками к / вызовам, который был применен к огромным системам (~~25 миллионов строк) кода C, и создавал такие графы вызовов, включая функции, вызываемые через указатели на функции.
Вы можете проверить мой генератор дерева вызовов C на основе bash здесь. Он позволяет вам указать одну или несколько функций C, для которых вы хотите, чтобы вызывающая сторона и / или вызываемая информация, или вы можете указать набор функций и определить график достижимости вызовов функций, которые их связывают... Т.е. скажите мне все способы main(), foo() и bar() связаны между собой. Он использует graphviz/dot для графического движка.