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

Невыровненный доступ

Указатель на байтовый буфер приводят к uint32_t* и читают слово — на одном микроконтроллере всё работает, на другом программа падает в HardFault. Причина — невыровненный доступ: слово лежит не по адресу, кратному четырём. Ядра Cortex-M относятся к нему по-разному.

Обновлено Редакция GAW

Невыровненный доступ (unaligned access) — обращение к слову по адресу, не кратному 4, или к полуслову по нечётному адресу. Байтовые обращения выровнены всегда (DUI 0553B, разд. 3.3.5). Как ядро реагирует на невыровненный адрес, зависит от архитектуры и от команды.

Что разрешают разные ядра

ЯдроLDR, LDRH, STR, STRHLDM, STM, LDRD, STRDИсточник
Cortex-M0HardFaultHardFaultDUI 0497A, разд. 3.3.4
Cortex-M3, M4выполняются, медленнееUsageFault всегдаDUI 0553B, разд. 3.3.5
Cortex-M3, M4 с UNALIGN_TRP = 1UsageFaultUsageFaultDUI 0553B, разд. 4.3.7

Даже там, где доступ разрешён, он обычно медленнее выровненного, а некоторые области памяти его вообще не поддерживают. Например, к регистрам блока управления системой процессор невыровненно не обращается. Поэтому Arm рекомендует выравнивать данные, а для поиска случайных невыровненных доступов — включать бит UNALIGN_TRP в регистре CCR. Отказ отмечается битом UNALIGNED регистра UFSR, а если UsageFault не разрешён, он эскалируется в HardFault.

Откуда берётся в программе

  • Приведение указателя. *(uint32_t *)(buf + 3) — чтение слова из середины байтового буфера, типичное при разборе пакетов UART, CAN или Modbus.
  • Указатель на поле упакованной структуры. __attribute__((packed)) даёт полю наименьшее выравнивание — один байт (GCC, Common Attributes). Пока к полю обращаются через саму структуру, компилятор это знает и генерирует подходящий доступ. Опасно взять адрес поля в обычный uint32_t * или uint64_t *: сведения о выравнивании теряются, и для 64-битного значения компилятор вправе выбрать LDRD, которая падает и на Cortex-M4.
  • Внешний формат данных. Заголовки файлов и сетевые пакеты кладут поля подряд, без выравнивания.

Надёжный способ — копировать поле в выровненную переменную через memcpy или собирать его из байтов сдвигами. Второй способ заодно решает вопрос порядка байтов.

Где читать подробнее

Набор команд загрузки и сохранения Thumb-2 разобран в статье Thumb-2, устройство отказов и регистров состояния — в статье о ядре Cortex-M3, а приёмы работы с памятью в C — в статье C для микроконтроллеров.

Частые вопросы

Почему код работает на STM32F4 и падает на STM32F0?

STM32F4 построен на Cortex-M4, который выполняет невыровненные LDR и STR (DUI 0553B, разд. 3.3.5). STM32F0 — на Cortex-M0, где любой невыровненный доступ вызывает HardFault (DUI 0497A, разд. 3.3.4). Код, который читает uint32_t из середины байтового буфера, переносим только через memcpy или побайтовую сборку.

Можно ли запретить невыровненный доступ и на Cortex-M4?

Да: бит UNALIGN_TRP регистра CCR (адрес 0xE000ED14) включает UsageFault на любое невыровненное обращение к слову или полуслову. Так ищут скрытые невыровненные доступы ещё на этапе отладки, чтобы код потом без сюрпризов перенёсся на Cortex-M0.

Какие команды падают даже без UNALIGN_TRP?

LDM, STM, LDRD, STRD и другие команды, кроме LDR, LDRH, LDRSH, STR, STRH и их непривилегированных вариантов с суффиксом T: при невыровненном адресе они вызывают UsageFault всегда. Поэтому опасен указатель, который обещает компилятору выравнивание, которого нет: например, адрес поля упакованной структуры, сохранённый в uint64_t * или uint32_t * и разыменованный, — для такого доступа компилятор вправе выбрать LDRD или LDM.

Источники

  1. Arm. Cortex-M4 Devices Generic User Guide, ARM DUI 0553B (ID012616) — разд. 3.3.5: выровненный доступ — слово по адресу, кратному 4, полуслово — кратному 2, байт выровнен всегда; невыровненный доступ поддерживают только LDR, LDRT, LDRH, LDRHT, LDRSH, LDRSHT, STR, STRT, STRH, STRHT, остальные команды загрузки и сохранения при невыровненном адресе вызывают UsageFault; невыровненный доступ обычно медленнее, некоторые области памяти могут его не поддерживать; разд. 4.3.7: бит 3 UNALIGN_TRP регистра CCR (0xE000ED14) включает отказ на любой невыровненный доступ к слову и полуслову, невыровненные LDM, STM, LDRD, STRD вызывают отказ всегда; разд. 4.3.10: бит UNALIGNED регистра UFSR; разд. 4.3.15: процессор не поддерживает невыровненный доступ к регистрам блока управления системой
  2. Arm. Cortex-M3 Devices Generic User Guide, ARM DUI 0552A — разд. 3.3.5: Cortex-M3 поддерживает невыровненный доступ только для того же набора команд LDR, LDRT, LDRH, LDRHT, LDRSH, LDRSHT, STR, STRT, STRH, STRHT
  3. GNU. GCC 16 manual, 6.4.1 Common Attributes — атрибут packed у члена структуры задаёт наименьшее возможное выравнивание — один бит для битового поля и один байт для остальных, если aligned не задаёт большее; для структуры целиком действует на все её члены
  4. Arm. Cortex-M0 Devices Generic User Guide, ARM DUI 0497A — разд. 3.3.4: Cortex-M0 не поддерживает невыровненный доступ, любая попытка вызывает исключение HardFault

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