В статье рассмотрен сравнительно простой способ дешифровки адресов 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: вывести номер версии
В утилиту можно передавать не один адрес, а сразу несколько, разделенных пробелами. То есть можно передать сразу несколько адресов, и утилита расшифрует каждый из адресов отдельно.
Практическое применение
Для начала необходимо запустить командную строку 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 файл и пользоваться более короткой записью.
На этом всё. Благодарю за внимание.
Если вам понравилась статья и вы желаете отблагодарить автора – просто кликните по любому рекламному объявлению.
Пожалуйста, оцените статью: