1. Тема дня
Сегодня разбираем диагностику аварий, возникающих из-за:
невалидного указателя
выхода за границы массива
переполнения стека
use-after-free
double free
повреждения heap
неправильного DMA-буфера
ошибочного адреса периферии
гонки данных
исполнения повреждённого кодаДля STM32:
HardFault
MemManage
BusFault
UsageFault
CFSR / HFSR
BFAR / MMFAR
MSP / PSP
exception stack frame
PC / LR / xPSRДля ESP32:
Guru Meditation
register dump
backtrace
EXCCAUSE / EXCVADDR
Core Dump
GDB Stub
heap poisoning
hardware watchpointsГлавная мысль: Инструкция, на которой процессор упал, часто является только первой инструкцией, заметившей повреждение. Реальная ошибка могла произойти значительно раньше — при записи за границу буфера, освобождении объекта или работе DMA.
2. Зачем это нужно в твоих проектах
AT-парсер EC25 может повредить память из-за слишком длинного URC, неверной длины MQTT payload, строки без \0 или хранения указателя на уже перезаписанный DMA/ring buffer. Для DMA/ADC возможен сценарий:
ADC DMA пишет 512 samples
массив выделен только на 256 samples
DMA портит соседнюю структуру FreeRTOS
следующее переключение задач вызывает HardFaultДля CAN/RS-485 опасны неверные DLC/length, CRC за пределами кадра, указатель на stack buffer в queue. Crash должен превращаться в артефакт:
firmware version
Git SHA
reset reason
fault registers
PC/LR
task name
stack watermark
последние diagnostic events
точный ELF релиза3. Теория
4.3.1 3.1. Три момента ошибки
1. Момент повреждения.
2. Момент обнаружения.
3. Момент падения.Падение в free() или scheduler часто означает, что память была повреждена раньше.
4.3.2 3.2. Cortex-M faults
MemManage:
нарушение MPU, запрещённый доступ, исполнение из XN-region.
BusFault:
ошибка шины, отсутствующая память, ошибка Flash/RAM/peripheral access.
UsageFault:
undefined instruction, invalid state, деление на ноль, unaligned access.HardFault часто является эскалацией другого fault. Если HFSR.FORCED = 1, расшифровывай CFSR.
4.3.3 3.3. Главные fault-регистры STM32
uint32_t cfsr = SCB->CFSR;
uint32_t hfsr = SCB->HFSR;
uint32_t dfsr = SCB->DFSR;
uint32_t afsr = SCB->AFSR;
uint32_t bfar = SCB->BFAR;
uint32_t mmfar = SCB->MMFAR;
uint32_t shcsr = SCB->SHCSR;CFSR состоит из:
bits 0-7: MMFSR
bits 8-15: BFSR
bits 16-31: UFSRBFAR и MMFAR используются только если установлены valid-флаги.
4.3.4 3.4. Exception stack frame
Cortex-M автоматически сохраняет:
R0
R1
R2
R3
R12
LR
PC
xPSRPC — адрес аварийной инструкции, LR помогает понять вызов, R0-R3 часто содержат проблемный указатель или длину.
4.3.5 3.5. HardFault wrapper
void hardfault_capture(uint32_t *stack_pointer, uint32_t exc_return);
__attribute__((naked))
void HardFault_Handler(void)
{
__asm volatile(
"tst lr, #4 \n"
"ite eq \n"
"mrseq r0, msp \n"
"mrsne r0, psp \n"
"mov r1, lr \n"
"b hardfault_capture \n"
);
}EXC_RETURN bit 2 выбирает MSP/PSP. При FPU нужно учитывать extended frame.
4.3.6 3.6. Поиск строки по PC
arm-none-eabi-addr2line \
-e build/firmware.elf \
-f \
-C \
0x08012A9EИспользуй точный ELF именно той сборки, которая упала.
4.3.7 3.7. ESP32 Guru Meditation
Типовой вывод:
Guru Meditation Error:
Core 1 panic'ed (LoadProhibited)
PC : ...
A0/A1 : ...
EXCCAUSE: ...
EXCVADDR: ...
Backtrace: ...EXCVADDR = 0x00000000 часто означает NULL pointer, 0x00000010 — поле структуры через NULL+offset.
4.3.8 3.8. ESP32 Core Dump
Core Dump сохраняет регистры, стеки задач, TCB и список задач.
idf.py coredump-info
idf.py coredump-debugВ GDB:
info registers
info threads
thread apply all bt
bt
frame 0
info locals4.3.9 3.9. Heap poisoning и integrity checkpoints
ESP-IDF может использовать heap poisoning. Характерные значения:
0xABBA1234: head canary
0xBAAD5678: tail canary
0xCECECECE: неинициализированная память
0xFEFEFEFE: освобождённая памятьПроверки:
heap_caps_check_integrity_all(true);Их можно временно ставить до/после подозрительных участков.
4.3.10 3.10. Hardware watchpoint
Если известен адрес, который кто-то портит:
watch *(uint32_t *)0x20001000CPU остановится на инструкции, которая реально изменила этот адрес.
4. Типичные ошибки
1. Смотреть только на строку crash.
2. Использовать ELF другой версии.
3. Безусловно доверять BFAR/MMFAR.
4. Не учитывать FPU extended frame.
5. Делать printf() внутри HardFault.
6. Записывать Flash из fault handler.
7. Не проверять stack pointer перед чтением frame.
8. Игнорировать imprecise BusFault.
9. Исправлять проблему добавлением задержки.
10. Не архивировать crash artifacts.5. Практическое задание на 30-60 минут
Создай CRASH_DEBUG_POLICY.md:
# Crash debugging policy
1. Every production build archives ELF, MAP and sdkconfig.
2. Every crash contains firmware version and Git SHA.
3. STM32 faults save CFSR, HFSR, BFAR, MMFAR and stacked PC/LR.
4. ESP32 production builds provide panic output or Core Dump.
5. Fault handlers do not allocate memory or use blocking APIs.
6. Flash is not written directly from an unsafe fault context.
7. Known corrupt addresses are investigated with watchpoints.
8. Heap corruption is narrowed using integrity checkpoints.
9. Crash reproducers become unit or HIL regression tests.
10. A delay is never accepted as a final race-condition fix.Для STM32: добавь HardFault wrapper, вызови controlled fault, найди PC через addr2line. Для ESP32: включи core dump, вызови controlled assert, прочитай idf.py coredump-info.
6. Что почитать или попробовать дальше
- ESP-IDF Fatal Errors, Core Dump, Heap Memory Debugging, JTAG Debugging.
- ST materials по HardFault debugging.
- ARM Cortex-M programming manual для своего ядра.
Короткий итог
crash detected
→ сохранить firmware identity
→ определить fault class
→ получить stacked PC/LR
→ проверить CFSR/HFSR и fault address
→ найти строку через exact ELF
→ проверить disassembly и operands
→ понять: причина здесь или память испорчена раньше
→ integrity checks / MPU / watchpoint
→ reproducer
→ regression testЗадание
Crash произошёл в scheduler после ADC DMA transfer. Объясните проверку гипотезы раннего повреждения по lengths, ownership и сохранённому build evidence.
Критерии самопроверки: Не считать crash line первопричиной; указать reproducer и наблюдаемое bounds/ownership violation.
Показать ответ автора
Сопоставить configured transfer count с allocated destination capacity и lifetime, проверить соседнюю память/integrity checkpoints и разобрать crash по точному сохранённому ELF. Scheduler crash может быть поздним симптомом ранней DMA write.
Задание
Перед переносом исходного HardFault wrapper на другой Cortex-M перечислите compatibility checks для безопасного чтения exception frame.
Критерии самопроверки: Не заявлять применимость одного wrapper ко всем Cortex-M variants. Явно обозначить target-specific assumptions и избегать Flash writes или сложного logging в fault handler.
Показать ответ автора
Проверить actual core/exception architecture, выбор MSP/PSP через EXC_RETURN, FPU/extended-frame behavior и stack-pointer validity. Разбирать только поддерживаемые target fault registers и valid fault-address fields.