Раскраска стека (stack painting)
Компилятор знает размер кадра каждой функции, но не знает, какие цепочки вызовов и прерываний сложатся в работе. Самый простой способ узнать реальную глубину стека — заранее заполнить свободную память известным значением и посмотреть, сколько его осталось нетронутым.
Обновлено Редакция GAW
Раскраска стека (stack painting) — приём измерения реального расхода стека: при старте программы свободную область памяти заполняют известным значением, а во время работы или после теста ищут, докуда это значение затёрто. Граница нетронутой области — самая глубокая точка, до которой доходил стек.
Как это сделано в avr-libc
В AVR-LibC 2.3 для этого есть функция _get_ram_unused() из <util/ram-usage.h>:
- Код запуска в секции
.init3заполняет ОЗУ от__heap_start— сразу за статическими переменными — доRAMENDвключительно. Значение по умолчанию — 0xAA. Раскраска подключается, только если функция действительно вызывается в программе. - Программа в нужный момент вызывает
_get_ram_unused()и получает число байтов, где шаблон уцелел.
Документация подчёркивает смысл результата: это оценка сверху свободной памяти на момент вызова и, соответственно, оценка снизу занятой. Со временем значение может только уменьшаться. Функция использует мало регистров, поэтому её допустимо вызывать и из прерывания. Поскольку на AVR динамическая память растёт навстречу стеку из того же промежутка, измерение показывает их совместный расход.
На Cortex-M
Готовой функции нет, но принцип тот же. Область стека определяет скрипт компоновщика: в шаблоне ST для STM32F103 вершина _estack и минимальный размер _Min_Stack_Size. Код запуска до вызова main() заполняет область шаблоном, а проверка ищет первое изменённое слово с дальнего от вершины конца. Размер зарезервированной области виден в map-файле.
Статический расчёт и измерение
GCC с флагом -fstack-usage записывает для каждой функции размер её кадра в файл .su. Сумма по самой длинной цепочке вызовов плюс кадры прерываний даёт оценку худшего случая, но рекурсию, указатели на функции и alloca компилятор не охватит. Раскраска, наоборот, показывает реальный расход, но только в тех сценариях, которые успели случиться. Вместе они дают надёжный запас. Защита от переполнения через MPU, канарейки и разбор последствий — в статье о переполнении стека.
Частые вопросы
Почему раскраска стека даёт только оценку снизу?
Она показывает максимум, до которого стек дошёл за время наблюдения. Если самая глубокая цепочка вызовов или вложенных прерываний за это время не случилась, она в измерение не попала. Кроме того, программа может случайно записать в стек байт, равный шаблону, — avr-libc поэтому называет результат нижней границей использованной памяти.
Чем раскраска лучше расчёта по -fstack-usage?
Они дополняют друг друга. Флаг -fstack-usage даёт размер кадра каждой функции, но для полной глубины нужно сложить их по графу вызовов и добавить прерывания, а рекурсию и указатели на функции компилятор не учтёт. Раскраска показывает реальное потребление в конкретных сценариях работы. Надёжная практика — посчитать худший случай статически и проверить раскраской на стенде.
Как раскрасить стек на STM32?
В коде запуска, до вызова main, заполнить область стека известным словом — обычно её границы берут из символов скрипта компоновщика, например _estack и размера _Min_Stack_Size. Во время работы функция проверки ищет первое изменённое слово от дальнего конца области. Готовой функции, как в avr-libc, в CMSIS нет, её пишут сами.
Источники
- AVR-LibC 2.3.2: include/util/ram-usage.h, libc/misc/init_ram_color.S, libc/misc/ram_unused.S — функция _get_ram_unused() (с версии 2.3): код запуска раскрашивает ОЗУ в секции .init3 — только если функция действительно используется — от __heap_start (сразу за статическими данными) до RAMEND включительно; функция считает, сколько раскрашенных байтов осталось нетронутыми, это нижняя граница использованной памяти; возвращаемое значение — верхняя граница свободной памяти на момент вызова и со временем не растёт; значение раскраски по умолчанию __ram_color_value = 0xAA; функция с малым числом регистров, её можно вызывать из прерывания
- GCC 16.2 Manual: Developer Options (-fstack-usage) — -fstack-usage: компилятор выводит размер стека для каждой функции в файл .su — имя функции, число байтов и квалификаторы static, dynamic, bounded
Нашли ошибку?
Смотрите также
- Переполнение стека и памятьОценка стека, раскраска ОЗУ, защитная область MPU, HardFault, куча
- СтекОбласть ОЗУ для адресов возврата и локальных переменных
- Куча (heap)ОЗУ для malloc между .bss и стеком
- Код запуска (startup)Что выполняется до main: стек, .data, .bss
- Map-файлОтчёт компоновщика о том, где в памяти лежат секции, функции и переменные
- Скрипт компоновщикаФайл .ld с картой памяти и правилами размещения секций прошивки