Как оценить использование библиотечных функций - PullRequest
0 голосов
/ 19 апреля 2019

Я пытаюсь вычислить максимальное использование стека встроенной программы с помощью статического анализа.

Я использовал флаг компилятора -fstack-usage, чтобы получить максимальное использование стека для каждой функции и флаг -fdump-rtl-expand для генерации графика всех вызовов функций.

Последним отсутствующим компонентом является использование в стеке встроенных функций.(на данный момент это всего лишь memset)

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

Может быть, есть какой-то способ компилироватьв функциях с флагом -fstack-usage?Или каким-то другим способом измерить использование их стека с помощью статического анализа?


Редактировать:

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

Ответы [ 2 ]

1 голос
/ 22 апреля 2019

Подход 1 (динамический анализ)

Вы можете определить размер стека во время выполнения, заполнив стек предопределенным шаблоном, выполнив memset, а затем проверив, сколько байтов было изменено.Это медленнее и сложнее, так как вам нужно скомпилировать пример программы, загрузить ее в целевой объект (если у вас нет симулятора) и собрать результаты.Вам также нужно быть осторожным с тестовыми данными, которые вы предоставляете функции, поскольку путь выполнения может изменяться в зависимости от размера, выравнивания данных и т. Д.

Для реального примера такого подхода проверьте Код Абсейла .

Подход 2 (статический анализ)

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

objdump -d /lib64/libc.so.6 | sed -ne '/<__memset_x86_64>:/,/^$/p' > memset.d
NUM_PUSHES=$(grep -c pushq memset.d)
LOCALS=$(sed -ne '/sub .*%rsp/{ s/.*sub \+\$\([^,]\+\),%rsp.*/\1/; p }' memset.d)
LOCALS=$(printf '%d' $LOCALS)  # Unhex
echo $(( LOCALS + 8 * NUM_PUSHES ))

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

Сборка AVR в целомболее сложный, потому что вы не можете легко определить выделение места для локальных переменных (модификация указателя стека разделена на несколько инструкций in, out и adiw, поэтому потребуется нетривиальный синтаксический анализ, например, в Python).Простые функции, такие как memset или memcpy, не используют локальные переменные, поэтому вы все равно можете использовать простые greps:

NUM_PUSHES=$(grep -c 'push ' memset.d)
NUM_RCALLS=$(grep -c 'rcall \+\.+0' memset.d)
# A safety check for functions which we can't handle
if grep -qi 'out \+0x3[de]' memset.d; then
  echo >&2 'Unable to parse stack modification'
  exit 1
fi
echo $((NUM_PUSHES + 2 * NUM_RCALLS))
0 голосов
/ 24 апреля 2019

Это не очень хороший ответ, но все же может быть полезным.

Многие из встроенных функций очень просты. Например, memset может быть реализовано как простой цикл. Из моего наблюдения видно, что компилятор избегает использования стека, если он может просто использовать регистры (что имеет смысл). Только очень длинная функция требует больше стека. Все, что нужно более коротким, это обратный адрес для инструкции ret.

Относительно безопасно предположить, что простые встроенные функции вообще не используют стек, кроме инструкций call и ret, поэтому объем памяти равен размеру указателя на функцию. (2 байта в моем случае)

Имейте в виду, что встроенные системы не всегда имеют архитектуру фон Неймана, и они часто хранят инструкции и данные в отдельной памяти. Размер указателей на функцию и данные может отличаться.

...