ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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#include "shared_primitives/strbuf.h" // pc_sb frame builder
11
12#if PC_ENABLE_EXC_DECODER
13
14#include <string.h>
15
16namespace
17{
18bool hexval(char c, uint8_t *v)
19{
20 if (c >= '0' && c <= '9')
21 {
22 *v = (uint8_t)(c - '0');
23 }
24 else if (c >= 'a' && c <= 'f')
25 {
26 *v = (uint8_t)(c - 'a' + 10);
27 }
28 else if (c >= 'A' && c <= 'F')
29 {
30 *v = (uint8_t)(c - 'A' + 10);
31 }
32 else
33 {
34 return false;
35 }
36 return true;
37}
38
39const char *skip_ws(const char *p)
40{
41 while (*p == ' ' || *p == '\t')
42 {
43 p++;
44 }
45 return p;
46}
47
48// Parse a "0x...." hex literal at p; on success write *out and return the char after the last digit.
49const char *parse_hex(const char *p, uint32_t *out)
50{
51 if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X'))
52 {
53 return nullptr;
54 }
55 p += 2;
56 uint32_t v = 0;
57 int n = 0;
58 uint8_t d = 0;
59 while (hexval(*p, &d) && n < 8)
60 {
61 v = (v << 4) | d;
62 p++;
63 n++;
64 }
65 if (n == 0)
66 {
67 return nullptr;
68 }
69 *out = v;
70 return p;
71}
72
73void put_json_str(pc_sb *b, const char *s)
74{
75 pc_sb_put(b, "\"");
76 const char *src = s ? s : ""; // GCOVR_EXCL_LINE the only caller passes info->cause, a fixed-size array
77 for (const char *p = src; *p; p++)
78 {
79 if (*p == '"' || *p == '\\')
80 {
81 char esc[3] = {'\\', *p, '\0'};
82 pc_sb_put(b, esc);
83 }
84 else if (b->len + 1 < b->cap)
85 {
86 b->p[b->len++] = *p;
87 }
88 else
89 {
90 b->ok = false;
91 }
92 }
93 pc_sb_put(b, "\"");
94}
95
96// Emit a 32-bit value as a JSON string literal "0x........".
97void put_hex32(pc_sb *b, uint32_t v)
98{
99 char t[13] = "\"0x00000000\"";
100 static const char *H = "0123456789abcdef";
101 for (int i = 0; i < 8; i++)
102 {
103 t[3 + i] = H[(v >> ((7 - i) * 4)) & 0xF];
104 }
105 pc_sb_put(b, t);
106}
107
108void put_int(pc_sb *b, int v)
109{
110 char t[12];
111 int n = 0;
112 bool neg = v < 0;
113 unsigned u = neg ? (unsigned)(-(long)v) : (unsigned)v; // GCOVR_EXCL_LINE neg is always false (core >= 0)
114 do
115 {
116 t[n++] = (char)('0' + u % 10);
117 u /= 10;
118 } while (u);
119 char o[13];
120 int k = 0;
121 // GCOVR_EXCL_START pc_exc_json's only put_int call is guarded by core >= 0, so v is never negative
122 if (neg)
123 {
124 o[k++] = '-';
125 }
126 // GCOVR_EXCL_STOP
127 for (int i = 0; i < n; i++)
128 {
129 o[k++] = t[n - 1 - i];
130 }
131 o[k] = '\0';
132 pc_sb_put(b, o);
133}
134
135// Parse a run of decimal digits at @p p into a small non-negative int, clamped to avoid signed-overflow
136// UB on absurd input (core ids / counts are tiny). Extracted to keep the callers' scan loops flat.
137int parse_small_int(const char *p)
138{
139 int n = 0;
140 while (*p >= '0' && *p <= '9')
141 {
142 if (n < 100000)
143 {
144 n = n * 10 + (*p - '0');
145 }
146 p++;
147 }
148 return n;
149}
150
151// Cause: "...panic'ed (LoadProhibited)."
152void parse_cause(const char *text, ExcInfo *out)
153{
154 const char *c = strstr(text, "panic'ed (");
155 if (!c)
156 {
157 return;
158 }
159 c += 10;
160 size_t i = 0;
161 while (i < sizeof(out->cause) - 1 && c[i] && c[i] != ')') // range check first (short-circuits the read)
162 {
163 out->cause[i] = c[i];
164 i++;
165 }
166 out->cause[i] = '\0';
167}
168
169// Core number: "Core N ...".
170void parse_core(const char *text, ExcInfo *out)
171{
172 const char *co = strstr(text, "Core ");
173 if (!co)
174 {
175 return;
176 }
177 const char *p = skip_ws(co + 5);
178 if (*p >= '0' && *p <= '9')
179 {
180 out->core = parse_small_int(p); // clamped inside; avoids signed-overflow UB on a huge number
181 }
182}
183
184// EXCVADDR (faulting data address).
185void parse_excvaddr(const char *text, ExcInfo *out)
186{
187 const char *e = strstr(text, "EXCVADDR");
188 if (!e)
189 {
190 return;
191 }
192 const char *colon = strchr(e, ':');
193 if (!colon)
194 {
195 return;
196 }
197 uint32_t v = 0;
198 if (parse_hex(skip_ws(colon + 1), &v))
199 {
200 out->excvaddr = v;
201 out->has_excvaddr = true;
202 }
203}
204
205// Register-dump PC: a line that starts with "PC" (not "EPC..."). Anchor to a line break.
206void parse_pc(const char *text, ExcInfo *out)
207{
208 const char *pcl = (strncmp(text, "PC", 2) == 0) ? text : strstr(text, "\nPC");
209 if (!pcl)
210 {
211 return;
212 }
213 const char *colon = strchr(pcl, ':');
214 if (!colon)
215 {
216 return;
217 }
218 uint32_t v = 0;
219 if (parse_hex(skip_ws(colon + 1), &v))
220 {
221 out->pc = v;
222 }
223}
224
225// Backtrace: "Backtrace: pc:sp pc:sp ...".
226void parse_backtrace(const char *text, ExcInfo *out)
227{
228 const char *bt = strstr(text, "Backtrace:");
229 if (!bt)
230 {
231 return;
232 }
233 const char *p = bt + 10;
234 while (out->frame_count < PC_EXC_MAX_FRAMES)
235 {
236 p = skip_ws(p);
237 uint32_t pc = 0;
238 uint32_t sp = 0;
239 const char *q = parse_hex(p, &pc);
240 if (!q || *q != ':')
241 {
242 break;
243 }
244 const char *r = parse_hex(q + 1, &sp);
245 if (!r)
246 {
247 break;
248 }
249 out->frames[out->frame_count].pc = pc;
250 out->frames[out->frame_count].sp = sp;
251 out->frame_count++;
252 p = r;
253 }
254}
255} // namespace
256
257bool pc_exc_parse(const char *text, ExcInfo *out)
258{
259 if (!text || !out)
260 {
261 return false;
262 }
263 out->core = -1;
264 out->cause[0] = '\0';
265 out->pc = 0;
266 out->excvaddr = 0;
267 out->has_excvaddr = false;
268 out->frame_count = 0;
269
270 parse_cause(text, out);
271 parse_core(text, out);
272 parse_excvaddr(text, out);
273 parse_pc(text, out);
274 parse_backtrace(text, out);
275
276 if (out->pc == 0 && out->frame_count > 0)
277 {
278 out->pc = out->frames[0].pc;
279 }
280
281 return out->cause[0] != '\0' || out->pc != 0 || out->frame_count > 0;
282}
283
284size_t pc_exc_json(const ExcInfo *info, char *out, size_t cap)
285{
286 if (!info || !out || cap == 0)
287 {
288 return 0;
289 }
290 pc_sb b = {out, cap, 0, true};
291 pc_sb_put(&b, "{");
292 bool first = true;
293 if (info->core >= 0)
294 {
295 pc_sb_put(&b, "\"core\":");
296 put_int(&b, info->core);
297 first = false;
298 }
299 if (!first)
300 {
301 pc_sb_put(&b, ",");
302 }
303 pc_sb_put(&b, "\"cause\":");
304 put_json_str(&b, info->cause);
305 pc_sb_put(&b, ",\"pc\":");
306 put_hex32(&b, info->pc);
307 if (info->has_excvaddr)
308 {
309 pc_sb_put(&b, ",\"excvaddr\":");
310 put_hex32(&b, info->excvaddr);
311 }
312 pc_sb_put(&b, ",\"backtrace\":[");
313 for (size_t i = 0; i < info->frame_count; i++)
314 {
315 if (i)
316 {
317 pc_sb_put(&b, ",");
318 }
319 put_hex32(&b, info->frames[i].pc);
320 }
321 pc_sb_put(&b, "]}");
322 if (!b.ok)
323 {
324 return 0;
325 }
326 out[b.len] = '\0';
327 return b.len;
328}
329
330#endif // PC_ENABLE_EXC_DECODER
ESP32 panic / exception decoder for a live diagnostics panel (PC_ENABLE_EXC_DECODER).
Bounded no-heap string builder that fails closed on overflow (one shared copy).
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Definition strbuf.h:60
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:30
char * p
Definition strbuf.h:31
size_t cap
Definition strbuf.h:32
size_t len
Definition strbuf.h:33
bool ok
Definition strbuf.h:34