Почему вызов функции по указателю даёт HardFault на Cortex-M?
Обновлено Редакция GAW
Cortex-M выполняет только команды Thumb. Состояние процессора хранит бит T в регистре EPSR, а при переходе по адресу командами BX, BLX или загрузкой в PC этот бит берётся из младшего бита адреса. По разделу A4.1.1 ARMv7-M Architecture Reference Manual бит 0 должен быть единицей, иначе процессор попытается выполнить команду с T = 0 и получит UsageFault INVSTATE. AAPCS32 закрепляет то же для указателей на код: бит 0 говорит, какой набор команд у цели.
Что видно в регистрах
После сброса обработчик UsageFault выключен (бит USGFAULTENA в SHCSR равен 0), поэтому отказ эскалируется в HardFault. В HFSR стоит FORCED, а в CFSR — бит INVSTATE. Если указатель попал в область, из которой выполнять код нельзя (XN), отказ будет другим — MemManage с битом IACCVIOL, и он тоже уходит в HardFault, пока MemManage не включён.
| Указатель | Бит 0 | Что происходит | CFSR |
|---|---|---|---|
| адрес функции, взятый компилятором | 1 | нормальный вызов | — |
| NULL (0x00000000) | 0 | INVSTATE на адресе 0 | INVSTATE |
| адрес из map-файла без +1 | 0 | INVSTATE на этом адресе | INVSTATE |
| мусор в чётном адресе ОЗУ | 0 | INVSTATE | INVSTATE |
| мусор в нечётном адресе | 1 | переход в мусор, другая ошибка | UNDEFINSTR или IBUSERR |
| адрес в области XN, например периферия 0x4000 0000–0x5FFF FFFF | любой | выборка команды запрещена | MemManage IACCVIOL |
Как прочитать кадр стека и найти адрес возврата, показывает статья о кадре стека исключения, общий порядок разбора — статья о HardFault.
Как исправить
- Проверяйте указатель перед вызовом:
if (cb != NULL) cb();— особенно для обратных вызовов, которые заполняются позже. - Если адрес функции получен как число, установите бит 0:
(void (*)(void))(addr | 1u). - Включите UsageFault битом USGFAULTENA, чтобы такие ошибки попадали в свой обработчик, а не в общий HardFault.
Регистры отказов Cortex-M3 перечислены на странице Cortex-M3, а другие частые источники HardFault — переполнение стека и порча указателей — в статье «Переполнение стека на микроконтроллере».
Похожие вопросы
Как найти, откуда был вызов?
В кадре стека, который ядро сохранило при входе в обработчик, смотрите два слова: PC — адрес, по которому пытались перейти (0 или чётный), и LR — адрес возврата в вызывающую функцию. Именно LR укажет строку с вызовом по указателю, если сопоставить его с map-файлом или отладочной информацией.
Откуда в указателе берётся сброшенный бит 0?
Из ручных преобразований: адрес функции записали числом из map-файла, прочитали из таблицы во флеш-памяти, собрали из байтов или выровняли маской. Компилятор и компоновщик сами ставят бит 0 для функций Thumb, поэтому указатели, полученные взятием адреса функции, правильные.
Почему на Cortex-M0 нет флага INVSTATE?
У Cortex-M0 нет отдельного обработчика UsageFault: по руководству Arm все отказы, включая выполнение команды не в состоянии Thumb, сразу вызывают HardFault. Причину определяют по кадру стека и коду, поэтому на M0 и M0+ особенно полезно проверять указатели на NULL до вызова.
Источники
- Arm. ARMv7-M Architecture Reference Manual, DDI 0403E.e, 2021 — A4.1.1 (с. A4-103) — Armv7-M поддерживает только состояние Thumb, бит 0 адреса в BX, BLX, LDR и LDM в PC должен быть 1, при EPSR.T = 0 возникает UsageFault INVSTATE; B1.5.14 — отключённый UsageFault эскалируется в HardFault; B3.2.15 — CFSR, UFSR.INVSTATE — бит 1
- Arm. Cortex-M3 Devices Generic User Guide, DUI 0552A, 2010 — 2.2.3, табл. 2-11 — области памяти с атрибутом XN: периферия 0x40000000–0x5FFFFFFF, внешние устройства, системная область; 2.4.2 — эскалация отказов в HardFault; табл. 4-25 — MMFSR.IACCVIOL: попытка выборки команды из области без права выполнения, возникает при любом обращении к XN, даже без MPU; табл. 4-24 — SHCSR.USGFAULTENA (бит 18) включает обработчик UsageFault; табл. 4-27 — UFSR.INVSTATE: сохранённый PC указывает на команду, которая неверно использовала EPSR
- Arm. Procedure Call Standard for the Arm Architecture (AAPCS32), репозиторий ARM-software/abi-aa, коммит 7850367 — таблица фундаментальных типов: указатель на код — 4 байта, бит 0 указывает набор команд цели: 0 — Arm, 1 — Thumb
- Arm. Cortex-M0 Devices Generic User Guide, DUI 0497A, 2009 — 2.4 — все отказы вызывают HardFault, в том числе выполнение команды не в состоянии Thumb после сброса бита T и BKPT без отладчика
Нашли ошибку?
Смотрите также
- HardFaultИсключение Cortex-M при необработанной ошибке
- Кадр стека исключенияВосемь регистров, которые Cortex-M сам сохраняет в стек при входе в исключение
- Cortex-M3Регистры, карта памяти, bit-banding, NVIC, SysTick, MPU, старт, пример
- Переполнение стека и памятьОценка стека, раскраска ОЗУ, защитная область MPU, HardFault, куча