Merge pull request #3716 from cesanta/crash

Print crash record optionally
This commit is contained in:
robertc2000
2026-10-01 14:48:36 +03:00
committed by GitHub
9 changed files with 119 additions and 76 deletions
+51 -33
View File
@@ -6991,7 +6991,10 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
// c->is_hexdumping = true;
if (mg_url_is_ssl(MG_MDASH_URL)) {
struct mg_str host = mg_url_host(MG_MDASH_URL);
struct mg_tls_opts opts = {.ca = mg_str(mg_mdash_ca_pem), .name = host};
struct mg_tls_opts opts;
memset(&opts, 0, sizeof(struct mg_tls_opts));
opts.ca = mg_str(mg_mdash_ca_pem);
opts.name = host;
mg_tls_init(c, &opts);
}
}
@@ -7001,16 +7004,18 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
// mDashTriggerEvent(s_mDashState, NULL);
} else if (ev == MG_EV_WS_MSG) {
struct mg_iobuf io = {0, 0, 0, 512};
struct mg_ws_message *wm = ev_data;
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
struct mg_str s = wm->data;
struct mg_rpc_req r;
while (s.len > 0 && isspace((uint8_t) s.buf[s.len - 1])) s.len--;
MG_INFO(("RPC: %.*s", (int) s.len, s.buf));
struct mg_rpc_req r = {.head = &c->mgr->rpcs,
.rpc = NULL,
.pfn = mg_pfn_iobuf,
.pfn_data = &io,
.req_data = NULL,
.frame = s};
memset(&r, 0, sizeof(struct mg_rpc_req));
r.head = &c->mgr->rpcs;
r.rpc = NULL;
r.pfn = mg_pfn_iobuf;
r.pfn_data = &io;
r.req_data = NULL;
r.frame = s;
mg_rpc_process(&r);
if (io.buf != NULL) {
MG_INFO((" %s", io.buf));
@@ -7026,32 +7031,35 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
(void) c;
}
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t n = va_arg(*ap, size_t);
uint32_t *p = va_arg(*ap, uint32_t *);
size_t len = 0;
while (n--) len += mg_xprintf(fn, arg, "%s%lu", len == 0 ? "" : ",", *p++);
return len;
}
static size_t print_crash(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t len = 0;
if (mg_health_valid()) {
uint32_t *data = mg_health_record.backtrace;
mg_rpc_ok(r,
"{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\",%m:{%m:true,"
"%m:{%m:\"0x%08lx\",%m:\"0x%08lx\",%m:\"0x%08lx\"},"
"%m:{%m:\"0x%08lx\",%m:%m}}}",
MG_ESC("fw_version"), MG_ESC(MG_FIRMWARE_VERSION),
MG_ESC("uptime"), (uint64_t) (mg_millis() / 1000),
MG_ESC("reboot_reason"),
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"),
MG_ARCH, MG_ESC("report"), MG_ESC("valid"), MG_ESC("regs"),
MG_ESC("sp"), (unsigned long) data[0], MG_ESC("lr"),
(unsigned long) data[1], MG_ESC("pc"), (unsigned long) data[2],
MG_ESC("stack"), MG_ESC("addr"), (unsigned long) data[0],
MG_ESC("data"), mg_print_base64,
(int) ((MG_HEALTH_BACKTRACE - 3) * sizeof(data[0])),
(uint8_t *) &data[3]);
} else {
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\",%m:{%m:false}}",
MG_ESC("fw_version"), MG_ESC(MG_FIRMWARE_VERSION),
MG_ESC("uptime"), (uint64_t) (mg_millis() / 1000),
MG_ESC("reboot_reason"),
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"),
MG_ARCH, MG_ESC("report"), MG_ESC("valid"));
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
len += mg_xprintf(fn, arg, ",%m:{%m:%hhu,%m:%u,%m:[%M]}", MG_ESC("crash"),
MG_ESC("version"), mg_health_record.magic[3], //
MG_ESC("cpuid"), mg_health_record.cpuid, //
MG_ESC("data"), print_uint32, n, mg_health_record.data);
}
(void) ap;
return len;
}
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
const char *reset_reason = mg_health_reason_str(mg_health_reason());
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\"%M}", //
MG_ESC("fw_version"), MG_ESC(MG_FIRMWARE_VERSION), //
MG_ESC("uptime"), (uint64_t) (mg_millis() / 1000), //
MG_ESC("reboot_reason"), MG_ESC(reset_reason), //
MG_ESC("arch"), MG_ARCH, //
print_crash);
}
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
@@ -7070,7 +7078,7 @@ static void mg_mdash_rpc_ota_write(struct mg_rpc_req *r) {
mg_rpc_err(r, 500, "%m", MG_ESC("data required"));
} else {
struct mg_str s = mg_str_n(&r->frame.buf[ofs] + 1, (size_t) (len - 2));
size_t n = mg_base64_decode(s.buf, s.len, s.buf, s.len); // Decode in-place
size_t n = mg_base64_decode(s.buf, s.len, s.buf, s.len); // Decode in-place
if (n == 0) {
mg_rpc_ok(r, "%m", MG_ESC("finished"));
mg_ota_end();
@@ -7115,6 +7123,10 @@ void mg_mdash_poll(struct mg_mgr *mgr) {
}
}
void mg_mdash_free(struct mg_mgr *mgr) {
mg_rpc_del(&mgr->rpcs, NULL);
}
#else
void mg_mdash_init(struct mg_mgr *mgr) {
(void) mgr;
@@ -7122,6 +7134,9 @@ void mg_mdash_init(struct mg_mgr *mgr) {
void mg_mdash_poll(struct mg_mgr *mgr) {
(void) mgr;
}
void mg_mdash_free(struct mg_mgr *mgr) {
(void) mgr;
}
#endif
#ifdef MG_ENABLE_LINES
@@ -8271,6 +8286,9 @@ void mg_mgr_free(struct mg_mgr *mgr) {
mgr->timers = NULL; // Important. Next call to poll won't touch timers
for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
mg_mgr_poll(mgr, 0);
#if MG_ENABLE_MDASH
mg_mdash_free(mgr);
#endif
#if MG_ENABLE_FREERTOS_TCP
FreeRTOS_DeleteSocketSet(mgr->ss);
#endif
+5 -3
View File
@@ -4967,14 +4967,15 @@ enum mg_health_reason {
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
};
#ifndef MG_HEALTH_BACKTRACE
#define MG_HEALTH_BACKTRACE 20 // Return addresses in mg_health::backtrace
#ifndef MG_HEALTH_DATA_SIZE
#define MG_HEALTH_DATA_SIZE 20
#endif
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint32_t backtrace[MG_HEALTH_BACKTRACE]; // Crash backtrace, frame 0 first
uint32_t cpuid; // ARM CPU ID
uint32_t data[MG_HEALTH_DATA_SIZE];
};
extern struct mg_health mg_health_record; // Defined in health.c
@@ -5056,6 +5057,7 @@ static inline void mg_health_init(void) {
void mg_mdash_init(struct mg_mgr *);
void mg_mdash_poll(struct mg_mgr *);
void mg_mdash_free(struct mg_mgr *);
+4 -3
View File
@@ -52,14 +52,15 @@ enum mg_health_reason {
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
};
#ifndef MG_HEALTH_BACKTRACE
#define MG_HEALTH_BACKTRACE 20 // Return addresses in mg_health::backtrace
#ifndef MG_HEALTH_DATA_SIZE
#define MG_HEALTH_DATA_SIZE 20
#endif
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint32_t backtrace[MG_HEALTH_BACKTRACE]; // Crash backtrace, frame 0 first
uint32_t cpuid; // ARM CPU ID
uint32_t data[MG_HEALTH_DATA_SIZE];
};
extern struct mg_health mg_health_record; // Defined in health.c
+48 -33
View File
@@ -55,7 +55,10 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
// c->is_hexdumping = true;
if (mg_url_is_ssl(MG_MDASH_URL)) {
struct mg_str host = mg_url_host(MG_MDASH_URL);
struct mg_tls_opts opts = {.ca = mg_str(mg_mdash_ca_pem), .name = host};
struct mg_tls_opts opts;
memset(&opts, 0, sizeof(struct mg_tls_opts));
opts.ca = mg_str(mg_mdash_ca_pem);
opts.name = host;
mg_tls_init(c, &opts);
}
}
@@ -65,16 +68,18 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
// mDashTriggerEvent(s_mDashState, NULL);
} else if (ev == MG_EV_WS_MSG) {
struct mg_iobuf io = {0, 0, 0, 512};
struct mg_ws_message *wm = ev_data;
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
struct mg_str s = wm->data;
struct mg_rpc_req r;
while (s.len > 0 && isspace((uint8_t) s.buf[s.len - 1])) s.len--;
MG_INFO(("RPC: %.*s", (int) s.len, s.buf));
struct mg_rpc_req r = {.head = &c->mgr->rpcs,
.rpc = NULL,
.pfn = mg_pfn_iobuf,
.pfn_data = &io,
.req_data = NULL,
.frame = s};
memset(&r, 0, sizeof(struct mg_rpc_req));
r.head = &c->mgr->rpcs;
r.rpc = NULL;
r.pfn = mg_pfn_iobuf;
r.pfn_data = &io;
r.req_data = NULL;
r.frame = s;
mg_rpc_process(&r);
if (io.buf != NULL) {
MG_INFO((" %s", io.buf));
@@ -90,32 +95,35 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
(void) c;
}
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t n = va_arg(*ap, size_t);
uint32_t *p = va_arg(*ap, uint32_t *);
size_t len = 0;
while (n--) len += mg_xprintf(fn, arg, "%s%lu", len == 0 ? "" : ",", *p++);
return len;
}
static size_t print_crash(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t len = 0;
if (mg_health_valid()) {
uint32_t *data = mg_health_record.backtrace;
mg_rpc_ok(r,
"{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\",%m:{%m:true,"
"%m:{%m:\"0x%08lx\",%m:\"0x%08lx\",%m:\"0x%08lx\"},"
"%m:{%m:\"0x%08lx\",%m:%m}}}",
MG_ESC("fw_version"), MG_ESC(MG_FIRMWARE_VERSION),
MG_ESC("uptime"), (uint64_t) (mg_millis() / 1000),
MG_ESC("reboot_reason"),
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"),
MG_ARCH, MG_ESC("report"), MG_ESC("valid"), MG_ESC("regs"),
MG_ESC("sp"), (unsigned long) data[0], MG_ESC("lr"),
(unsigned long) data[1], MG_ESC("pc"), (unsigned long) data[2],
MG_ESC("stack"), MG_ESC("addr"), (unsigned long) data[0],
MG_ESC("data"), mg_print_base64,
(int) ((MG_HEALTH_BACKTRACE - 3) * sizeof(data[0])),
(uint8_t *) &data[3]);
} else {
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\",%m:{%m:false}}",
MG_ESC("fw_version"), MG_ESC(MG_FIRMWARE_VERSION),
MG_ESC("uptime"), (uint64_t) (mg_millis() / 1000),
MG_ESC("reboot_reason"),
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"),
MG_ARCH, MG_ESC("report"), MG_ESC("valid"));
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
len += mg_xprintf(fn, arg, ",%m:{%m:%hhu,%m:%u,%m:[%M]}", MG_ESC("crash"),
MG_ESC("version"), mg_health_record.magic[3], //
MG_ESC("cpuid"), mg_health_record.cpuid, //
MG_ESC("data"), print_uint32, n, mg_health_record.data);
}
(void) ap;
return len;
}
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
const char *reset_reason = mg_health_reason_str(mg_health_reason());
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\"%M}", //
MG_ESC("fw_version"), MG_ESC(MG_FIRMWARE_VERSION), //
MG_ESC("uptime"), (uint64_t) (mg_millis() / 1000), //
MG_ESC("reboot_reason"), MG_ESC(reset_reason), //
MG_ESC("arch"), MG_ARCH, //
print_crash);
}
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
@@ -134,7 +142,7 @@ static void mg_mdash_rpc_ota_write(struct mg_rpc_req *r) {
mg_rpc_err(r, 500, "%m", MG_ESC("data required"));
} else {
struct mg_str s = mg_str_n(&r->frame.buf[ofs] + 1, (size_t) (len - 2));
size_t n = mg_base64_decode(s.buf, s.len, s.buf, s.len); // Decode in-place
size_t n = mg_base64_decode(s.buf, s.len, s.buf, s.len); // Decode in-place
if (n == 0) {
mg_rpc_ok(r, "%m", MG_ESC("finished"));
mg_ota_end();
@@ -179,6 +187,10 @@ void mg_mdash_poll(struct mg_mgr *mgr) {
}
}
void mg_mdash_free(struct mg_mgr *mgr) {
mg_rpc_del(&mgr->rpcs, NULL);
}
#else
void mg_mdash_init(struct mg_mgr *mgr) {
(void) mgr;
@@ -186,4 +198,7 @@ void mg_mdash_init(struct mg_mgr *mgr) {
void mg_mdash_poll(struct mg_mgr *mgr) {
(void) mgr;
}
void mg_mdash_free(struct mg_mgr *mgr) {
(void) mgr;
}
#endif
+1
View File
@@ -18,3 +18,4 @@
void mg_mdash_init(struct mg_mgr *);
void mg_mdash_poll(struct mg_mgr *);
void mg_mdash_free(struct mg_mgr *);
+3
View File
@@ -300,6 +300,9 @@ void mg_mgr_free(struct mg_mgr *mgr) {
mgr->timers = NULL; // Important. Next call to poll won't touch timers
for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
mg_mgr_poll(mgr, 0);
#if MG_ENABLE_MDASH
mg_mdash_free(mgr);
#endif
#if MG_ENABLE_FREERTOS_TCP
FreeRTOS_DeleteSocketSet(mgr->ss);
#endif
+3 -1
View File
@@ -1,6 +1,6 @@
SRCS = mongoose.c unit_test.c packed_fs.c
HDRS = $(wildcard ../src/*.h) $(wildcard ../src/drivers/*.h)
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_SSI=1 -DMG_ENABLE_ASSERT=1 -DMG_ENABLE_IPV6=$(IPV6)
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_SSI=1 -DMG_ENABLE_ASSERT=1 -DMG_ENABLE_IPV6=$(IPV6) -DMG_ENABLE_MDASH=$(MDASH)
WARN ?= -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion -Wundef
OPTS ?= -O3 -g3
INCS ?= -Isrc -I.
@@ -13,6 +13,7 @@ DOCKER_BIN ?= docker
DOCKER ?= $(DOCKER_BIN) run --platform linux/amd64 --net=host --rm $(ENV) -v $(ROOT_DIR):$(ROOT_DIR) -w $(CWD)
VCFLAGS ?= /nologo /W3 /O2 /MD /I.
IPV6 ?= 1
MDASH ?= 1
ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common
ASAN_OPTIONS ?= detect_leaks=1
# This does not include ESP32, ESP8266, Microchip; all examples that are directly placed under an OS tutorial. Also, it is needed that way...
@@ -269,6 +270,7 @@ clean: clean_tutorials clean_tutorials_embedded
# MIP (net_builtin + L2 + driver) TESTS
mip_test: MDASH = 0
mip_test: mip_test.c mongoose.c mongoose.h packed_fs.c Makefile
$(CC) mip_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
+3 -2
View File
@@ -46,15 +46,16 @@ __attribute__((used, noinline)) static void fault_c(uint32_t *frame,
uint32_t exc_return) {
extern uint32_t _estack;
uint32_t *stack = frame + 8 + ((exc_return & (1U << 4)) ? 0 : 18);
uint32_t *r = mg_health_record.backtrace;
uint32_t *r = mg_health_record.data;
if (frame[7] & (1U << 9)) stack++; // Eight-byte stack alignment padding
r[CRASH_SP] = (uint32_t) (uintptr_t) stack;
r[CRASH_LR] = frame[5];
r[CRASH_PC] = frame[6];
for (size_t i = 0; i < MG_HEALTH_BACKTRACE - CRASH_STACK; i++) {
for (size_t i = 0; i < MG_HEALTH_DATA_SIZE - CRASH_STACK; i++) {
r[CRASH_STACK + i] = stack + i < &_estack ? stack[i] : 0;
}
mg_health_record.cpuid = SCB->CPUID;
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
NVIC_SystemReset();
}
@@ -28,7 +28,7 @@
// Crash report support. Health record lives in the .mg_health region, see link.ld
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
#define MG_HEALTH_BACKTRACE 1022 // fill the 4KB backup SRAM
#define MG_HEALTH_DATA_SIZE 1000 // fill the 4KB backup SRAM
// mdash.net device management service support
#define MG_ENABLE_MDASH 1