Добрый день, уважаемый читатель! При локальной отладке в случае сбоя паники в COM-порт обычно печатается backtrace, который можно “расшифровать” и выяснить место точку возникновения проблемы. Зная файл исходного кода и номер строки, можно с большой степенью вероятности предположить причину проблемы и устранить её. В прошлой статье я рассказал, как можно расшифровать backtrace для поиска причины сбоя на устройстве.
Но иногда уже после сборки и отладки очередного устройства, а затем и установки его “на место”, вдруг начинают проявляться самопроизвольные перезагрузки из-за паники. Возникает довольно непростая задача: определить место возникновения сбоя без возможности просмотреть логи в терминале.
Но и при доступе к устройству так же могут быть некоторые сложности – ошибка может возникать раз в неделю или реже, в этом случае “мониторить” устройство через COM-порт становится проблематично. В PlatformIO имеется возможность сохранять отладочный вывод в файл, можно использовать этот способ, но это не всегда удобно.
Гораздо хуже, если физического доступа к устройству вообще нет – например, если оно физически расположено в другом населенном пункте или в труднодоступном месте. В таком случае отладка возможна, только если устройство самостоятельно отправит какие-либо данные для отладки в telegram, на mqtt-брокер или куда-либо ещё.
Проблема в том, что на текущий момент ни framework-espidf, ни framework-arduinoespressif32 не предусматривают штатной возможности отправки backtrace куда-либо, кроме COM-порта. Так что придется “колхозить” перехват и сохранение backtrace самостоятельно.
Массив для сохранения backtrace
Под сохранение backtrace достаточно выделить сравнительно небольшой массив uint32_t чисел. Размер массива зависит от того, сколько адресов нужно там хранить. Обычно достаточно 10-16 ячеек, но в некоторых особо тяжелых случаях нужно и больше (например если перезагрузка возникает при ошибке выделения памяти, когда куча исчерпана – в этом случае в начале backtrace будут вызовы системных функций выделения памяти). Я определил этот массив так:
С помощью макроса CONFIG_RESTART_DEBUG_INFO можно включать или отключать сохранение отладочной информации, с помощью макроса CONFIG_RESTART_DEBUG_STACK_DEPTH можно устанавливать размер буфера под сохранение backtrace. Кроме того, в данной структуре сохраняются данные о размере свободной кучи.
Возникает вопрос в том, где хранить эти данные при перезагрузке.
Первая мысль – сохранить во flash памяти, например в NVS-разделе. Но, увы, это не всегда прокатывает, так как не удается выполнить запись (собственно из-за сбоя).
В итоге я поступил по другому – пометил данные для отладке модификатором __NOINIT_ATTR. Это заставит компилятор поместить данный блок данных в область, которая не очищается при перезагрузке. Конечно, при выключении и включении устройства данные будут утеряны в любом случае, но в этом случае они и не требуются.
Заполнение данных для backtrace
Как я уже упомянул выше, в ESP-IDF не предусмотрен вывод backtrace куда-либо ещё, кроме com-порта. Что ж, придется сделать это самостоятельно. Для этого я воспользовался функцией esp_backtrace_print из файла \.platformio\packages\framework-espidf\components\xtensa\debug_helpers.c, и написал похожий вариант:
А затем:
При сохранении данных stk_frame.sp не сохраняется, так как это особо не влияет на расшифровку адресов. Осталось обеспечить вызов этой функции перед перезагрузкой и в случае паники.
Сохранение backtrace перед перезагрузкой устройства
Реализовать сохранение данных при “штатной” перезагрузке устройства командой esp_restart очень просто. Для этого достаточно зарегистрировать обработчик “штатной” перезагрузки. Всего можно использовать от 1 до 5 обработчиков, и это число, увы, изменить нельзя.
Это обеспечит сохранение отладочной информации в зарезервированной области памяти при перезагрузке из-за обновления OTA или в других “нормальных” ситуациях.
Но, увы, при “панике” из-за ошибки этот метод не сработает. Решить эту задачу для ESP-IDF можно, создав wrapper для системной функции esp_panic_handler с помощью опции -Wl. Для этого создаем обертку для esp_panic_handler:
Затем в файл CMakeLists.txt необходимо добавить строку idf_build_set_property(LINK_OPTIONS “-Wl,–wrap=esp_panic_handler” APPEND):
Это заставит компилятор вызвать нашу функцию esp_panic_handler вместо “штатной”, а затем уже наша функция вызовет встроенную. Вуаля!
Отправка backtrace после перезагрузки устройства
Осталось только отправить эти данные после перезагрузки куда-нибудь на сервер. Например это можно сделать, опубликовав эти данные на mqtt-брокер. А можно отправить в telegram, добавив backtrace к информации о запуске устройства:
Затем:
Теперь при “программной перезагрузке” или сбое получим примерно такое сообщение:
Можно скопировать эти данные в attr2line и расшифровать место возникновения ошибки. Задача решена.
Где всё это скачать?
Как всегда - на GitHub. Отдельного модуля / библиотеки для отладки не предусмотрено, функции интегрированы в библиотечку reEsp32:
Чтобы было понятней, немного поясню код:
Предупреждение: при модификации библиотеки в будущем строки могут "съехать"
Понимаю, тема не очень простая - задавайте вопросы в комментариях.
_______________
На этом пока всё, до встречи на сайте и на dzen-канале!
👍 Понравилась статья? Поддержите канал лайком или комментарием! Каналы на Дзене "живут" только за счет ваших лайков.
📌Подпишитесь на канал и вы всегда будете в курсе новых статей.
🔶 Полный архив статей вы найдете здесь
Благодарю за вашу поддержку! 🙏