Статьи

Анализ использования статического стека GNU

Переполнение стека — большая проблема: если я вижу сбой системы, первое, что я обычно делаю, я пытаюсь увеличить размер стека, чтобы посмотреть, исчезнет ли проблема. Линкер GNU может проверить, вписываются ли мои глобальные переменные в RAM. Но он не может знать, сколько мне нужно стека. Так как здорово было бы иметь возможность узнать, сколько стека мне нужно?

Статический анализ использования стека с помощью GNU

И действительно, это возможно с помощью инструментов GNU (например, я использую его с компиляторами GNU ARM Embedded (launchpad) 4.8 и 4.9 :-). Но похоже, что эта способность широко не известна?

обзор

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

  1. Заполните память стека определенным шаблоном.
  2. Пусть приложение запустится.
  3. Проверьте с помощью отладчика, какая часть этого стекового шаблона была перезаписана.

Это работает довольно хорошо. За исключением того, что это очень эмпирически. Что мне нужно, это некоторые цифры из компилятора, чтобы иметь лучшее представление.

В этой статье я представляю подход с инструментами GNU плюс сценарий Perl, чтобы сообщить об использовании стека в приложении

-fstack-usageОпция компилятора GNU

Пакет компиляторов GNU имеет интересную опцию: -fstack-usage

«Модуль, скомпилированный с -fstack-usage, сгенерирует дополнительный файл, который определяет максимальный объем используемого стека для каждой функции. Файл имеет то же базовое имя, что и целевой объектный файл с .suрасширением ». ( https://gcc.gnu.org/onlinedocs/gnat_ugn/Static-Stack-Usage-Analysis.html )

Если я добавлю эту опцию в настройки компилятора, теперь будет файл .su (использование стека) вместе с каждым файлом объекта (.o):

Файл использования стека

Файлы представляют собой простые текстовые файлы, подобные этим:

main.c:36:6:bar    48    static
main.c:41:5:foo    88    static
main.c:47:5:main    8    static

В нем перечислены исходный файл (main.c), строка (35) и столбец (5), позиция функции, имя функции (bar), использование стека в байтах (48) и распределение (статическое, это нормальный случай).

Создание стекового отчета

Хотя файлы .su уже являются отличным источником информации о файлах и функциях, как их объединить, чтобы получить полную картину? Я нашел Perl-скрипт (avstack.pl), разработанный Дэниелом Бером (см. Http://dlbeer.co.nz/oss/avstack.html ).

Из исходного скрипта вам может понадобиться адаптировать $objdumpи $call_cost. С помощью $objdumpя указываю команду GNU objdump (убедитесь, что она присутствует в PATH) и $call_costявляется постоянным значением, добавляемым к стоимости для каждого вызова:


my $objdump = "arm-none-eabi-objdump";
my $call_cost = 4;

Вызовите avstack.pl со списком объектных файлов, например

avstack.pl ./Debug/Sources/main.o ./Debug/Sources/application.o

: idea: Вам нужно перечислить все объектные файлы, у сценария нет возможности использовать все файлы .o в каталоге. Я обычно помещаю вызов Perl-файла в пакетный файл, который я вызываю на этапе после сборки (см. « Выполнение нескольких команд как этапы после сборки в Eclipse »).

Это создает отчет как это:

  Func                               Cost    Frame   Height
------------------------------------------------------------------------
> main                                176       12        4
  foo                                 164       92        3
  bar                                  72       52        2
> INTERRUPT                            28        0        2
  __vector_I2C1                        28       28        1
  foobar                               20       20        1
R recursiveFunct                       20       20        1
  __vector_UART0                       12       12        1

Peak execution estimate (main + worst-case IV):
  main = 176, worst IV = 28, total = 204
  • Имена функций с «>» в начале показывают «корневые» функции: они не вызываются откуда-либо еще (возможно, я не передал все объектные файлы или действительно не используется).
  • Если функция рекурсивная, она помечается как ‘R’ . Смета будет для одного уровня рекурсии.
  • Стоимость показывает совокупное использование стека (эта функция плюс все вызываемые абоненты).
  • Frame — это размер стека, используемый в файле .su, включая $call_costконстанту.
  • Высота указывает количество уровней вызовов, вызванных этой функцией.

Обратите внимание на запись INTERRUPT: это уровень стека, необходимый для прерываний. Инструмент предполагает не вложенные прерывания: он учитывает использование стека Interrupt Vector (IV) в худшем случае до пикового выполнения:

Peak execution estimate (main + worst-case IV):
  main = 176, worst IV = 28, total = 204

То, что считается подпрограммой прерывания, контролируется этой частью в скрипте Perl, поэтому каждая функция, начинающаяся с __vector_, обрабатывается как подпрограмма прерывания:


# Create fake edges and nodes to account for dynamic behaviour.
$call_graph{"INTERRUPT"} = {};

foreach (keys %call_graph) {
    $call_graph{"INTERRUPT"}->{$_} = 1 if /^__vector_/;
}

Код сборки

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

  Func                               Cost    Frame   Height
------------------------------------------------------------------------
> HF1_HardFaultHandler                  0        0        1

Компилятор предупредит меня об этом:

вычисление использования стека не поддерживается для этой цели

: idea: Я не нашел способа предоставить эту информацию компилятору в исходном коде.

Задачи RTOS

Инструмент прекрасно работает и готов к работе для задач в системе, основанной на RTOS (например, FreeRTOS). Таким образом, с помощью инструмента я получаю хорошую оценку использования каждого стека задач, но мне нужно посчитать до этого значения использование стека прерываний:

  Func                               Cost    Frame   Height
------------------------------------------------------------------------
> ShellTask                           712       36       17

-Wstack-usage Предупреждение

Еще одна полезная опция компилятора -Wstack-usage. С этой опцией компилятор выдаст предупреждение всякий раз, когда использование стека превышает заданный предел.

Возможность предупредить об использовании стека

Таким образом, я могу быстро проверить, какие функции превышают лимит:

предупреждение об использовании стека

Резюме

Пакет компилятора GNU поставляется с очень полезной опцией, -fstack-usageкоторая генерирует текстовые файлы для каждого модуля компиляции (исходный файл), в которых указывается использование стека. Эти файлы могут быть обработаны в дальнейшем, и я использую отличный Perl-скрипт, созданный Дэниелом Биром (спасибо!). С представленными инструментами и методами я получаю оценку использования стека заранее. Я знаю, что это только оценка, что рекурсия учитывается только на минимальном уровне, а код сборки не учитывается. Я мог бы расширить файл Perl для сканирования папок на предмет наличия всех объектных файлов в нем, если кто-то уже сделал это? Если так, пожалуйста, оставьте комментарий и поделитесь :-).