From 877d44059ef05ccc6bfa794ea1e47802b0bedf67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Holger=20M=C3=BCller?= Date: Sun, 24 Mar 2024 23:52:28 +0100 Subject: [PATCH 1/7] Add url redirect (#8970) * added getAvailableVersion(), moved _httpClientTimeout and _followRedirects to protected, added enum HTTPUpdateError * auto numbering of HTTPUpdateError enum * added getAvailableVersion(), debug output current current Sketch MD5 * Revert "added getAvailableVersion(), debug output current current Sketch MD5" This reverts commit 60d2c7762e7fb1fed7fae37fa99be149e12f125c. * Revert "auto numbering of HTTPUpdateError enum" This reverts commit 61785b27da3f2d42f8f95316d78ce22e5b00103a. * Revert "added getAvailableVersion(), moved _httpClientTimeout and _followRedirects to protected, added enum HTTPUpdateError" This reverts commit cec84ed17ab149d3e48082293f9e2723246b7d0b. * add redirect function * enhanced redirect() by cache control and client stop * updated redirect() comment * replaced redirect() API calls in examples * server.client().stop() not needed, redirect() does this --- .../CaptivePortalAdvanced/handleHttp.ino | 11 ++------- .../NAPTCaptivePortal/PortalRedirectHttp.ino | 4 +--- .../examples/WebServer/WebServer.ino | 3 +-- .../ESP8266WebServer/src/ESP8266WebServer.h | 24 +++++++++++++++++++ 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino b/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino index aec2556a48..c7380017d1 100644 --- a/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino +++ b/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino @@ -27,9 +27,7 @@ void handleRoot() { boolean captivePortal() { if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname) + ".local")) { Serial.println("Request redirected to captive portal"); - server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true); - server.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. - server.client().stop(); // Stop is needed because we sent no content length + server.redirect(String("http://") + toStringIp(server.client().localIP())); return true; } return false; @@ -91,12 +89,7 @@ void handleWifiSave() { Serial.println("wifi save"); server.arg("n").toCharArray(ssid, sizeof(ssid) - 1); server.arg("p").toCharArray(password, sizeof(password) - 1); - server.sendHeader("Location", "wifi", true); - server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - server.sendHeader("Pragma", "no-cache"); - server.sendHeader("Expires", "-1"); - server.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. - server.client().stop(); // Stop is needed because we sent no content length + server.redirect("wifi"); saveCredentials(); connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID } diff --git a/libraries/DNSServer/examples/NAPTCaptivePortal/PortalRedirectHttp.ino b/libraries/DNSServer/examples/NAPTCaptivePortal/PortalRedirectHttp.ino index 286490772d..5de12229a5 100644 --- a/libraries/DNSServer/examples/NAPTCaptivePortal/PortalRedirectHttp.ino +++ b/libraries/DNSServer/examples/NAPTCaptivePortal/PortalRedirectHttp.ino @@ -36,13 +36,11 @@ void sendPortalRedirect(String path, String targetName) { If the "Location" header element works the HTML stuff is never seen. */ // https://tools.ietf.org/html/rfc7231#section-6.4.3 - server.sendHeader("Location", path, true); - addNoCacheHeader(); String reply = FPSTR(portalRedirectHTML); reply.reserve(reply.length() + 2 * path.length() + 80); reply.replace("{t}", targetName); reply.replace("{1}", path); - server.send(302, "text/html", reply); + server.redirect(path, reply); } #endif // LWIP_FEATURES && !LWIP_IPV6 diff --git a/libraries/ESP8266WebServer/examples/WebServer/WebServer.ino b/libraries/ESP8266WebServer/examples/WebServer/WebServer.ino index 81a58478f9..ffadddcca4 100644 --- a/libraries/ESP8266WebServer/examples/WebServer/WebServer.ino +++ b/libraries/ESP8266WebServer/examples/WebServer/WebServer.ino @@ -43,8 +43,7 @@ void handleRedirect() { if (!LittleFS.exists(url)) { url = "/$update.htm"; } - server.sendHeader("Location", url, true); - server.send(302); + server.redirect(url); } // handleRedirect() diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 397132f161..9f2b8b19c3 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -207,6 +207,30 @@ class ESP8266WebServerTemplate sendContent(emptyString); } + /** + * @brief Redirect to another URL, e.g. + * webserver.on("/index.html", HTTP_GET, []() { webserver.redirect("/"); }); + * There are 3 points of redirection here: + * 1) "Location" element in the header + * 2) Disable client caching + * 3) HTML "content" element to redirect + * If the "Location" header element works the HTML content is never seen. + * https://tools.ietf.org/html/rfc7231#section-6.4.3 + * @param url URL to redirect to + * @param content Optional redirect content + */ + void redirect(const String& url, const String& content = emptyString) { + sendHeader(F("Location"), url, true); + sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); + sendHeader(F("Pragma"), F("no-cache")); + sendHeader(F("Expires"), F("-1")); + send(302, F("text/html"), content); // send 302: "Found" + if (content.isEmpty()) { + // Empty content inhibits Content-length header so we have to close the socket ourselves. + client().stop(); // Stop is needed because we sent no content length + } + } + // Whether other requests should be accepted from the client on the // same socket after a response is sent. // This will automatically configure the "Connection" header of the response. From 8731f63594c3e77a645c2fd49440e97d7f1454df Mon Sep 17 00:00:00 2001 From: David Refoua Date: Mon, 25 Mar 2024 02:45:29 +0330 Subject: [PATCH 2/7] minor typo fixes (#9106) --- cores/esp8266/WString.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/WString.h b/cores/esp8266/WString.h index 47a9177534..1a2aebb298 100644 --- a/cores/esp8266/WString.h +++ b/cores/esp8266/WString.h @@ -33,7 +33,7 @@ #include #include -// an abstract class used as a means to proide a unique pointer type +// an abstract class used as a means to provide a unique pointer type // but really has no body class __FlashStringHelper; #define FPSTR(pstr_pointer) (reinterpret_cast(pstr_pointer)) @@ -204,7 +204,7 @@ class String { bool concat(double num); // if there's not enough memory for the concatenated value, the string - // will be left unchanged (but this isn't signalled in any way) + // will be left unchanged (but this isn't signaled in any way) template String &operator +=(const T &rhs) { concat(rhs); @@ -343,7 +343,7 @@ class String { char *wbuffer() { return const_cast(buffer()); } // Writable version of buffer // concatenation is done via non-member functions - // make sure we still have access to internal methods, since we optimize based on capacity of both sides and want to manipulate internal buffers directly + // make sure we still have access to internal methods, since we optimize based on the capacity of both sides and want to manipulate internal buffers directly friend String operator +(const String &lhs, String &&rhs); friend String operator +(String &&lhs, String &&rhs); friend String operator +(char lhs, String &&rhs); From 41ecd65c6a4a711d6d799629699a41400992c376 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Mon, 25 Mar 2024 00:35:19 +0100 Subject: [PATCH 3/7] Stream: +helpers to stream regular String (#9043) This allows to use a String as a destination Stream by using a temporary with streaming helpers. --- cores/esp8266/Stream.h | 4 ++++ cores/esp8266/StreamString.h | 16 +++++++-------- .../examples/StreamString/StreamString.ino | 20 +++++++++++++++---- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/cores/esp8266/Stream.h b/cores/esp8266/Stream.h index 21f319ee6b..0706bec001 100644 --- a/cores/esp8266/Stream.h +++ b/cores/esp8266/Stream.h @@ -196,21 +196,25 @@ class Stream: public Print { // returns number of transferred bytes size_t sendAvailable (Stream* to) { return sendGeneric(to, -1, -1, oneShotMs::alwaysExpired); } size_t sendAvailable (Stream& to) { return sendAvailable(&to); } + size_t sendAvailable (Stream&& to) { return sendAvailable(&to); } // transfers data until timeout // returns number of transferred bytes size_t sendAll (Stream* to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, -1, timeoutMs); } size_t sendAll (Stream& to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendAll(&to, timeoutMs); } + size_t sendAll (Stream&& to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendAll(&to, timeoutMs); } // transfers data until a char is encountered (the char is swallowed but not transferred) with timeout // returns number of transferred bytes size_t sendUntil (Stream* to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, readUntilChar, timeoutMs); } size_t sendUntil (Stream& to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendUntil(&to, readUntilChar, timeoutMs); } + size_t sendUntil (Stream&& to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendUntil(&to, readUntilChar, timeoutMs); } // transfers data until requested size or timeout // returns number of transferred bytes size_t sendSize (Stream* to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, maxLen, -1, timeoutMs); } size_t sendSize (Stream& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); } + size_t sendSize (Stream&& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); } // remaining size (-1 by default = unknown) virtual ssize_t streamRemaining () { return -1; } diff --git a/cores/esp8266/StreamString.h b/cores/esp8266/StreamString.h index 2331a3c51a..dced5aee80 100644 --- a/cores/esp8266/StreamString.h +++ b/cores/esp8266/StreamString.h @@ -29,7 +29,7 @@ #include "WString.h" /////////////////////////////////////////////////////////////// -// S2Stream points to a String and makes it a Stream +// S2Stream ("String to Stream") points to a String and makes it a Stream // (it is also the helper for StreamString) class S2Stream: public Stream @@ -184,19 +184,18 @@ class S2Stream: public Stream return peekPointer < 0 ? string->length() : string->length() - peekPointer; } - // calling setConsume() will consume bytes as the stream is read - // (enabled by default) + // calling setConsume() will make the string consumed as the stream is read. + // (default behaviour) void setConsume() { peekPointer = -1; } - // Reading this stream will mark the string as read without consuming - // (not enabled by default) - // Calling resetPointer() resets the read state and allows rereading. - void resetPointer(int pointer = 0) + // Calling resetPointer() resets the read cursor and allows rereading. + // (this is the opposite of default mode set by setConsume()) + void resetPointer(size_t pointer = 0) { - peekPointer = pointer; + peekPointer = std::min(pointer, (size_t)string->length()); } protected: @@ -204,6 +203,7 @@ class S2Stream: public Stream int peekPointer; // -1:String is consumed / >=0:resettable pointer }; +/////////////////////////////////////////////////////////////// // StreamString is a S2Stream holding the String class StreamString: public String, public S2Stream diff --git a/libraries/esp8266/examples/StreamString/StreamString.ino b/libraries/esp8266/examples/StreamString/StreamString.ino index f177b53edc..73512cf2af 100644 --- a/libraries/esp8266/examples/StreamString/StreamString.ino +++ b/libraries/esp8266/examples/StreamString/StreamString.ino @@ -54,7 +54,7 @@ void testStreamString() { { // We use a a lighter StreamConstPtr(input) to make a read-only Stream out of // a String that obviously should not be modified during the time the - // StreamConstPtr instance is used. It is used as a source to be sent to + // StreamConstPtr instance is used. It is used as a read-only source to be sent to // 'result'. result.clear(); @@ -77,7 +77,7 @@ void testStreamString() { // Now inputString is made into a Stream using S2Stream, // and set in non-consume mode (using ::resetPointer()). - // Then, after that input is read once, it won't be anymore readable + // Then, after input is read once, it won't be anymore readable // until the pointer is reset. S2Stream input(inputString); @@ -87,7 +87,7 @@ void testStreamString() { input.sendAll(result); input.sendAll(result); check("S2Stream.sendAll(StreamString)", result.c_str(), "hello"); - check("unmodified String given to S2Stream", inputString.c_str(), "hello"); + check("String given to S2Stream is unmodified", inputString.c_str(), "hello"); } { @@ -103,6 +103,17 @@ void testStreamString() { check("S2Stream.resetPointer(2):", result.c_str(), "llo"); } + { + // Streaming to a regular String + + String someSource{ F("hello") }; + String someDestString; + + StreamConstPtr(someSource).sendAll(S2Stream(someDestString)); + StreamConstPtr(someSource).sendAll(S2Stream(someDestString)); + check("StreamConstPtr(someSource).sendAll(S2Stream(someDestString))", someDestString.c_str(), "hellohello"); + } + { // inputString made into a Stream // reading the Stream consumes the String @@ -181,7 +192,8 @@ void setup() { testStreamString(); - Serial.printf("sizeof: String:%d Stream:%d StreamString:%d SStream:%d\n", (int)sizeof(String), (int)sizeof(Stream), (int)sizeof(StreamString), (int)sizeof(S2Stream)); + Serial.printf("sizeof: String:%zu Stream:%zu StreamString:%zu S2Stream:%zu StreamConstPtr:%zu\n", + sizeof(String), sizeof(Stream), sizeof(StreamString), sizeof(S2Stream), sizeof(StreamConstPtr)); } #endif From eda4e0855fa5ebcf1d7f621f35f25f6bab503335 Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Tue, 26 Mar 2024 10:58:20 +0300 Subject: [PATCH 4/7] CONT stack overflow postmortem (#9083) - check in cont_run() and cont_suspend() whether a1 is out of bounds - in case a1 is broken, postmortem will still report proper context in proper stack boundaries additionally - as suggested in #9069, change stack smashing to a single line that does not mention any Exceptions - reduce overall stack dump length when there are know garbage values i.e. cont stackguard - decoder.py addr search regexp would no longer skip stack lines with '<' - fix decoder.py parsing so it notices both stack smashing and alloc errors --- cores/esp8266/cont.S | 20 +++++-- cores/esp8266/cont.h | 11 +++- cores/esp8266/cont_util.cpp | 27 ++++++---- cores/esp8266/core_esp8266_main.cpp | 2 +- cores/esp8266/core_esp8266_postmortem.cpp | 64 +++++++++++++++++------ cores/esp8266/debug.h | 3 ++ tools/decoder.py | 33 ++++++------ 7 files changed, 112 insertions(+), 48 deletions(-) diff --git a/cores/esp8266/cont.S b/cores/esp8266/cont.S index 4832b39170..ee049d46f5 100644 --- a/cores/esp8266/cont.S +++ b/cores/esp8266/cont.S @@ -26,8 +26,14 @@ cont_suspend: /* a1: sp */ /* a2: void* cont_ctx */ - /* adjust stack and save registers */ + /* adjust stack */ addi a1, a1, -24 + + /* make sure that a1 points after cont_ctx.stack[] */ + addi a4, a2, 32 + bltu a1, a4, cont_overflow + + /* save registers */ s32i a12, a1, 0 s32i a13, a1, 4 s32i a14, a1, 8 @@ -47,6 +53,11 @@ cont_suspend: l32i a1, a2, 4 jx a0 +cont_overflow: + mov.n a3, a1 + movi a4, __stack_overflow + jx a4 + cont_continue: l32i a12, a1, 0 l32i a13, a1, 4 @@ -113,7 +124,7 @@ cont_run: bnez a4, cont_resume /* else */ /* set new stack*/ - l32i a1, a2, 16; + l32i a1, a2, 16 /* goto pfn */ movi a2, cont_wrapper jx a2 @@ -121,12 +132,15 @@ cont_run: cont_resume: /* a1 <- cont_ctx.sp_suspend */ l32i a1, a2, 12 + /* make sure that a1 points after cont_ctx.stack[] */ + addi a5, a2, 32 + bltu a1, a5, cont_overflow /* reset yield flag, 0 -> cont_ctx.pc_suspend */ movi a3, 0 s32i a3, a2, 8 /* jump to saved cont_ctx.pc_suspend */ movi a0, cont_ret - jx a4 + jx a4 cont_norm: /* calculate pointer to cont_ctx.struct_start from sp */ diff --git a/cores/esp8266/cont.h b/cores/esp8266/cont.h index ba6d432a88..4c9578851b 100644 --- a/cores/esp8266/cont.h +++ b/cores/esp8266/cont.h @@ -22,11 +22,16 @@ #define CONT_H_ #include +#include #ifndef CONT_STACKSIZE #define CONT_STACKSIZE 4096 #endif +#ifndef CONT_STACKGUARD +#define CONT_STACKGUARD 0xfeefeffe +#endif + #ifdef __cplusplus extern "C" { #endif @@ -62,8 +67,11 @@ void cont_run(cont_t*, void (*pfn)(void)); // execution state (registers and stack) void cont_suspend(cont_t*); +// Check that cont resume state is valid. Immediately panics on failure. +void cont_check_overflow(cont_t*); + // Check guard bytes around the stack. Immediately panics on failure. -void cont_check(cont_t*); +void cont_check_guard(cont_t*); // Go through stack and check how many bytes are most probably still unchanged // and thus weren't used by the user code. i.e. that stack space is free. (high water mark) @@ -78,7 +86,6 @@ bool cont_can_suspend(cont_t* cont); // free, running the routine, then checking the max free void cont_repaint_stack(cont_t *cont); - #ifdef __cplusplus } #endif diff --git a/cores/esp8266/cont_util.cpp b/cores/esp8266/cont_util.cpp index 746dcccbfd..6c49a38fcf 100644 --- a/cores/esp8266/cont_util.cpp +++ b/cores/esp8266/cont_util.cpp @@ -23,38 +23,45 @@ #include #include -#include "cont.h" +#include "core_esp8266_features.h" #include "debug.h" +#include "cont.h" + extern "C" { -static constexpr unsigned int CONT_STACKGUARD { 0xfeefeffe }; +static constexpr uint32_t CONT_STACKSIZE_U32 { sizeof(cont_t::stack) / sizeof(*cont_t::stack) }; void cont_init(cont_t* cont) { memset(cont, 0, sizeof(cont_t)); cont->stack_guard1 = CONT_STACKGUARD; cont->stack_guard2 = CONT_STACKGUARD; - cont->stack_end = cont->stack + (sizeof(cont->stack) / 4); + cont->stack_end = &cont->stack[0] + CONT_STACKSIZE_U32; cont->struct_start = (unsigned*) cont; // fill stack with magic values to check high water mark - for(int pos = 0; pos < (int)(sizeof(cont->stack) / 4); pos++) + for(int pos = 0; pos < (int)(CONT_STACKSIZE_U32); pos++) { cont->stack[pos] = CONT_STACKGUARD; } } -void IRAM_ATTR cont_check(cont_t* cont) { - if ((cont->stack_guard1 == CONT_STACKGUARD) - && (cont->stack_guard2 == CONT_STACKGUARD)) +void IRAM_ATTR cont_check_guard(cont_t* cont) { + if ((cont->stack_guard1 != CONT_STACKGUARD) + || (cont->stack_guard2 != CONT_STACKGUARD)) { - return; + __stack_chk_fail(); + __builtin_unreachable(); } +} - __stack_chk_fail(); - __builtin_unreachable(); +void IRAM_ATTR cont_check_overflow(cont_t* cont) { + if (cont->sp_suspend && (cont->sp_suspend < &cont->stack[0])) { + __stack_overflow(cont, cont->sp_suspend); + __builtin_unreachable(); + } } // No need for this to be in IRAM, not expected to be IRQ called diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index d0309cc71f..91a64e0ab8 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -261,7 +261,7 @@ static void loop_wrapper() { } loop(); loop_end(); - cont_check(g_pcont); + cont_check_guard(g_pcont); if (serialEventRun) { serialEventRun(); } diff --git a/cores/esp8266/core_esp8266_postmortem.cpp b/cores/esp8266/core_esp8266_postmortem.cpp index 7fff9d2ff7..95844534e8 100644 --- a/cores/esp8266/core_esp8266_postmortem.cpp +++ b/cores/esp8266/core_esp8266_postmortem.cpp @@ -48,7 +48,7 @@ static const char* s_unhandled_exception = NULL; // Common way to notify about where the stack smashing happened // (but, **only** if caller uses our handler function) -static uint32_t s_stacksmash_addr = 0; +static uint32_t s_stack_chk_addr = 0; void abort() __attribute__((noreturn)); static void uart_write_char_d(char c); @@ -59,6 +59,7 @@ static void print_stack(uint32_t start, uint32_t end); // using numbers different from "REASON_" in user_interface.h (=0..6) enum rst_reason_sw { + REASON_USER_STACK_OVERFLOW = 252, REASON_USER_STACK_SMASH = 253, REASON_USER_SWEXCEPTION_RST = 254 }; @@ -188,7 +189,7 @@ static void postmortem_report(uint32_t sp_dump) { } else if (rst_info.reason == REASON_SOFT_WDT_RST) { ets_printf_P(PSTR("\nSoft WDT reset")); - const char infinite_loop[] = { 0x06, 0xff, 0xff }; // loop: j loop + const uint8_t infinite_loop[] = { 0x06, 0xff, 0xff }; // loop: j loop if (is_pc_valid(rst_info.epc1) && 0 == memcmp_P(infinite_loop, (PGM_VOID_P)rst_info.epc1, 3u)) { // The SDK is riddled with these. They are usually preceded by an ets_printf. ets_printf_P(PSTR(" - deliberate infinite loop detected")); @@ -198,17 +199,23 @@ static void postmortem_report(uint32_t sp_dump) { rst_info.exccause, /* Address executing at time of Soft WDT level-1 interrupt */ rst_info.epc1, 0, 0, 0, 0); } else if (rst_info.reason == REASON_USER_STACK_SMASH) { - ets_printf_P(PSTR("\nStack smashing detected.\n")); - ets_printf_P(PSTR("\nException (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"), - 5 /* Alloca exception, closest thing to stack fault*/, s_stacksmash_addr, 0, 0, 0, 0); - } + ets_printf_P(PSTR("\nStack smashing detected at 0x%08x\n"), s_stack_chk_addr); + } + else if (rst_info.reason == REASON_USER_STACK_OVERFLOW) { + ets_printf_P(PSTR("\nStack overflow detected\n")); + } else { ets_printf_P(PSTR("\nGeneric Reset\n")); } - uint32_t cont_stack_start = (uint32_t) &(g_pcont->stack); - uint32_t cont_stack_end = (uint32_t) g_pcont->stack_end; - uint32_t stack_end; + uint32_t cont_stack_start; + if (rst_info.reason == REASON_USER_STACK_SMASH) { + cont_stack_start = s_stack_chk_addr; + } else { + cont_stack_start = (uint32_t) (&g_pcont->stack[0]); + } + + uint32_t cont_stack_end = cont_stack_start + CONT_STACKSIZE; // amount of stack taken by interrupt or exception handler // and everything up to __wrap_system_restart_local @@ -249,15 +256,21 @@ static void postmortem_report(uint32_t sp_dump) { sp_dump = stack_thunk_get_cont_sp(); } - if (sp_dump > cont_stack_start && sp_dump < cont_stack_end) { + uint32_t stack_end; + + // above and inside of cont, dump from the sp to the bottom of the stack + if ((rst_info.reason == REASON_USER_STACK_OVERFLOW) + || ((sp_dump > cont_stack_start) && (sp_dump < cont_stack_end))) + { ets_printf_P(PSTR("\nctx: cont\n")); stack_end = cont_stack_end; } + // in system, reposition to a known address + // it's actually 0x3ffffff0, but the stuff below ets_run + // is likely not really relevant to the crash else { ets_printf_P(PSTR("\nctx: sys\n")); stack_end = 0x3fffffb0; - // it's actually 0x3ffffff0, but the stuff below ets_run - // is likely not really relevant to the crash } ets_printf_P(PSTR("sp: %08x end: %08x offset: %04x\n"), sp_dump, stack_end, offset); @@ -296,11 +309,20 @@ static void print_stack(uint32_t start, uint32_t end) { for (uint32_t pos = start; pos < end; pos += 0x10) { uint32_t* values = (uint32_t*)(pos); + // avoid printing irrelevant data + if ((values[0] == CONT_STACKGUARD) + && (values[0] == values[1]) + && (values[1] == values[2]) + && (values[2] == values[3])) + { + continue; + } + // rough indicator: stack frames usually have SP saved as the second word - bool looksLikeStackFrame = (values[2] == pos + 0x10); + const bool looksLikeStackFrame = (values[2] == pos + 0x10); ets_printf_P(PSTR("%08x: %08x %08x %08x %08x %c\n"), - pos, values[0], values[1], values[2], values[3], (looksLikeStackFrame)?'<':' '); + pos, values[0], values[1], values[2], values[3], (looksLikeStackFrame) ? '<' : ' '); } } @@ -370,7 +392,7 @@ void __panic_func(const char* file, int line, const char* func) { uintptr_t __stack_chk_guard = 0x08675309 ^ RANDOM_REG32; void __stack_chk_fail(void) { s_user_reset_reason = REASON_USER_STACK_SMASH; - s_stacksmash_addr = (uint32_t)__builtin_return_address(0); + s_stack_chk_addr = (uint32_t)__builtin_return_address(0); if (gdb_present()) __asm__ __volatile__ ("syscall"); // triggers GDB when enabled @@ -382,4 +404,16 @@ void __stack_chk_fail(void) { __builtin_unreachable(); // never reached, needed to satisfy "noreturn" attribute } +void __stack_overflow(cont_t* cont, uint32_t* sp) { + s_user_reset_reason = REASON_USER_STACK_OVERFLOW; + s_stack_chk_addr = (uint32_t)&cont->stack[0]; + + if (gdb_present()) + __asm__ __volatile__ ("syscall"); // triggers GDB when enabled + + postmortem_report((uint32_t)sp); + + __builtin_unreachable(); // never reached, needed to satisfy "noreturn" attribute +} + } // extern "C" diff --git a/cores/esp8266/debug.h b/cores/esp8266/debug.h index 0e30000c53..437479748c 100644 --- a/cores/esp8266/debug.h +++ b/cores/esp8266/debug.h @@ -4,6 +4,8 @@ #include #include +#include "cont.h" + #define _DEBUG_LEAF_FUNCTION(...) __asm__ __volatile__("" ::: "a0", "memory") #ifdef DEBUG_ESP_CORE @@ -32,6 +34,7 @@ extern "C" { #endif void __stack_chk_fail(void) __attribute__((noreturn)); + void __stack_overflow(cont_t*, uint32_t*) __attribute__((noreturn)); void __unhandled_exception(const char* str) __attribute__((noreturn)); void __panic_func(const char* file, int line, const char* func) __attribute__((noreturn)); #define panic() __panic_func(PSTR(__FILE__), __LINE__, __func__) diff --git a/tools/decoder.py b/tools/decoder.py index 1ed3dcf8ed..0055693648 100755 --- a/tools/decoder.py +++ b/tools/decoder.py @@ -52,6 +52,7 @@ "permit stores", ) + # similar to java version, which used `list` and re-formatted it # instead, simply use an already short-format `info line` # TODO `info symbol`? revert to `list`? @@ -96,12 +97,12 @@ def addresses_addr2line(addr2line, elf, addresses): def decode_lines(format_addresses, elf, lines): - STACK_RE = re.compile(r"^[0-9a-f]{8}:\s+([0-9a-f]{8} ?)+ *$") + ANY_ADDR_RE = re.compile(r"0x[0-9a-fA-F]{8}|[0-9a-fA-F]{8}") + HEX_ADDR_RE = re.compile(r"0x[0-9a-f]{8}") - LAST_ALLOC_RE = re.compile( - r"last failed alloc call: ([0-9a-fA-F]{8})\(([0-9]+)\).*" - ) - LAST_ALLOC = "last failed alloc" + MEM_ERR_LINE_RE = re.compile(r"^(Stack|last failed alloc call)") + + STACK_LINE_RE = re.compile(r"^[0-9a-f]{8}:\s\s+") CUT_HERE_STRING = "CUT HERE FOR EXCEPTION DECODER" EXCEPTION_STRING = "Exception (" @@ -131,13 +132,11 @@ def format_address(address): stack_addresses = print_all_addresses(stack_addresses) last_stack = line.strip() # 3fffffb0: feefeffe feefeffe 3ffe85d8 401004ed - elif in_stack and STACK_RE.match(line): - stack, addrs = line.split(":") - addrs = addrs.strip() - addrs = addrs.split(" ") + elif in_stack and STACK_LINE_RE.match(line): + _, addrs = line.split(":") + addrs = ANY_ADDR_RE.findall(addrs) stack_addresses.setdefault(last_stack, []) - for addr in addrs: - stack_addresses[last_stack].append(addr) + stack_addresses[last_stack].extend(addrs) # epc1=0xfffefefe epc2=0xfefefefe epc3=0xefefefef excvaddr=0xfefefefe depc=0xfefefefe elif EPC_STRING in line: pairs = line.split() @@ -152,13 +151,13 @@ def format_address(address): elif EXCEPTION_STRING in line: number = line.strip()[len(EXCEPTION_STRING) : -2] print(f"Exception ({number}) - {EXCEPTION_CODES[int(number)]}") + # stack smashing detected at # last failed alloc call: ()[@] - elif LAST_ALLOC in line: - values = LAST_ALLOC_RE.match(line) - if values: - addr, size = values.groups() - print() - print(f"Allocation of {size} bytes failed: {format_address(addr)}") + elif MEM_ERR_LINE_RE.match(line): + for addr in ANY_ADDR_RE.findall(line): + line = line.replace(addr, format_address(addr)) + print() + print(line.strip()) # postmortem guards our actual stack dump values with these elif ">>>stack>>>" in line: in_stack = True From d7c50f76aa8a6110c0d7f9c10a10e6a6001b035e Mon Sep 17 00:00:00 2001 From: David Baka Date: Wed, 27 Mar 2024 14:07:29 +0100 Subject: [PATCH 5/7] Updater - fixed signature verification for compressed binaries (#9109) Previously, Arduino Core attempted to read from flash memory without proper consideration for the 4-byte alignment requirement when calculating the hash for the signature verification. This did not present an issue when uncompressed binaries are checked as all compiled binaries are 4-aligned (unconfirmed, just an educated guess), and signature verification appears to work well in these cases. When uploading a compressed binary (based on this) the gzip algorithm makes no attempt to produce a 4-aligned file. The rest of the signing results in a valid signed binary regardless, however when calculating the hash for the verification process there is a ~75% chance that the hash will include some bytes from the signature, thus compromising the whole signature verification process. editorial note: ESP.flashRead for u8 arrays (aka byte arrays) was already updated to properly handle both aligned and unaligned target buffer and / or length, while u32 expects that its arguments are already aligned. Since array pointer in Updater is already aligned, this properly handles unaligned size case. --- cores/esp8266/Updater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index b4961b616d..ef79a5cbf3 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -284,7 +284,7 @@ bool UpdaterClass::end(bool evenIfRemaining){ _hash->begin(); for (uint32_t offset = 0; offset < binSize; offset += sizeof(buff)) { auto len = std::min(sizeof(buff), binSize - offset); - ESP.flashRead(_startAddress + offset, reinterpret_cast(&buff[0]), len); + ESP.flashRead(_startAddress + offset, buff, len); _hash->add(buff, len); } _hash->end(); From 75cd58f64757a7b87e9b9659274c2955346e7ef9 Mon Sep 17 00:00:00 2001 From: David Refoua Date: Thu, 28 Mar 2024 01:28:51 +0330 Subject: [PATCH 6/7] fix minor typo (#9111) --- libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp b/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp index 5eb62eec15..a8589c4d25 100644 --- a/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp +++ b/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp @@ -21,7 +21,7 @@ extern "C" void __disableWiFiAtBootTime() // Does (almost) nothing: WiFi is enabled by default in nonos-sdk // ... but restores legacy WiFi credentials persistence to true at boot time - // (can be still overrisden by user before setting up WiFi, like before) + // (can be still overridden by user before setting up WiFi, like before) // (note: c++ ctors not called yet at this point) ESP8266WiFiClass::persistent(true); From 685f2c97ff4b3c76b437a43be86d1dfdf6cb33e3 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Sun, 31 Mar 2024 18:24:25 +0200 Subject: [PATCH 7/7] decoder.py: allowing to use it live (#9108) - avoid ctx repetition and hide some useless fw debug lines - hide "DECODE IT" - immediately print stack at closing marker - check for tool executable at startup live command line example > pyserial-miniterm /dev/ttyUSB0 115200 | \ path/to/esp8266/Arduino/tools/decoder.py \ --tool addr2line \ --toolchain-path path/to/esp8266/Arduino/tools/xtensa-lx106-elf/bin \ path/to/build.elf --- tools/decoder.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/decoder.py b/tools/decoder.py index 0055693648..b1f1706767 100755 --- a/tools/decoder.py +++ b/tools/decoder.py @@ -10,6 +10,7 @@ import sys import re import subprocess +import shutil # https://github.com/me-no-dev/EspExceptionDecoder/blob/349d17e4c9896306e2c00b4932be3ba510cad208/src/EspExceptionDecoder.java#L59-L90 EXCEPTION_CODES = ( @@ -104,7 +105,10 @@ def decode_lines(format_addresses, elf, lines): STACK_LINE_RE = re.compile(r"^[0-9a-f]{8}:\s\s+") + IGNORE_FIRMWARE_RE = re.compile(r"^(epc1=0x........, |Fatal exception )") + CUT_HERE_STRING = "CUT HERE FOR EXCEPTION DECODER" + DECODE_IT = "DECODE IT" EXCEPTION_STRING = "Exception (" EPC_STRING = "epc1=" @@ -132,6 +136,8 @@ def format_address(address): stack_addresses = print_all_addresses(stack_addresses) last_stack = line.strip() # 3fffffb0: feefeffe feefeffe 3ffe85d8 401004ed + elif IGNORE_FIRMWARE_RE.match(line): + continue elif in_stack and STACK_LINE_RE.match(line): _, addrs = line.split(":") addrs = ANY_ADDR_RE.findall(addrs) @@ -163,8 +169,10 @@ def format_address(address): in_stack = True # ignore elif "<<