ESP8266/32 dostęp do zrzutów stosu przez WWW

Pisząc uaktualnienia do firmware NAM, dodaliśmy jedną cechę, która powinna znacznie usprawnić jego rozwój. Gdy zdarzy się jakiś poważny błąd w oprogramowaniu NAM, procesor ESP8266 zgłasza wyjątek i restartuje cały układ. Szczegółowe informacje o wyjątku, oraz o stanie pamięci stosu są prezentowane w formie komunikatu na porcie szeregowym.

W początkowym etapie rozwoju oprogramowania to w zupełności wystarcza. Jednak gdy większość oczywistych krytycznych błędów zostanie wyłapana, zaczynają się schody.

NAM może pracować w różnych konfiguracjach sprzętowych, wysyłać dane do różnych API. Niektóre błędy się objawiają dopiero gdy zbiegnie się wiele różnych czynników. Trudno cały czas mieć podłączony jakiś komputer rejestrujący wszystko co nadaje na Serialu ESP. Na szczęście SDK ESP8266 (ESP32 ma też taką funkcję) podaje pomocną dłoń.

W momencie wystąpienia wyjątku, przed resetem, zostanie wywołana funkcja custom_crash_callback. Ma ona pełen zestaw informacji o stanie procesora i programu w momencie wystąpienia wyjątku. To co nas najbardziej interesuje to stos. Zawiera on całą historię wywołań funkcji, wraz z miejscem gdzie nastąpił wyjątek.

Od wersji NAMF 2020-29 którą wypuściliśmy dziś, NAM zapisuje te informacje i można je sobie ściągnąć w dowolnym momencie przez WWW.

Potrzebujemy dwóch rzeczy poza zrzutem stosu. Po pierwsze narzędzia dekodującego stos ESP. Po drugie pliku z obrazem firmware. Dokładnie takiego jak wgrany na NAM. Tylko pliku ELF a nie BIN. No i jeszcze narzędzia addr2line które jest dostępne razem z GCC w linux, jest też częścią pakietu instalowanego by używać ESP, ale ścieżkę do niego musisz odnaleźć sam, bo ja używam tego z GCC w moim Ubuntu.

Po kolei. Najpierw dekoder.

Ze strony projektu na Githubie z zakładki ‘Releases’ ściągnij zipa z ostatnią wersją. Tam będzie w środku plik EspStackTraceDecoder.jar, który musisz zapisać u siebie (to że Jave musisz mieć zainstalowaną to chyba oczywiste).

Teraz ELF z firmware.

Jeśli wgrywasz sam własne wersje to znajdziesz ją w katalogu .pio/builds/ŚRODOWISKO. Środowisko będzie zależało od języka interfejsu. Jeśli to wersja EN to będzie nodemcuv2_en.

Dla korzystających z “oficjalnych” binarek potrzebna jest wersja oprogramowania i język interfejsu. Wszystko to znajdziecie w zrzucie stosu jaką wam przedstawi NAM.

Pobierz zrzut stosu

By dostać się do ostatniego błędu (bo tylko o ostanim jest przechowywana informacja) trzeba zajrzeć na stronę http://nazwa-sensora.local/stack_dump. Jeśli nie było żadnego wyjątku to wyświetli komunikat “No stack dump”. Jeśli zaś był, taka na przykład informacja:

Mon May 18 17:11:44 2020
NAMF-2020-29
EN
fad52f13b0c314df2375b59c0b3654a3

Exception (29):
epc1=0x40235ccf epc2=0x0000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000

>>>stack>>>

ctx: sys
sp: 3ffffc10 end: 3fffffc0 offset: 01a0
3ffffdb0:  3ffe8f81 00000007 3fff331c 40205939
3ffffdc0:  00616c62 3fff3344 00000000 40206218
3ffffdd0:  00000001 00000001 3fff331c 4020c814
3ffffde0:  00000001 40205328 3fff331c 401000e1
3ffffdf0:  3fff331c 3ffefed8 3fff331c 4020624e
3ffffe00:  61726300 00356873 80000000 00000003
3ffffe10:  3fff331c 3ffefed8 3ffefe98 4020c5aa
3ffffe20:  6172632f 00356873 87100ba6 3fff3700
3ffffe30:  0000002f 80006873 000058bf 3ffe93be
3ffffe40:  00000001 3ffefed8 4bc6a7f0 00000000
3ffffe50:  00000001 00000000 40100ba6 1eb851eb
3ffffe60:  00000000 3fff3d0c 3ffefe98 3ffe93be
3ffffe70:  00000001 3ffefebc 3ffefe98 4020c8a7
3ffffe80:  3ffe9490 00000000 00001388 4021f875
3ffffe90:  00000000 3fff3d0c 3ffefc8c 4021ddee
3ffffea0:  3ffeff80 3ffefca4 3ffefc8c 4020cba6
3ffffeb0:  402322dc 00000030 0000001a ffffffff
3ffffec0:  3fff3700 000e000f 80227720 3fff3500
3ffffed0:  000b000f 80000000 00000000 fffffffe
3ffffee0:  ffffffff 3fffc6fc 00000001 3ffe8874
3ffffef0:  00000000 3fffdad0 3fff0634 00000030
3fffff00:  3fff0ee4 00000001 4021f800 3fff0420
3fffff10:  800c3500 28188e00 3fff3800 8021dd65
3fffff20:  00000000 4bc6a7f0 80001d65 00000000
3fffff30:  00000000 80000000 4bc6a700 00000000
3fffff40:  8025f571 00186a00 40100ba6 80000000
3fffff50:  00000000 00000032 80000000 40228500
3fffff60:  000c3500 808acbda 3fff0400 4020ed52
3fffff70:  80000000 0012001f 80004950 402260d8
3fffff80:  3ffefc90 00000000 00000001 401007ac
3fffff90:  3fffdad0 00000000 3fff05f4 3fff0634
3fffffa0:  3fffdad0 00000000 3fff05f4 40227740
3fffffb0:  feefeffe feefeffe 3ffe8874 40100711
<<<stack<<<

Najpierw data i godzina (GMT, oczywiście jeśli sensor ma połączenie z internetem, bo bierze czas z NTP). W drugiej linii wersja firmware, dalej wersja językowa i sygnatura MD5 pliku bin. Potem “leci” cały zrzut stosu – staraliśmy się by był dokładnie taki jak na Serialu, jeszcze są jakieś drobne różnice, ale pracujemy nad tym.

Począwszy od wersji -29 na githubie dostępne są wszystkie pliki .bin które poszły na serwer uaktualnień. Również pliki ELF, z których wygenerowane zostały pliki BIN. Jeśli mieliście wersje EN 2020-29 to ściągnąć trzeba najpierw 2020-29.bin. Bin – po to by móc zweryfikować że to co zgłosiło błąd to dokładnie ten sam obraz. Jak? Za pomocą MD5 – jeśli skrót MD5 pliku .bin jest taki jak na waszym zrzucie to znaczy, że na pewno to samo analizujecie. Jeśli sygnatura się zgadza – ściągnijcie plik elf i go zapiszcie.

Analiza!

Dobra, mamy plik ELF, mamy EspStackTraceDecoder.jar, więc pozostaje zapisać zrzut stosu do pliku tekstowego (od linii ze słowem Exception do <<<stack<<<) i wywołać komendę:

java -jar ~/OLD_HOME/tmp/EspStackTraceDecoder.jar which /usr/bin/addr2line .pio/build/nodemcuv2_en/firmware.bin ~/tmp/stack.txt

Rezultat:

xception Cause: 29 [StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores]

0x40235ccf: __fast_strcpy at /home/earle/src/esp-quick-toolchain/repo/newlib/newlib/libc/machine/xtensa/fast_strcpy.S:108
0x40205939: crashme2() at ??:?
0x40206218: esp8266webserver::FunctionRequestHandler::handle(esp8266webserver::ESP8266WebServerTemplate&, HTTPMethod, String) at ??:?
0x4020c814: std::_Function_handler::_M_invoke(std::_Any_data const&) at main.cpp:?
0x40205328: esp8266webserver::FunctionRequestHandler::canHandle(HTTPMethod, String) at ??:?
0x401000e1: std::function::operator()() const at ??:?
0x4020624e: esp8266webserver::FunctionRequestHandler::handle(esp8266webserver::ESP8266WebServerTemplate&, HTTPMethod, String) at ??:?
0x4020c5aa: esp8266webserver::ESP8266WebServerTemplate::_handleRequest() at ??:?
0x40100ba6: millis at ??:?
0x4020c8a7: esp8266webserver::ESP8266WebServerTemplate::handleClient() at ??:?
0x4021f875: esp8266::MDNSImplementation::MDNSResponder::_process(bool) at ??:?
0x4021ddee: esp8266::MDNSImplementation::MDNSResponder::update() at ??:?
0x4020cba6: loop at ??:?
0x402322dc: preloop_update_frequency() at ??:?
0x4021f800: esp8266::MDNSImplementation::MDNSResponder::_process(bool) at ??:?
0x40100ba6: millis at ??:?
0x40228500: delay_end at ??:?
0x4020ed52: setup at ??:?
0x402260d8: String::~String() at ??:?
0x401007ac: ets_post at ??:?
0x40227740: loop_wrapper() at core_esp8266_main.cpp:?
0x40100711: cont_wrapper at ??:?

Zgadza się – to był celowy błąd wywołany przez funkcję crashme. Wszystko teraz jasne!

Powyższy tekst dotyczy debugowania firmware naszego sensora Nettigo Air Monitor, jeśli jesteś tutaj by zaimplementować podobną funkcję w swoim projekcie przyjrzyj się:

  • stack_dump.cpp gdzie jest zapisywany zrzut na SPIFFS (bo nasz projekt i tak używa FS więc lepsze to niż zajmowanie EEPROM)
  • webserver.cpp gdzie znajdziesz funkcję wyświetlającą plik ze zrzutem

Ścieżki/nazwy plików mogą się zmienić, bo intensywnie nad NAMF pracujemy.

Aha! Z seriala też działa!

Oczywiście, dekoder zrzutów stosu działa dla zrzutów które ESP wypisuje na Serialu! Dlatego staraliśmy się by wersja WWW miała format jak ten na Serialu. Jeśli masz projekt nękany przez wyjątki i nie wiesz gdzie i dlaczego, to podłącz się do Seriala, zgraj zrzut stosu i użyj dekodera z własnym firmware by zobaczyć co się stało.