Перейти к содержимому

Расшифровка адресов ESP32 backtrace в PlatformIO

В статье рассмотрен сравнительно простой способ дешифровки адресов backtrace в среде VSCode + PlatformIO. Под дешифровкой backtrace в контексте данной статьи понимается преобразование адресов в имена файлов и номера строк исходного кода. В настоящее время я занимаюсь разработкой для ESP32 (и иногда ESP8266), поэтому в статье рассмотрен пример именно для этой конфигурации. Но, насколько я понимаю, этот способ вполне применим и для других микроконтроллеров, программируемых в PlatformIO.

При разработке устройств на микроконтроллерах иногда приходится сталкиваться с различными сбоями, вызванными допущенными ошибками при программировании. При возникновении критических ошибок микроконтроллер печатает дамп регистров и backtrace, а затем перезагружается. В разных ситуациях текст сообщения может быть различным, например, это может выглядеть так:

Core 1 register dump:
PC      : 0x400e14ed  PS      : 0x00060030  A0      : 0x800d0805  A1      : 0x3ffb5030
A2      : 0x00000000  A3      : 0x00000001  A4      : 0x00000001  A5      : 0x3ffb50dc
A6      : 0x00000000  A7      : 0x00000001  A8      : 0x00000000  A9      : 0x3ffb5000
A10     : 0x00000000  A11     : 0x3ffb2bac  A12     : 0x40082d1c  A13     : 0x06ff1ff8
A14     : 0x3ffb7078  A15     : 0x00000000  SAR     : 0x00000014  EXCCAUSE: 0x0000001d
EXCVADDR: 0x00000000  LBEG    : 0x4000c46c  LEND    : 0x4000c477  LCOUNT  : 0xffffffff

Backtrace: 0x400e14ed:0x3ffb5030 0x400d0802:0x3ffb5050

Для меня содержимое регистров пока не представляет особой ценности, а вот расшифровка backtrace (то есть преобразование адресов вызовов в имена файлов и номера строк) позволяет точно установить место возникновения исключительной ситуации в коде программы. Проанализировав место возникновения ошибки в большинстве случаев сразу же удается понять причину её появления.

Предисловие

Предупреждение: во всех случаях для декодирования backtrace Вам потребуется ELF-файл, в котором и содержится вся необходимая информация для сопоставления адресов номерам строк исходного кода. Для PlatformIO и esp32dev этот файл можно найти в каталоге каталог проекта\.pio\build\esp32dev\firmware.elf.

Для начала стоить упомянуть об штатных (уже имеющихся, готовых) средствах для декодирования исключений.

В Arduino IDE для анализа исключений существует достаточно удобный плагин EspExceptionDecoder. Он позволяет легко и просто декодировать backtrace, но в PlatformIO не работает.

В PlatformIO имеется возможность включить дешифрацию адресов в номера строк в встроенном терминале “на лету”, прямо при выводе сообщений в терминал. Для этого в файле параметров проекта platformio.ini необходимо добавить следующие строки:

[env]
monitor_filters = esp32_exception_decoder

Удобно? Казалось бы очень… На деле это приводит к очень сильному замедлению вывода сообщений в терминал, ведь попытка декодирования производится для каждой новой строки в терминале. Пришлось отказаться от этой сомнительной возможности.

На самом деле и EspExceptionDecoder и esp32_exception_decoder используют для декодирования адресов внешнюю программу: addr2line. Эта программа поставляется вместе с компилятором и может различаться для разных платформ и микроконтроллеров. Это же указано в справке для IDF Monitor:

To decode each address, IDF Monitor runs the following command in the background:
xtensa-esp32-elf-addr2line -pfiaC -e build/PROJECT.elf ADDRESS

Для ESP32 и ESP-IDF (toolchain-xtensa-esp32) ее можно найти здесь:

${env:USERPROFILE}\.platformio\packages\toolchain-xtensa-esp32\bin\xtensa-esp32-elf-addr2line.exe

Для других toolchain расположение и префикс утилиты могут различаться, но легко находятся поиском.

Параметры командной стройки для утилиты addr2line

addr2line [-a| --addresses ] [-b bfdname | --target=bfdname] [-C | --demangle[=style]] [-e filename | --exe=filename] [-f | --function] [-s | --basename] [-i | --inlines] [-p | --pretty-print] [-j | --section=name] [-H | --help] [-V | --version] [addr addr ...]

-a --addresses: отображать адреса перед именем функции, информацией о файле и номере строки в шестнадцатеричной форме
-b --target = : укажите формат целевого файла как bfdname
-e --exe = : укажите имя исполняемого файла, адрес которого необходимо преобразовать
-i --inlines: если адрес, который нужно преобразовать, является встроенной функцией, выходная информация включает информацию о не встроенной функции в ее ближайшем диапазоне
-j --section = : данный адрес представляет собой смещение указанного раздела, а не абсолютный адрес
-p --pretty-print: сделать вывод информации этой функции более человечным: информация о каждом адресе занимает одну строку
-s --basenames: отображать только базовый адрес каждого имени файла (то есть конкретный путь к файлу не отображается, отображается только имя файла)
-f --functions: отображение информации об имени функции при отображении имени файла и выходной информации о номере строки
-C --demangle [= style]: декодировать имена символов низкого уровня в имена уровня пользователя
-h --help: вывод справочной информации
-v --version: вывести номер версии

Источники: ENG, RUS

В утилиту можно передавать не один адрес, а сразу несколько, разделенных пробелами. То есть можно передать сразу несколько адресов, и утилита расшифрует каждый из адресов отдельно.

Практическое применение

Для начала необходимо запустить командную строку Windows. Быстрее всего это делать, нажав клавиши “Win” + “R” на клавиатуре, а затем набрать команду “cmd“. Или через меню “Пуск” – “Служебные” – “Командная строка”. Затем в открытом окне вводим примерно следующую команду (правильные пути к файлам подставьте сами):

c:\Users\kotyara12\.platformio\packages\toolchain-xtensa-esp32\bin\xtensa-esp32-elf-addr2line.exe -pfiaC 
 -e c:\PlatformIO\village_security\.pio\build\esp32dev\firmware.elf 
 backtrace

где:

  • c:\Users\kotyara12 – путь к текущему профилю пользователя, подставьте свои данные
  • toolchain-xtensa-esp32 – toolchain, который Вы используете, подставьте свои данные
  • xtensa-esp32-elf- – префикс, который зависит от toolchain, подставьте свои данные
  • c:\PlatformIO\village_security – путь к проекту, подставьте свои данные
  • esp32dev – MCU или плата, под которую собирается проект, подставьте свои данные
  • backtraceвместо backtrace подставляем собственно перечисление адресов, без слова “Backtrace:”

… и нажимаем Enter. В результате, если все сделано правильно, получим примерно такие результаты:

c:\Users\kotyara12\.platformio\packages\toolchain-xtensa-esp32\bin\xtensa-esp32-elf-addr2line.exe -pfiaC -e c:\PlatformIO\village_security\.pio\build\esp32dev\firmware.elf 0x4008150a 0x400d5e4f 0x40082121 0x400d5cd3 0x400e7da6

0x4008150a: debugUpdate at C:/PlatformIO/libs/system/reEsp32/src/reEsp32.cpp:305
0x400d5e4f: espDefaultShutdownHandler() at C:/PlatformIO/libs/system/reEsp32/src/reEsp32.cpp:145
0x40082121: esp_restart at \.platformio\packages\framework-espidf\components\esp_system/system_api.c:71
0x400d5cd3: espRestartTimer(void*) at C:/PlatformIO/libs/system/reEsp32/src/reEsp32.cpp:169
0x400e7da6: timer_process_alarm at \.platformio\packages\framework-espidf\components\esp_timer\src/esp_timer.c:302
 (inlined by) timer_task at \.platformio\packages\framework-espidf\components\esp_timer\src/esp_timer.c:341

На примере показан backtrace при получении “внешней” команды restart через mqtt. Команда оповещает системный цикл событий о перезапуске и запускает таймер на некоторое время, чтобы остальные службы смогли завершить свою работу. Что здесь видно: в последней строке таймер отработал положенное время и запустил callback-функцию espRestartTimer(void*), которая, в свою очередь, вызвала команду esp_restart. Во время завершения работы был вызван espDefaultShutdownHandler(), который, в свою очередь, вызвал debugUpdate(), где, собственно и произошло сохранение данного backtrace в области памяти NO_INIT, чтобы быть отправленным пользователю после перезагрузки.

Можно сохранить команду в cmd / bat файл и пользоваться более короткой записью.

На этом всё. Благодарю за внимание.

Добавить комментарий

Ваш адрес email не будет опубликован.