Этот вопрос связан с вопрос, который я задал ранее в этот день: Интересно, возможно ли сгенерировать график звонящего из заданной функции (или имени символа, например, взятого из 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 предоставляет имена для символов, не предоставляемых генератором точек, в то время как генератор точек предоставляет гораздо больше деталей относительно вызовов.
В общем, вы не можете извлечь точный граф управления потоком из двоичного файла, из-за косвенные прыжки и звонки там. Непрямой вызов машинного кода входит в содержимое какого-либо регистра, и вы не можете надежно оценить все значения, которые может принять регистр (это может быть доказано эквивалентным проблема остановки).
Есть ли способ убедиться, что вся информация, необходимая для генерации графа вызовов из / в любую функцию, которая вызывается внутри этого двоичного файла?
нет, а также эта проблема эквивалентна проблеме остановки, таким образом, никогда не будет надежного способа получить этот граф вызовов (в полной и надежной форме).
Компилятор C ++ (обычно) генерирует косвенные переходы для вызовов виртуальных функций (они переходят через виртуальные таблицы) и, вероятно, при использовании общей библиотеки (читайте Drepper’s Как писать общие библиотеки бумага для большего).
Заглянуть в BINSEC инструмент (разработанный коллегами из CEA, LIST и INRIA), по крайней мере, для поиска ссылок.
Если вы действительно хотите найти большинство (но не все) динамических выделений памяти в исходном коде C ++, вы можете использовать статический анализ исходного кода (например, Frama-С или же Frama-Clang) и другие инструменты, но они не являются серебряной пулей.
Помните, что распределение функций, таких как malloc
или же operator new
может быть помещен в расположение указателя функции (и ваш код C ++ может иметь некоторые распределитель глубоко где-то похоронен, то вы, скорее всего, косвенный звонки в malloc
)
Может быть, вы могли бы потратить месяцы усилий на написание своего Плагин GCC искать звонки malloc
после оптимизации внутри компилятора GCC (но обратите внимание, что плагины GCC привязаны к одной конкретной версии GCC). Я не уверен, что оно того стоит. Мой старый (устарел, не поддерживается) GCC MELT проект было в состоянии найти звонки malloc
с размером выше некоторой заданной константы. Возможно, по крайней мере через год — конец 2019 года или позже — мой проект-преемник (bismon, финансируется КОЛЕСНИЦА Проект 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: Мэтт Годболт «Что мой компилятор сделал для меня в последнее время? Откручиваем крышку компилятора » говорить.
Других решений пока нет …