Переполнение стека — большая проблема: если я вижу сбой системы, первое, что я обычно делаю, я пытаюсь увеличить размер стека, чтобы посмотреть, исчезнет ли проблема. Линкер GNU может проверить, вписываются ли мои глобальные переменные в RAM. Но он не может знать, сколько мне нужно стека. Так как здорово было бы иметь возможность узнать, сколько стека мне нужно?
Статический анализ использования стека с помощью GNU
И действительно, это возможно с помощью инструментов GNU (например, я использую его с компиляторами GNU ARM Embedded (launchpad) 4.8 и 4.9 :-). Но похоже, что эта способность широко не известна?
обзор
Один подход, который я использовал в течение очень долгого времени:
- Заполните память стека определенным шаблоном.
- Пусть приложение запустится.
- Проверьте с помощью отладчика, какая часть этого стекового шаблона была перезаписана.
Это работает довольно хорошо. За исключением того, что это очень эмпирически. Что мне нужно, это некоторые цифры из компилятора, чтобы иметь лучшее представление.
В этой статье я представляю подход с инструментами 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 для сканирования папок на предмет наличия всех объектных файлов в нем, если кто-то уже сделал это? Если так, пожалуйста, оставьте комментарий и поделитесь :-).