HardFault
Программа на STM32 внезапно зависает в бесконечном цикле HardFault_Handler. Это исключение ядра Cortex-M, в которое процессор попадает при серьёзной ошибке — обращении по неверному адресу, неверной команде, разрушенном стеке — если более точный обработчик не сработал.
Обновлено Редакция GAW
HardFault — системное исключение ядер Arm Cortex-M, в которое процессор переходит при ошибке, которую не может обработать никакой другой механизм. По руководству Arm (разд. 2.3.2) у него фиксированный приоритет −1 — выше любого настраиваемого прерывания; выше только NMI (−2) и сброс (−3). Его адрес — слово 3 (четвёртое по счёту) таблицы векторов, смещение 0x0C.
Откуда берётся
Arm перечисляет источники отказов (разд. 2.4):
- ошибка шины при выборке команды, чтении вектора или обращении к данным;
- неопределённая команда;
- попытка выполнить код из области, помеченной как неисполняемая (XN);
- попытка выполнить команду со сброшенным битом T — например, переход по адресу с младшим битом 0;
- нарушение правил блока защиты памяти.
У Cortex-M3 и M4 для этих случаев есть отдельные обработчики MemManage, BusFault и UsageFault, но если обработчик не включён или отказ случился внутри обработчика с тем же или более высоким приоритетом, происходит эскалация: отказ превращается в HardFault (разд. 2.4.2). Ошибка чтения таблицы векторов всегда сразу HardFault.
| Бит HFSR | Что значит |
|---|---|
| VECTTBL | ошибка шины при чтении вектора |
| FORCED | эскалация другого отказа в HardFault |
Как искать причину
Ядро при входе в исключение сохраняет в стек восемь слов, в том числе PC команды, на которой случилась ошибка. Достав его в обработчике и найдя адрес в map-файле, обычно сразу видят виноватую функцию. Типичные причины в прошивках — испорченный указатель, переполнение стека в чужие данные, обращение по адресу, где нет ни памяти, ни периферии. Деление на ноль по умолчанию не вызывает отказа: по ARMv7-M результат просто 0, пока не установлен бит CCR.DIV_0_TRP.
Не путать HardFault со сбросом от сторожевого таймера: HardFault — исключение, программа остаётся в обработчике, а сторож перезапускает микроконтроллер. Устройство ядра, регистры и исключения — в статье «Архитектура ARM Cortex-M3».
Частые вопросы
Почему STM32 попадает в HardFault?
Чаще всего из-за обращения по неверному адресу (нулевой или испорченный указатель — ошибка шины), из-за переполнения стека, перехода по адресу с младшим битом 0 (не Thumb) или потому, что отказы MemManage, BusFault и UsageFault не включены и эскалировали в HardFault. Регистр HFSR показывает, был ли это отказ чтения таблицы векторов (VECTTBL) или эскалация (FORCED).
Как найти место, где произошёл HardFault?
При входе в исключение ядро сохраняет в стек 8 слов, среди них PC команды, вызвавшей отказ. В обработчике определяют, какой стек использовался (по EXC_RETURN в LR), читают сохранённый PC и смотрят адрес в map-файле или отладчике. Регистры CFSR, HFSR, MMFAR и BFAR уточняют причину и адрес обращения.
Бывает ли HardFault на Cortex-M0?
Да, и там он единственный обработчик отказов: отдельных MemManage, BusFault и UsageFault у Cortex-M0 нет, все ошибки ведут в HardFault. На Cortex-M3 и старше эти три обработчика есть, но выключены после сброса, поэтому без настройки отказы тоже попадают в HardFault.
Источники
- Arm. Cortex-M3 Devices Generic User Guide, DUI 0552A, 2010 — разд. 2.3.2: HardFault — исключение из-за ошибки при обработке исключения или ошибки, которую не может обработать другой механизм; фиксированный приоритет −1; разд. 2.4: источники отказов — ошибка шины при выборке команды, чтении таблицы векторов или обращении к данным, неопределённая команда, выполнение из области XN, сброшенный бит T (вектор с младшим битом 0), нарушение MPU; табл. 2-18: VECTTBL, FORCED; табл. 4-12, 4-24: SHCSR после сброса 0x00000000 — обработчики MemManage, BusFault, UsageFault выключены; разд. 2.4.2: эскалация в HardFault — обработчик вызвал отказ того же или более низкого приоритета, обработчик отказа не включён
- Arm. ARMv7-M Architecture Reference Manual, DDI 0403E.e, 2021 — B1.5.6: при входе в исключение сохраняется кадр из 8 слов (0x20 байт), в том числе PC; A4.4.6, B3.2.8: деление на ноль даёт UsageFault только при CCR.DIV_0_TRP = 1, иначе результат 0
- Arm. Cortex-M0 Devices Generic User Guide, DUI 0497A — Exception types, Fault handling: HardFault — единственное исключение, обрабатываемое обработчиком отказов; все отказы ведут в HardFault
Нашли ошибку?