Как определить максимальное использование стека во встроенной системе с помощью gcc?

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

Мне сказали, что у компилятора gcc теперь есть опция -fstack-usage и -fcallgraph-info, которые можно каким-то образом использовать для статического вычисления точного «Максимального использования стека» для меня. («Анализ требований к стеку во время компиляции с помощью GCC» от Botcazou, Comar , и Хайнке).

Найджел Джонс говорит, что рекурсия - действительно плохая идея для встроенных систем («Вычисление размера вашего стека», 2009 г.), поэтому я старался не делать в этом коде каких-либо взаимно рекурсивных функций.

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

Без рекурсии или рекурсивных обработчиков прерываний должно быть возможно статическое определение максимального использования стека. (И поэтому большинство ответов на Как определить максимальное использование стека? действительно не применять). Насколько я понимаю, я (или, предпочтительно, некоторый бит кода на моем ПК, который автоматически запускается каждый раз, когда я перестраиваю исполняемый файл) сначала нахожу максимальную глубину стека для каждого обработчика прерывания, когда он не прерывается прерыванием с более высоким приоритетом, и максимальный глубина стека функции main (), когда она не прерывается. Затем я складываю их все, чтобы найти общую (в худшем случае) максимальную глубину стека. Это происходит (в моей встроенной системе), когда фоновая задача main () находится на максимальной глубине, когда она прерывается прерыванием с самым низким приоритетом, и это прерывание находится на максимальной глубине, когда оно прерывается следующим с самым низким приоритетом прерывание и так далее.

Я использую YAGARTO с gcc 4.6.0 для компиляции кода для LM3S1968 ARM Cortex-M3.

Итак, как мне использовать параметр -fstack-usage и -fcallgraph-info с gcc для расчета максимальной глубины стека? Или есть лучший подход для определения максимального использования стека?

(См. Как определить максимальное использование стека во встроенной системе? почти на тот же вопрос, адресованный компилятору Keil.)


person David Cary    schedule 17.06.2011    source источник
comment
Также обратите внимание, что любое использование указателей функций будет зафиксировано только динамическим анализом.   -  person Gabe    schedule 24.06.2011
comment
Чтобы получить информацию о вызывающем и вызывающем абонентах, вы можете использовать -fdump-ipa-cgraph. Опция callgraph, о которой вы говорите, не существует, afaik.   -  person parvus    schedule 31.03.2017
comment
Повторное включение прерываний непосредственно перед возвратом из ISR не предотвратит повторный вход в систему, которая допускает вложенные прерывания. Единственный способ добиться этого - отключить прерывания в ISR и повторно включить их из основного кода.   -  person iheanyi    schedule 27.04.2019
comment
@iheanyi: А? Я очень осторожен, не повторно разрешаю прерывания перед инструкцией возврата из прерывания (RETI), поэтому я не понимаю ваш комментарий. stackoverflow.com/questions/52886592/; infocenter.arm.com/help /index.jsp?topic=/com.arm.doc.ddi0460d/; и т.д. подразумевают, что есть несколько других способов предотвращения повторного входа, которые не включают повторное включение прерываний в основном коде. Конкретный обработчик прерывания никогда не будет повторно введен (вложен), если этот обработчик никогда не повторно активирует прерывания до последнего RETI, верно?   -  person David Cary    schedule 01.05.2019
comment
Дэвид, перечитывая твой вопрос, я вижу, что ошибаюсь. Предполагая, что вы отключаете прерывания при входе в ISR, повторное включение перед последним RETI гарантирует, что вы не сможете повредить какие-либо данные, затронутые в ISR. Неважно, войдете ли вы в ISR повторно или нет.   -  person iheanyi    schedule 02.05.2019


Ответы (7)


Документы GCC:

-fstack-использование

Делает информацию об использовании стека вывода компилятора для программы для каждой функции. Имя файла для дампа создается добавлением .su к имени aux. auxname генерируется из имени выходного файла, если он явно указан и не является исполняемым файлом, в противном случае это базовое имя исходного файла. Запись состоит из трех полей:

  • Имя функции.
  • Количество байтов.
  • Один или несколько квалификаторов: статический, динамический, ограниченный.

Квалификатор static означает, что функция управляет стеком статически: фиксированное количество байтов выделяется для кадра при входе в функцию и освобождается при выходе из функции; никакие другие настройки стека в функции не выполняются. Второе поле - это фиксированное количество байтов.

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

Я не могу найти никаких ссылок на -fcallgraph-info

Вы можете потенциально создать необходимую информацию из -fstack-usage и -fdump-tree-optimized

Для каждого листа в -fdump-tree-optimized найдите его родителей и просуммируйте их номер размера стека (учитывая, что это число относится к любой функции с динамическим, но не ограниченным) из -fstack-usage, найдите максимальное из этих значений и это должно быть ваше максимальное использование стека.

person τεκ    schedule 17.06.2011
comment
W.r.t. Я не могу найти никаких ссылок на -fcallgraph-info, Этот вариант описан в анализе требований к стеку во время компиляции с помощью GCC. Компилятор gcc сообщества AdaCore ARM ELF GNAT поддерживает -fcallgraph-info. - person Chester Gillon; 13.06.2021

На всякий случай, если никто не найдет лучшего ответа, я опубликую то, что у меня было, в комментарии к вашему другому вопросу, хотя у меня нет опыта использования этих параметров и инструментов:

GCC 4.6 добавляет параметр -fstack-usage, который дает статистику использования стека для каждой функции.

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

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

person Michael Burr    schedule 17.06.2011

Довольно поздно, но для всех, кто смотрит на это, ответы, содержащиеся в объединении выходных данных из fstack-usage и инструментов графа вызовов, таких как cflow, могут оказаться совершенно неверными для любого динамического выделения, даже ограниченного, потому что нет информации о том, когда этот динамический стек происходит выделение. Поэтому невозможно узнать, к каким функциям следует применить значение. В качестве надуманного примера, если (упрощенный) вывод fstack-usage будет следующим:

main        1024     dynamic,bounded
functionA    512     static
functionB     16     static

и очень простое дерево вызовов:

main
    functionA
    functionB

Наивный подход к их объединению может привести к тому, что main -> functionA будет выбран в качестве пути максимального использования стека, длиной 1536 байт. Но если самое большое динамическое выделение стека в main () - это передать большой аргумент, такой как запись, в functionB () непосредственно в стек в условном блоке, который вызывает functionB (я уже сказал, что это было надумано), тогда действительно main -> functionB - это путь максимального использования стека размером 1040 байт. В зависимости от существующего программного обеспечения, а также для других целей с более ограниченным доступом, которые передают все в стеке, кумулятивные ошибки могут быстро привести вас к поиску совершенно неправильных путей, требующих значительно завышенных максимальных размеров стека.

Кроме того, в зависимости от вашей классификации «реентерабельных», когда речь идет о прерываниях, можно полностью пропустить некоторые выделения стека. Например, прерывание уровня 7 многих процессоров Coldfire является чувствительным к краям и поэтому игнорирует маску отключения прерывания, поэтому, если семафор используется для преждевременного выхода из инструкции, вы можете не считать его реентерабельным, но начальное выделение стека все равно произойдет раньше. семафор проверяется.

Короче говоря, вы должны быть предельно осторожны при использовании этого подхода.

person Community    schedule 27.09.2014
comment
Кстати, во встроенной работе я бы обычно избегал передачи значительно больших данных в стек. - person Technophile; 13.02.2018
comment
@Technophile Конечно, и моя первоначальная формулировка не очень хороша в этом отношении - любое динамическое распределение в стеке вносит неоднозначность, независимо от того, передается оно по значению или нет. Мой (теперь устаревший) опыт работы со встроенными системами связан с использованием статически распределенных куч и стека, но, если вы хотите выполнить какой-либо аудит существующей кодовой базы, вероятно, стоит отметить. - person ; 21.02.2018
comment
Это крайне надумано. Для того, чтобы снежный ком превратился в крайнюю ошибку, потребуется конструкция с глубоким вложением чередующихся вызовов функций, где одно условие выделяет очень мало памяти и вызывает функцию с большим использованием стека и противоположным условием, которое выделяет много памяти и вызывает функцию с использование небольшого стека. Не только это, но и дизайн каким-то образом гарантирует, что это не случайно, но что путь от функции малого использования никогда не приведет к функции, которая использует большой объем памяти стека. - person iheanyi; 27.04.2019
comment
Вероятность того, что кто-то случайно спроектирует это в своем коде, но не сможет учесть это при вычислении использования стека, довольно мала. Чем больше становится ваше приложение, тем меньше разница между наивным подходом и точным вычислением. Кроме того, тем труднее становится вычислить точное. Я не понимаю, как можно пройти через такой строгий дизайн, который приведет к тому, что наивный подход окажется в корне неверным. . .и каким-то образом не может объяснить, как дизайн повлияет на использование стека. - person iheanyi; 27.04.2019
comment
@iheanyi Вы упустили суть и сделали два комментария. Таким образом, вам необходимо понимать ограничения любого подхода, прежде чем вы сможете оценить его полезность в ваших обстоятельствах. Надуманный пример (мои собственные слова, пять лет назад) явно иллюстративный, а не исчерпывающий. Эти инженеры лучше всего решают, являются ли проблемы актуальными или значительными с учетом кодовой базы, библиотек и варианта использования (например, академический или нормативный). Тем не менее, я определенно не хотел бы использовать инструменты статического анализа, написанные инженерами, которые устраняют пробелы в охвате описанными вами способами. - person ; 28.04.2019
comment
Ваш пример производит неверное впечатление. Инженеры всегда должны стремиться понимать ограничения своих инструментов. Подобные надуманные примеры создают впечатление, что наивный подход плох, когда он на самом деле неоптимален и дает хорошие результаты в большинстве случаев. Я не уверен, что вы думаете о статическом анализе. Цель анализа использования стека сильно отличается от цели статического анализа, который сам по себе также не поддается точным решениям. Если вы утверждаете, что не следует использовать статический анализ, потому что никто не может предоставить точное решение - это тоже недальновидно. - person iheanyi; 28.04.2019
comment
@iheanyi Впечатления субъективные. Инженеры могут определить, актуальна ли для них информация. Этот пример - не что иное, как конкретная иллюстрация предостережений в отношении этого подхода, без чрезмерного ограничения контекста в OP. Я не должен предполагать, исключают ли какие-либо читательские практики разработки, что это проблема. Если вы не согласны с тем, что это ответ, а не комментарий, я бы, вероятно, согласился бы задним числом, но я не нашел конкретный характер ваших комментариев конструктивным или уместным с учетом фактического намерения. Больше обсуждать нечего. - person ; 28.04.2019
comment
С моей точки зрения, читая ответ, вы делаете предположения о методах читателя и запрещаете какой-либо образ действий, при этом категорически предполагая, что иначе будет катастрофой. Мой комментарий объясняет, почему я не согласен. - person iheanyi; 29.04.2019

В итоге я написал сценарий на Python для реализации ответа от τεκ. Слишком много кода для публикации здесь, но его можно найти на github.

person PeterM    schedule 20.05.2016

Я не знаком с параметрами -fstack-usage и -fcallgraph-info. Однако всегда можно выяснить фактическое использование стека:

  1. Выделите достаточное пространство стека (для этого эксперимента) и инициализируйте его чем-нибудь легко идентифицируемым. Мне нравится 0xee.
  2. Запустите приложение и проверьте все его внутренние пути (по всем комбинациям входных данных и параметров). Дайте ему поработать более чем «достаточно долго».
  3. Изучите область стека и посмотрите, какая часть стека была использована.
  4. Установите размер стека плюс 10% или 20%, чтобы допускать обновления программного обеспечения и редкие условия.
person wallyk    schedule 17.06.2011
comment
OP специально ищет метод, который вычисляет наихудший случай на основе статического анализа, а не экспериментов во время выполнения. - person Michael Burr; 18.06.2011
comment
@Michael: Ну, ОП написал: Или есть какой-нибудь лучший подход для определения максимального использования стека? Конечно, старый метод даст полезный ответ. Это может быть проще, чем анализировать пути кода. - person wallyk; 18.06.2011
comment
Проблема с анализом времени выполнения заключается в том, что он работает только при очень небольшом количестве возможных путей. У большинства нетривиальных программ миллиарды, если не бесконечно много. - person Gilles 'SO- stop being evil'; 19.06.2011
comment
@Gilles Billions в программе, написанной для встроенной системы с 64 КБ ОЗУ? - person endolith; 17.03.2017
comment
@endolith Миллиарды состояний могут быть закодированы всего в 32 битах ОЗУ. - person Gilles 'SO- stop being evil'; 18.03.2017
comment
@endolith StackOverflow задуман как общий ресурс, и желательно, чтобы ответы были широко полезными. Несомненно, существуют встроенные системы с более чем 64 КБ; В настоящее время я работаю над одним с ОСРВ и более десятка потоков. - person Technophile; 13.02.2018
comment
@Gilles, возможно, вы сможете закодировать такое количество состояний в 32-битном формате, но как это соотносится с тестированием всех внутренних путей вашего приложения? Можно закодировать наибольшее обнаруженное простое число в относительно небольшом количестве битов по сравнению с вычислительным временем, необходимым для его вычисления. Я отдаю вам должное за снарка - но в этом нет никакого смысла. - person iheanyi; 27.04.2019
comment
Это может быть метод пожилых людей, но я бы не стал так сильно ему доверять. Обычно вы берете результаты из этого и применяете некоторый фактор выдумки, например, удвоение объема памяти. Дело в том, что для чего-либо, помимо тривиального приложения, время выполнения, которое вам понадобится для этого метода, чтобы дать вам хорошую оценку использования стека, увеличивается экспоненциально с количеством потенциальных вызовов функций. - person iheanyi; 27.04.2019
comment
@iheanyi Я понятия не имею, что вы пытаетесь сказать в своем первом комментарии. В моем не было шутки. Даже с крошечным пространством для кодирования состояний может быть слишком много возможных состояний, чтобы проанализировать их все. Практически в любой реальной программе, содержащей циклы, любой анализ наихудшего случая требует принятия решительных мер для приравнивания эквивалентных состояний. Т.е. что вы говорите в своем втором комментарии. - person Gilles 'SO- stop being evil'; 27.04.2019
comment
Это своего рода лучший / единственный метод, поскольку gcc не имеет представления о прерываниях. Вы не можете отслеживать прерывания с помощью статического анализа, точка. - person Lundin; 05.05.2021

Обычно существует два подхода - статический и динамический.

Статический: скомпилируйте свой проект с -fdump-rtl-expand -fstack-usage и из *.expand скрипта получите дерево вызовов и использование стека каждой функции. Затем переберите все листья в дереве вызовов и вычислите использование стека в каждом листе и получите максимальное использование стека. Затем сравните это значение с доступной памятью на цели. Это работает статически и не требует запуска программы. Это не работает с рекурсивными функциями. Не работает с массивами VLA. В случае, если sbrk() работает с разделом компоновщика, а не с предварительно выделенным статическим буфером, он не принимает во внимание динамическое выделение, которое может увеличиваться само по себе с другой стороны. В моем дереве есть сценарий , stacklyze.sh, с которым я исследовал этот вариант.

Время выполнения: до и после каждого вызова функции проверяйте текущее использование стека. Скомпилируйте код с -finstrument-functions. Затем определите две функции в своем коде, которые примерно должны получить текущее использование стека и работать с ними:

static unsigned long long max_stack_usage = 0;

void __cyg_profile_func_enter(void * this, void * call) __attribute__((no_instrument_function)) {
      // get current stack usage using some hardware facility or intrisic function
      // like __get_SP() on ARM with CMSIS
      unsigned cur_stack_usage = __GET_CURRENT_STACK_POINTER() - __GET_BEGINNING_OF_STACK();
      // use debugger to output current stack pointer
      // for example semihosting on ARM
      __DEBUGGER_TRANSFER_STACK_POINTER(cur_stack_usage);
      // or you could store the max somewhere
      // then just run the program
      if (max_stack_usage < cur_stack_usage) {
            max_stack_usage = max_stack_usage;
      }
      // you could also manually inspect with debugger
      unsigned long long somelimit = 0x2000;
      if (cur_stack_usage > somelimit) {
           __BREAKPOINT();
      }
}
void __cyg_profile_func_exit(void * this, void * call) __attribute__((no_instrument_function)) {
      // well, nothing
}

До и после выполнения каждой функции - вы можете проверить текущее использование стека. Поскольку функция вызывается до использования стека внутри функции, этот метод не принимает текущее использование стека функций - это всего лишь одна функция, которая мало что делает, и ее можно как-то смягчить, узнав, какая функция это, а затем получив стек. использование с -fstack-usage и добавление его к результату.

person KamilCuk    schedule 05.05.2021

В общем, вам необходимо объединить информацию графа вызовов с файлами .su, созданными -fstack-usage, чтобы найти наиболее глубокое использование стека, начиная с конкретной функции. Начиная с main() или точки входа потока, вы получите наихудший вариант использования этого потока.

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

person Clifford    schedule 05.05.2021
comment
Этот вопрос был процитирован как дубликат недавнего вопроса stackoverflow.com/questions/67397737/, но он устарел, и принятый ответ, возможно, является неполное или теоретическое решение. Я добавил эту информацию как более полное и практичное решение (которое не существовало, когда вопрос был первоначально задан). - person Clifford; 06.05.2021