Расшифровка отладочной информации ESP32

Отладка программы на микроконтроллере — необходимый навык для любого инженера, занятого разработкой электронных устройств. Методов отладки множество: мигание светодиодами, вывод ошибок в UART, отладка через специальные интерфейсы JTAG/SWD. В этой статье мы обратим внимание на отладку программы, написанной для микроконтроллера ESP32 (по факту SoC).

Данный микроконтроллер имеет встроенный механизм формирования отладочной информации и вывода её через UART-интерфейс. Так выглядит кадр отладочной информации ESP32-S3, формируемый при возникновении ошибки. Многие, кто программирует для ESP32, встречали такой лог и не знали, что с ним делать.

Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Core  1 register dump:

PC: 0x4200cd53  PS: 0x00060b30  A0: 0x8200d1cb  A1: 0x3fca3730
A2: 0x3fc9ea6c  A3: 0x000000b4  A4: 0x000000be  A5: 0x00000000
A6: 0x00000014  A7: 0x3fca3820  A8: 0x00000078  A9: 0x00000000
A10: 0x00000001  A11: 0x00000000  A12: 0x00000000  A13: 0x00000000
A14: 0x00000000  A15: 0x000000b4  SAR: 0x0000000a  EXCCAUSE: 0x0000001c
EXCVADDR: 0x00000000  LBEG: 0x4200bcb4  LEND: 0x4200bcbe  LCOUNT: 0x00000000

Backtrace: 0x4200cd50:0x3fca3730 0x4200d1c8:0x3fca3880 0x42007078:0x3fca38b0 0x4200888c:0x3fca3900 0x4200560f:0x3fca3920 0x42005c47:0x3fca3940 0x42016e68:0x3fca3960 0x4037cff6:0x3fca3980

Если посмотреть на этот набор шестнадцатеричных чисел взглядом IT-обывателя, то он мало что прояснит. Сверху — непонятное сообщение об ошибке. Ниже (Core 1 register dump) — состояние всех регистров. Ещё ниже (Backtrace) — вероятно, трассировка стека, но только без названий функций.

Примечание

Трассировка стека — это отчёт о цепочке вызовов функций, которая привела к ошибке. Это очень ценная информация для поиска ошибки. Именно её расшифровкой мы и займёмся.

Необходимая информация для расшифровки хранится в файле .elf, который генерируется во время компиляции программы. А для анализа этих .elf-файлов в esp-idf существуют специальные утилиты — addr2line.

Итак, чтобы расшифровать трассировку стека в ESP32, понадобится:

  • утилита addr2line (для ESP32-S3 это xtensa-esp32s3-elf-addr2line);

  • файл .elf.

Где взять утилиту addr2line?

Эта утилита устанавливается вместе с набором инструментов esp-idf. Соответственно, самый простой способ её получить — установить esp-idf. Ссылка на инструкцию по установке есть в конце статьи.

Утилиты для работы с конкретными ядрами микроконтроллеров живут в папке ~/.espressif/tools. Зайдём в эту папку и выберем подходящий пакет. Для работы с ESP32, ESP32-S2 или ESP32-S3 откроем папку xtensa-esp-elf. А вот у ESP32-C6 ядро другое — RISC-V, так что и утилита будет совершенно другая, в папке riscv32-esp-elf.

Спускаемся вниз до папки bin и находим там xtensa-esp32s3-elf-addr2line — это то, что нам нужно!

Также наличие нужной утилиты можно проверить так:

which xtensa-esp32s3-elf-addr2line

Подсказка

Видно утилиту будет только после подключения окружения esp-idf командой source. Как это сделать, читай ниже.

Где взять .elf?

Этот файл формируется каждый раз при сборке проекта для целей отладки и анализа. В отличие от .hex-файла, в .elf хранятся имена функций, что нам как раз и необходимо для расшифровки отладочной информации.

Например, в среде Arduino IDE файл лежит во временной папке проекта:

C:\Users\master\AppData\Local\arduino\sketches\26D11705A98E98C54B438CA83C79660E\test.ino.elf

Расшифровка отладочной информации (backtrace)

Итак, по шагам.

  1. Подключаем окружение esp-idf командой:

source export.sh
  1. Запускаем утилиту расшифровки, передавая в неё .elf и исходные коды отладочной информации:

xtensa-esp32s3-elf-addr2line -pfiaC -e test.ino.elf 0x4200cd50:0x3fca3730 0x4200d1c8:0x3fca3880 0x42007078:0x3fca38b0 0x4200888c:0x3fca3900 0x4200560f:0x3fca3920 0x42005c47:0x3fca3940 0x42016e68:0x3fca3960 0x4037cff6:0x3fca3980

Результат:

0x4200d1c8:0x3fca3880 0x42007078:0x3fca38b0 0x4200888c:0x3fca3900 0x4200560f:0x3fca3920 0x42005c47:0x3fca3940 0x42016e68:0x3fca3960 0x4037cff6:0x3fca3980
0x4200cd50: TFT_eSPI::pushImage(long, long, long, long, unsigned short*, unsigned short) at C:\Users\olege\AppData\Local\Programs\Arduino IDE/c:\Users\olege\Documents\Arduino\libraries\TFT_eSPI/TFT_eSPI.cpp:1504
0x4200d1c8: TFT_eSPI::frameEnd() at C:\Users\olege\AppData\Local\Programs\Arduino IDE/c:\Users\olege\Documents\Arduino\libraries\TFT_eSPI/TFT_eSPI.cpp:2469
0x42007078: SDGFX::drawItemBar(Item&, unsigned char) at C:\Users\olege\AppData\Local\Programs\Arduino IDE/C:\Devel\Projects\1. ROC\ROC.TFT-ESP-S3\src\RobotClass_Photon_S3/sdgfx.cpp:450
0x4200888c: SDGFX::drawItem(Item&, unsigned char) at C:\Users\olege\AppData\Local\Programs\Arduino IDE/C:\Devel\Projects\1. ROC\ROC.TFT-ESP-S3\src\RobotClass_Photon_S3/sdgfx.cpp:191
0x4200560f: handleGFX() at C:\Users\olege\AppData\Local\Programs\Arduino IDE/C:\Devel\Projects\1. ROC\ROC.TFT-ESP-S3\src\RobotClass_Photon_S3/RobotClass_Photon_S3.ino:440 (discriminator 2)
 (inlined by) handleGFX() at C:\Users\olege\AppData\Local\Programs\Arduino IDE/C:\Devel\Projects\1. ROC\ROC.TFT-ESP-S3\src\RobotClass_Photon_S3/RobotClass_Photon_S3.ino:426 (discriminator 2)
0x42005c47: loop() at C:\Users\olege\AppData\Local\Programs\Arduino IDE/C:\Devel\Projects\1. ROC\ROC.TFT-ESP-S3\src\RobotClass_Photon_S3/RobotClass_Photon_S3.ino:619
0x42016e68: loopTask(void*) at C:\Users\olege\AppData\Local\Programs\Arduino IDE/C:\Users\olege\AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.1.1\cores\esp32/main.cpp:74
0x4037cff6: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:139

Совет

Теперь понятно, куда копать. В данном коде ошибка была в функции drawItemBar. Она была успешно изолирована и устранена!

Полезные ссылки

Инструкция по установке esp-idf на Linux: