Получение графа потока управления из кода ANSI C

Я создаю инструмент для тестирования приложений ansi C. Просто загрузите код, просмотрите график потока управления, запустите тест, отметьте все вершины, которые были поражены. Я пытаюсь построить CFG самостоятельно из синтаксического анализа кода. К сожалению, он запутывается, если код вложен. GCC дает возможность получить CFG из скомпилированного кода. Я мог бы написать парсер для его вывода, но мне нужны номера строк для установки точек останова. Есть ли способ получить номера строк при выводе графика потока управления с помощью -fdump-tree-cfg или -fdump-tree-vcg?

3 ответов


для графа потока управления программой на C вы можете посмотреть существующие синтаксические анализаторы Python для C:

графики вызовов тесно связаны с графики потока управления. Существует несколько подходов для создания график (зависимости, функции) в C код. Это может оказаться полезным для продвижения с генерацией графа потока управления. Способы создания графиков зависимостей в C:

  • используя cflow:

    • cflow +pycflow2dot +точка (GPL, BSD) cflow является надежным, потому что он может обрабатывать код, который не может компилироваться, например, отсутствует включает. Если директивы препроцессора сильно используются, может потребоваться --cpp возможность предварительной обработки кода.
    • cflow + cflow2dot + dot (GPL v2, GPL v3, Eclipse Public License (EPL) v1) (обратите внимание, что cflow2dot нуждается в некоторой фиксации пути, прежде чем он будет работать)
    • cflow +cflow2dot.Баш (GPL v2,?)
    • cflow +cflow2vcg (GPL v2 , GPL v2)
    • расширенные cflow (GPL v2) со списком для исключения символов из графика
  • используя cscope:

  • ncc (cflow like)

  • KCachegrind (средство просмотра зависимостей KDE)
  • Calltree

следующие инструменты, к сожалению, требуют, чтобы код был компилируемым, потому что они зависят от вывода из gcc:

  • CodeViz (GPL v2) (слабое место: нужен компилируемый источник, потому что он использует gcc для сброса файлов cdepn)
  • gcc +Египет +dot (GPL v*, Perl = GPL / художественная лицензия, EPL v1) (egypt использует gcc для производства RTL, поэтому не удается для любого багги исходного кода или даже в случае, если вы просто хотите сосредоточиться на одном файле из большего проекта. Поэтому он не очень полезен по сравнению с более надежным cflow-основанные toolchains. Обратите внимание, что Египет по умолчанию поддерживает исключение вызовов библиотек из графика, чтобы сделать это уборщик.

кроме того, графики зависимостей файлов для C / C++ могут быть созданы с помощью crowfood.


поэтому я сделал еще несколько исследований, и нетрудно получить номера строк для узлов. Просто добавьте lineno опция для одного из этих вариантов, чтобы получить его. Так что используйте -fdump-tree-cfg-lineno или -fdump-tree-vcg-lineno. Мне потребовалось некоторое время, чтобы проверить, являются ли эти цифры надежный. В случае графа в VCG метка формата каждого узла содержит два числа. Это номера строк для начала и конца части кода, представленной этим узлом.


методы динамического анализа

в этом ответе я описываю несколько методов динамического анализа.

динамические методы фактически запускают программу для определения графика вызовов.

противоположностью динамических методов являются статические методы, которые пытаются определить его только из источника без запуска программы.

преимущества динамических методов:

  • ловит указатели функций и виртуальные вызовы 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

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

в правом нижнем углу выберите вкладку" график вызовов". Это показывает интерактивный график вызовов, что соответствует производительности в другие окна при нажатии на функции.

чтобы экспортировать график, щелкните его правой кнопкой мыши и выберите "Экспорт графика". Экспортированный PNG выглядит так:

из этого мы видим, что:

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

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

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

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

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

gcc -finstrument-functions + etrace

https://github.com/elcritch/etrace

-finstrument-functions добавляет обратные вызовы, etrace анализирует файл ELF и реализует все обратные вызовы.

Я не мог заставить его работать вместе к сожалению: почему '-finstrument-функции ' не работают для меня?

заявленный выход имеет формат:

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

вероятно, самый эффективный метод, кроме конкретной поддержки аппаратной трассировки, но имеет обратную сторону, что вам нужно перекомпилировать код.