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

Этот вопрос связан с вопросом, который я задал ранее в этот день: мне интересно, возможно ли сгенерировать график звонящего из заданной функции (или имени символа, например, взятого из nm), даже если интересующая функция не является частью "моего" исходного кода (например, находится в библиотеке, например malloc() )

Например знать где malloc используется в моей программе с именем foo Я бы сначала посмотрел имя символа:

nm foo | grep malloc
         U malloc@@GLIBC_2.2.5

А затем запустите инструмент (для которого может потребоваться специально скомпилированная / связанная версия моей программы или некоторые артефакты компилятора):

 find_usages foo-with-debug-symbols "malloc@@GLIBC_2.2.5"

Который будет генерировать (текстовый) график звонящего, который я затем смогу обработать дальше.

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

Прогресс

С помощью radare2 Мне удалось создать dot Граф вызывающего из исполняемого файла, но что-то по-прежнему не хватает. Я компилирую следующую программу на C++, которую, я уверен, должен использовать malloc() или же new:

#include <string>

int main() {
  auto s = std::string("hello");
  s += " welt";
  return 0;
}

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

g++ foo.cpp -static

Запустив nm a.out | grep -E "_Znwm|_Znam|_Znwj|_Znaj|_ZdlPv|_ZdaPv|malloc|free" Вы можете увидеть много символов, которые используются для выделения памяти.

Теперь я бегу radare2 на исполняемом файле:

r2 -qAc 'agCd' a.out > callgraph.dot

С небольшим сценарием (вдохновленным этим ответом) я ищу путь вызова из любого символа, содержащего "sym.operatornew", но, похоже, его нет!

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

Есть ли лучший способ запустить radare2? Похоже, что разные типы визуализации графа вызовов предоставляют разную информацию - например, генератор изображений ascii действительно предоставляет имена для символов, не предоставляемых генератором точек, в то время как генератор точек предоставляет гораздо больше деталей относительно вызовов.

1 ответ

В общем, вы не можете извлечь точный граф потока управления из двоичного файла из-за косвенных переходов и вызовов там. Косвенный вызов машинного кода запрыгивает в содержимое какого-либо регистра, и вы не можете надежно оценить все значения, которые может принять регистр (это может оказаться доказанным эквивалентным проблеме остановки).

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

Нет, и эта проблема эквивалентна проблеме остановки, поэтому никогда не будет надежного способа получить этот граф вызовов (в полной и надежной форме).

Компилятор C++ (обычно) генерирует косвенные переходы для вызовов виртуальных функций (они переходят через виртуальную таблицу) и, вероятно, при использовании разделяемой библиотеки (подробнее читайте в статье Дреппера " Как писать общие библиотеки").

Посмотрите на инструмент BINSEC (разработанный коллегами из CEA, LIST и INRIA), по крайней мере, чтобы найти ссылки.

Если вы действительно хотите найти большинство (но не все) динамических выделений памяти в исходном коде C++, вы можете использовать статический анализ исходного кода (например, Frama-C или Frama-Clang) и другие инструменты, но они не являются серебряной пулей.

Помните, что распределение функций, таких как malloc или же operator new может быть помещен в расположение указателя функции (и ваш код C++ может иметь где-то глубоко скрытый распределитель, тогда вы, вероятно, будете иметь косвенные вызовы malloc)

Возможно, вы могли бы потратить месяцы усилий на написание своего собственного плагина GCC для поиска звонков malloc после оптимизации внутри компилятора GCC (но обратите внимание, что плагины GCC привязаны к одной конкретной версии GCC). Я не уверен, что оно того стоит. Мой старый (устаревший, не обслуживаемый) проект GCC MELT смог найти звонки mallocс размером выше некоторой заданной константы. Возможно, по крайней мере через год - конец 2019 года или позже - мой проект-преемник ( bismon, финансируемый проектом CHARIOT H2020) может стать достаточно зрелым, чтобы помочь вам.

Помните также, что GCC способен к довольно причудливой оптимизации, связанной сmalloc, Попробуйте скомпилировать следующий код C

//file mallfree.c
#include <stdlib.h>
int weirdsum(int x, int y) {
  int*ar2 = malloc(2*sizeof(int));
  ar2[0] = x; ar2[1] = y;
  int r = ar2[0] + ar2[1];
  free (ar2);
  return r;
}

сgcc -S -fverbose-asm -O3 mallfree.c, Вы увидите, что генерируетсяmallfree.sассемблерный файл не содержит вызова mallocили free, Такая оптимизация разрешена правилом" как если" и практически полезна для оптимизации большинства случаев использования стандартных контейнеров C++.

Поэтому то, что вы хотите, непросто даже для явно "простого" кода C++ (и в общем случае невозможно).

Если вы хотите закодировать плагин GCC и потратить на этот вопрос более года (или могли бы заплатить за это не менее 500 тыс. Евро), свяжитесь со мной. Смотрите также https://xkcd.com/1425/ (ваш вопрос практически невозможен).

Кстати, что вас действительно волнует, так это динамическое выделение памяти в оптимизированном коде (вы действительно хотите встраивание и устранение мертвого кода, и GCC делает это довольно хорошо с -O3 или же -O2). Когда GCC не оптимизируется вообще (например, с -O0 которая является неявной оптимизацией), она будет выполнять много "бесполезного" динамического выделения памяти, особенно с кодом C++ (используя стандартную библиотеку C++). См. Также CppCon 2017: Мэтт Годболт "Что мой компилятор сделал для меня в последнее время? Откручиваем крышку компилятора ".

Другие вопросы по тегам