Существует ли программный способ проверки повреждения стека

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

Мое решение, Я зарегистрировал таймер на 10 мс, и этот таймер проверит повреждение стека.

Метод проверки коррупции стека, 1. Инициализируйте память стека с помощью некоторого уникального шаблона (я использую 0x5A5A5A5A) 2. Проверьте время, если верхняя часть памяти стека по-прежнему равна 0x5A5A5A5A

Мой вопрос,

Есть ли лучший способ проверить этот тип коррупции

Забыл добавить, добавив: OS: Itron, Процессор: ARM9. Компилятор: не является GCC (определенный ARM9, поставляемый поставщиком процессора)... И нет встроенной поддержки проверки стека...

Ответ 1

При работе на встроенной платформе в последнее время я выглядел высоко и низко для способов сделать это (это было на ARM7).

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

Я также "сворачивал свои собственные" векторы исключений для data_abort и т.д. В "сети" для возврата стека вызовов есть несколько замечательных примеров. Это то, что вы можете сделать с отладчиком JTAG, сломать, когда произойдет какой-либо из этих прерванных векторов, а затем изучить стек. Это может быть полезно, если у вас есть только 1 или 2 точки останова (что, по-видимому, является нормой для отладки ARM JTAG).

Ответ 2

ARM9 поддерживает поддержку отладки JTAG/ETM; вы должны иметь возможность настроить точку доступа для доступа к данным, например, 64 байта в верхней части ваших стеков, которые затем инициируют прерывание данных, которое вы могли бы поймать в своей программе или извне.

(Аппаратное обеспечение, с которым я работаю, поддерживает только 2 точки чтения/записи, не уверен, что это ограничение на чипе или окружающий сторонний комплект отладки.)

Этот документ, который представляет собой чрезвычайно низкоуровневое описание того, как взаимодействовать с функциональностью JTAG, предлагает вам прочитать ваш процессор Техническое справочное руководство - и я могу поручиться за то, что в главе 9 ( "Поддержка отладки" ) имеется достаточное количество информации более высокого уровня для ARM946E-S r1p1 TRM.

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


Другая альтернатива: ваш компилятор может поддерживать опцию командной строки для добавления вызовов функций на точки входа и выхода функций (некоторая "void enterFunc (const char * callFunc)" и "void exitFunc (const char * callFunc)" ), для профилирования стоимости функций, более точной трассировки стека или аналогичной. Затем вы можете записать эти функции, чтобы проверить ваше канатное значение стека.

(В стороне, в нашем случае мы фактически игнорируем имя функции, которое передается (я бы хотел, чтобы линкер лишил их), и просто используйте значение регистра канала (LR) для записи, откуда мы пришли Мы используем это для получения точных трассировок вызовов, а также для профилирования информации, проверка стека канарейки в этот момент тоже будет тривиальной!)

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


Очень позднее обновление: в наши дни, если у вас есть конвейер, основанный на clang + LLVM, вы можете использовать Address Sanitizer (ASAN) поймать некоторые из них. Будьте в поиске похожих функций в своем компиляторе! (Это стоит знать о UBSAN и других дезинфицирующих средствах.)

Ответ 3

Какой компилятор вы используете? Я догадываюсь о конкретной ОС. Если вы используете GCC, вы можете использовать Stack-Smashing Protector. Это может быть исправление для вашей производственной системы, предотвращающее проблему, а также позволит вам обнаружить ее в процессе разработки.

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

Ответ 4

Я сделал точно так же, как вы предложили на dsPIC, используя CMX-Tiny +, однако в проверке стека я также сохраняю "метку скрытой отметки" для каждого стека. Вместо того, чтобы проверять значение в верхней части стека, я повторяю сверху, чтобы найти первое значение без подписи, и если оно выше, чем раньше, я сохраняю его в статической переменной. Это выполняется в задаче с наименьшим приоритетом, так что она выполняется всякий раз, когда ничего не запланировано (по существу заменяя цикл ожидания, в RTOS вы можете подключить цикл простоя и сделать это там). Это означает, что он обычно проверяется чаще, чем периодическая проверка 10 мс; за это время весь планировщик может быть завинчен.

Моя методология заключается в том, чтобы увеличить размер стеков, выполнить код, затем проверить метки прилива, чтобы определить маржу для каждой задачи (и стек ISR - не забывайте об этом!) и соответственно отрегулировать стеки, если Мне нужно восстановить "потраченное впустую" пространство из огромных стеков (я не беспокоюсь, если пространство в противном случае не нужно).

Преимущество такого подхода заключается в том, что вы не ждете, пока стек не сломается, чтобы обнаружить потенциальную проблему; вы отслеживаете его при разработке и по мере того, как изменения проверяются. Это полезно, так как если повреждение попадает на TCB или адрес возврата, ваш планировщик может быть настолько сломан, что проверка никогда не срабатывает после переполнения.

Некоторые RTOS имеют встроенную функциональность (embOS, vxWorks, о которой я знаю). ОС, использующие оборудование MMU, может быть лучше, поставив стек в защищенном пространстве памяти, поэтому переполнение приведет к сбою данных. Это лучший способ, который вы ищете; ARM9 имеет MMU, но ОС, которые поддерживают его, как правило, дороже. QNX Neutrino возможно?

Дополнительная заметка

Если вы не хотите, чтобы проверка высокого уровня вручную выполнялась вручную, просто увеличьте количество стеков, скажем, 1K, а затем в задаче проверки стека остановите условие, когда маржа опустится ниже 1K. Таким образом, вы с большей вероятностью поймаете ловушку условия ошибки, пока планировщик будет оставаться жизнеспособным. Не доказательство дурака, но если вы начнете выделять объекты достаточно крупными, ударьте стек за один раз, сигнальные колокола в любом случае должны звонить вам в голову - его более частая медленная ползучесть стека, вызванная все более глубокой функцией вложенности и тому подобное, что это будет помогите с.

Clifford.

Ответ 5

Как упоминает Ли, лучшим вариантом может быть переход Electric Fence к вашему собственному компилятору ARM9. В противном случае формат ARM ABI и стека хорошо документирован, поэтому вы можете написать функцию CHECK_STACK, которая проверяет, что обратные адреса указывают на функции и т.д.

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

Ответ 6

У вас есть исходный код ядра? В последний раз, когда я писал ядро, я добавил (как опцию) проверку стека в самом ядре.

Всякий раз, когда будет происходить переключение контекста, ядро ​​проверяет два стека:

(1) Задача поменялась → , если задача взорвала его стек во время его запуска, сообщите об этом прямо сейчас.

(2) Задача назначения (целевая) → перед тем, как перейти в новую задачу, давайте удостовериться, что какой-то дикий код не сбивал его стек. Если его стек поврежден, даже не включайте задачу, мы ввернуты.

Теоретически стеки всех задач могут быть проверены, но приведенные выше комментарии объясняют, почему я проверил эти 2 стека (настраиваемый).

В дополнение к этому, код приложения может контролировать задачи (включая стек прерывания, если он есть) в режиме ожидания, тиковый ISR и т.д.

Ответ 7

Просмотрите эти похожие вопросы: обработка во встроенных системах и как я могу визуализировать использование шаблона памяти avr программа.

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

Настройте область памяти в MMU, которая будет использоваться для стека. Он должен быть ограничен двумя областями памяти, где MMU не разрешает доступ. Когда приложение будет запущено, вы получите исключение/прерывание сразу после.

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

MMU также может обнаруживать обращения нулевого указателя, если вы запретили доступ к памяти в нижней части вашего бара.

Если у вас есть источник RTOS, вы можете создать защиту от кластера и кучи в нем.

Ответ 8

В идеале valgrind будет поддерживать вашу платформу/ОС. Это шокирует меня тем, что вы не получаете отдельную область памяти vm для каждого стека потоков. Если есть способ создать приложение, чтобы он мог работать и на Linux, вы, вероятно, можете воспроизвести там ошибку и поймать ее с помощью valgrind.