DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
exc_decoder.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file exc_decoder.cpp
6 * @brief ESP32 panic / exception decoder (see exc_decoder.h).
7 */
8
10
11#if DETWS_ENABLE_EXC_DECODER
12
13#include <string.h>
14
16
17namespace
18{
19bool hexval(char c, uint8_t *v)
20{
21 if (c >= '0' && c <= '9')
22 *v = (uint8_t)(c - '0');
23 else if (c >= 'a' && c <= 'f')
24 *v = (uint8_t)(c - 'a' + 10);
25 else if (c >= 'A' && c <= 'F')
26 *v = (uint8_t)(c - 'A' + 10);
27 else
28 return false;
29 return true;
30}
31
32const char *skip_ws(const char *p)
33{
34 while (*p == ' ' || *p == '\t')
35 p++;
36 return p;
37}
38
39// Parse a "0x...." hex literal at p; on success write *out and return the char after the last digit.
40const char *parse_hex(const char *p, uint32_t *out)
41{
42 if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X'))
43 return nullptr;
44 p += 2;
45 uint32_t v = 0;
46 int n = 0;
47 uint8_t d = 0;
48 while (hexval(*p, &d) && n < 8)
49 {
50 v = (v << 4) | d;
51 p++;
52 n++;
53 }
54 if (n == 0)
55 return nullptr;
56 *out = v;
57 return p;
58}
59
60void put_json_str(DetSb *b, const char *s)
61{
62 det_sb_put(b, "\"");
63 for (const char *p = s ? s : ""; *p; p++)
64 {
65 if (*p == '"' || *p == '\\')
66 {
67 char esc[3] = {'\\', *p, '\0'};
68 det_sb_put(b, esc);
69 }
70 else if (b->len + 1 < b->cap)
71 b->p[b->len++] = *p;
72 else
73 b->ok = false;
74 }
75 det_sb_put(b, "\"");
76}
77
78// Emit a 32-bit value as a JSON string literal "0x........".
79void put_hex32(DetSb *b, uint32_t v)
80{
81 char t[13] = "\"0x00000000\"";
82 static const char *H = "0123456789abcdef";
83 for (int i = 0; i < 8; i++)
84 t[3 + i] = H[(v >> ((7 - i) * 4)) & 0xF];
85 det_sb_put(b, t);
86}
87
88void put_int(DetSb *b, int v)
89{
90 char t[12];
91 int n = 0;
92 bool neg = v < 0;
93 unsigned u = neg ? (unsigned)(-(long)v) : (unsigned)v;
94 do
95 {
96 t[n++] = (char)('0' + u % 10);
97 u /= 10;
98 } while (u);
99 char o[13];
100 int k = 0;
101 if (neg)
102 o[k++] =
103 '-'; // GCOVR_EXCL_LINE detws_exc_json's only put_int call is guarded by core >= 0, so v is never negative
104 for (int i = 0; i < n; i++)
105 o[k++] = t[n - 1 - i];
106 o[k] = '\0';
107 det_sb_put(b, o);
108}
109
110// Parse a run of decimal digits at @p p into a small non-negative int, clamped to avoid signed-overflow
111// UB on absurd input (core ids / counts are tiny). Extracted to keep the callers' scan loops flat.
112int parse_small_int(const char *p)
113{
114 int n = 0;
115 while (*p >= '0' && *p <= '9')
116 {
117 if (n < 100000)
118 n = n * 10 + (*p - '0');
119 p++;
120 }
121 return n;
122}
123
124// Cause: "...panic'ed (LoadProhibited)."
125void parse_cause(const char *text, ExcInfo *out)
126{
127 const char *c = strstr(text, "panic'ed (");
128 if (!c)
129 return;
130 c += 10;
131 size_t i = 0;
132 while (i < sizeof(out->cause) - 1 && c[i] && c[i] != ')') // range check first (short-circuits the read)
133 {
134 out->cause[i] = c[i];
135 i++;
136 }
137 out->cause[i] = '\0';
138}
139
140// Core number: "Core N ...".
141void parse_core(const char *text, ExcInfo *out)
142{
143 const char *co = strstr(text, "Core ");
144 if (!co)
145 return;
146 const char *p = skip_ws(co + 5);
147 if (*p >= '0' && *p <= '9')
148 out->core = parse_small_int(p); // clamped inside; avoids signed-overflow UB on a huge number
149}
150
151// EXCVADDR (faulting data address).
152void parse_excvaddr(const char *text, ExcInfo *out)
153{
154 const char *e = strstr(text, "EXCVADDR");
155 if (!e)
156 return;
157 const char *colon = strchr(e, ':');
158 if (!colon)
159 return;
160 uint32_t v = 0;
161 if (parse_hex(skip_ws(colon + 1), &v))
162 {
163 out->excvaddr = v;
164 out->has_excvaddr = true;
165 }
166}
167
168// Register-dump PC: a line that starts with "PC" (not "EPC..."). Anchor to a line break.
169void parse_pc(const char *text, ExcInfo *out)
170{
171 const char *pcl = (strncmp(text, "PC", 2) == 0) ? text : strstr(text, "\nPC");
172 if (!pcl)
173 return;
174 const char *colon = strchr(pcl, ':');
175 if (!colon)
176 return;
177 uint32_t v = 0;
178 if (parse_hex(skip_ws(colon + 1), &v))
179 out->pc = v;
180}
181
182// Backtrace: "Backtrace: pc:sp pc:sp ...".
183void parse_backtrace(const char *text, ExcInfo *out)
184{
185 const char *bt = strstr(text, "Backtrace:");
186 if (!bt)
187 return;
188 const char *p = bt + 10;
189 while (out->frame_count < DETWS_EXC_MAX_FRAMES)
190 {
191 p = skip_ws(p);
192 uint32_t pc = 0;
193 uint32_t sp = 0;
194 const char *q = parse_hex(p, &pc);
195 if (!q || *q != ':')
196 break;
197 const char *r = parse_hex(q + 1, &sp);
198 if (!r)
199 break;
200 out->frames[out->frame_count].pc = pc;
201 out->frames[out->frame_count].sp = sp;
202 out->frame_count++;
203 p = r;
204 }
205}
206} // namespace
207
208bool detws_exc_parse(const char *text, ExcInfo *out)
209{
210 if (!text || !out)
211 return false;
212 out->core = -1;
213 out->cause[0] = '\0';
214 out->pc = 0;
215 out->excvaddr = 0;
216 out->has_excvaddr = false;
217 out->frame_count = 0;
218
219 parse_cause(text, out);
220 parse_core(text, out);
221 parse_excvaddr(text, out);
222 parse_pc(text, out);
223 parse_backtrace(text, out);
224
225 if (out->pc == 0 && out->frame_count > 0)
226 out->pc = out->frames[0].pc;
227
228 return out->cause[0] != '\0' || out->pc != 0 || out->frame_count > 0;
229}
230
231size_t detws_exc_json(const ExcInfo *info, char *out, size_t cap)
232{
233 if (!info || !out || cap == 0)
234 return 0;
235 DetSb b = {out, cap, 0, true};
236 det_sb_put(&b, "{");
237 bool first = true;
238 if (info->core >= 0)
239 {
240 det_sb_put(&b, "\"core\":");
241 put_int(&b, info->core);
242 first = false;
243 }
244 if (!first)
245 det_sb_put(&b, ",");
246 det_sb_put(&b, "\"cause\":");
247 put_json_str(&b, info->cause);
248 det_sb_put(&b, ",\"pc\":");
249 put_hex32(&b, info->pc);
250 if (info->has_excvaddr)
251 {
252 det_sb_put(&b, ",\"excvaddr\":");
253 put_hex32(&b, info->excvaddr);
254 }
255 det_sb_put(&b, ",\"backtrace\":[");
256 for (size_t i = 0; i < info->frame_count; i++)
257 {
258 if (i)
259 det_sb_put(&b, ",");
260 put_hex32(&b, info->frames[i].pc);
261 }
262 det_sb_put(&b, "]}");
263 if (!b.ok)
264 return 0;
265 out[b.len] = '\0';
266 return b.len;
267}
268
269#endif // DETWS_ENABLE_EXC_DECODER
ESP32 panic / exception decoder for a live diagnostics panel (DETWS_ENABLE_EXC_DECODER).
Bounded no-heap string builder that fails closed on overflow (one shared copy).
void det_sb_put(DetSb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Definition strbuf.h:36
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:28
size_t len
Definition strbuf.h:31
char * p
Definition strbuf.h:29
bool ok
Definition strbuf.h:32
size_t cap
Definition strbuf.h:30