Перейти к содержанию

Почему вызов функции по указателю даёт 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)0INVSTATE на адресе 0INVSTATE
адрес из map-файла без +10INVSTATE на этом адресеINVSTATE
мусор в чётном адресе ОЗУ0INVSTATEINVSTATE
мусор в нечётном адресе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 до вызова.

Источники

  1. 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
  2. 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
  3. Arm. Procedure Call Standard for the Arm Architecture (AAPCS32), репозиторий ARM-software/abi-aa, коммит 7850367 — таблица фундаментальных типов: указатель на код — 4 байта, бит 0 указывает набор команд цели: 0 — Arm, 1 — Thumb
  4. Arm. Cortex-M0 Devices Generic User Guide, DUI 0497A, 2009 — 2.4 — все отказы вызывают HardFault, в том числе выполнение команды не в состоянии Thumb после сброса бита T и BKPT без отладчика

Нашли ошибку?