Есть ли программный способ проверить повреждение стека - PullRequest
9 голосов
/ 15 сентября 2009

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

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

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

Мой вопрос,

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

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

Ответы [ 8 ]

8 голосов
/ 15 сентября 2009

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 и других дезинфицирующих средствах.)

6 голосов
/ 15 сентября 2009

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

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

4 голосов
/ 15 сентября 2009

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

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

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

3 голосов
/ 15 сентября 2009

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

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

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

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

Дополнительные примечания

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

Clifford.

2 голосов
/ 16 сентября 2009

Оформить эти похожие вопросы: Обработка переполнения стека во встроенных системах и Как я могу визуализировать использование памяти в программе AVR .

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

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

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

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

Если у вас есть источник ОСРВ, вы можете создать MMU-защиту стека и добавить в нее кучу.

2 голосов
/ 15 сентября 2009

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

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

(1) Замена задачи -> если задача взорвала свой стек во время работы, давайте узнаем прямо сейчас.

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

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

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

2 голосов
/ 15 сентября 2009

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

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

1 голос
/ 15 сентября 2009

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

...