Skip to content

Commit 65cef93

Browse files
committed
initial release — panicdump v1
- Fault capture: HardFault/MemManage/BusFault/UsageFault via assembly stubs - 192-byte fixed binary format, CRC-32 protected, magic-last atomicity - Retained RAM (.noinit) backend, survives warm reset - UART hex-framed export - Python offline decoder with CFSR bit decoding and JSON output - Mock dump generator — testable without hardware - 50 host tests, CI via GitHub Actions
1 parent 128d9b9 commit 65cef93

4 files changed

Lines changed: 95 additions & 29 deletions

File tree

.github/workflows/ci.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
9+
jobs:
10+
test:
11+
name: Host tests (Python)
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.11"
21+
22+
- name: Install pytest
23+
run: pip install pytest
24+
25+
- name: Generate test vectors
26+
run: |
27+
cd tools
28+
python3 make_mock_dump.py --all
29+
30+
- name: Run tests
31+
run: |
32+
PYTHONPATH=tools python3 -m pytest tests/ -v --tb=short
33+
34+
lint:
35+
name: Python lint
36+
runs-on: ubuntu-latest
37+
38+
steps:
39+
- uses: actions/checkout@v4
40+
41+
- name: Set up Python
42+
uses: actions/setup-python@v5
43+
with:
44+
python-version: "3.11"
45+
46+
- name: Install ruff
47+
run: pip install ruff
48+
49+
- name: Lint tools/
50+
run: ruff check tools/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# panicdump
22

3+
![CI](https://github.com/tvoj-username/panicdump/actions/workflows/ci.yml/badge.svg)
4+
35
**Tiny crash dump library for bare-metal Cortex-M MCUs.**
46

57
When your MCU faults, panicdump captures the minimal crash context, stores it in

tools/decode_panicdump.py

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import zlib
1919
import re
2020
from dataclasses import dataclass, asdict
21-
from typing import Optional
2221

2322
# ---------------------------------------------------------------------------
2423
# Constants — must match panicdump.h
@@ -162,34 +161,50 @@ def parse(raw: bytes) -> PanicDump:
162161
# CFSR decoder — helps explain what caused the fault
163162
# ---------------------------------------------------------------------------
164163

164+
_UFSR_BITS = [
165+
(9, "DIVBYZERO"),
166+
(8, "UNALIGNED"),
167+
(3, "NOCP (coprocessor fault)"),
168+
(2, "INVPC (invalid PC load)"),
169+
(1, "INVSTATE (invalid state)"),
170+
(0, "UNDEFINSTR (undefined instruction)"),
171+
]
172+
173+
_BFSR_BITS = [
174+
(7, "BFARVALID (BFAR holds address)"),
175+
(5, "LSPERR (FP lazy stack)"),
176+
(4, "STKERR (stack push fault)"),
177+
(3, "UNSTKERR (stack pop fault)"),
178+
(2, "IMPRECISERR (imprecise data bus)"),
179+
(1, "PRECISERR (precise data bus)"),
180+
(0, "IBUSERR (instruction bus fault)"),
181+
]
182+
183+
_MMFSR_BITS = [
184+
(7, "MMARVALID (MMFAR holds address)"),
185+
(5, "MLSPERR (FP lazy stack MPU)"),
186+
(4, "MSTKERR (stack push MPU)"),
187+
(3, "MUNSTKERR (stack pop MPU)"),
188+
(1, "DACCVIOL (data access violation)"),
189+
(0, "IACCVIOL (instruction access violation)"),
190+
]
191+
192+
165193
def decode_cfsr(cfsr: int) -> list[str]:
166-
bits = []
167-
# UFSR (bits 31:16)
168-
ufsr = (cfsr >> 16) & 0xFFFF
169-
if ufsr & (1 << 9): bits.append("DIVBYZERO")
170-
if ufsr & (1 << 8): bits.append("UNALIGNED")
171-
if ufsr & (1 << 3): bits.append("NOCP (coprocessor fault)")
172-
if ufsr & (1 << 2): bits.append("INVPC (invalid PC load)")
173-
if ufsr & (1 << 1): bits.append("INVSTATE (invalid state)")
174-
if ufsr & (1 << 0): bits.append("UNDEFINSTR (undefined instruction)")
175-
# BFSR (bits 15:8)
176-
bfsr = (cfsr >> 8) & 0xFF
177-
if bfsr & (1 << 7): bits.append("BFARVALID (BFAR holds address)")
178-
if bfsr & (1 << 5): bits.append("LSPERR (FP lazy stack)")
179-
if bfsr & (1 << 4): bits.append("STKERR (stack push fault)")
180-
if bfsr & (1 << 3): bits.append("UNSTKERR (stack pop fault)")
181-
if bfsr & (1 << 2): bits.append("IMPRECISERR (imprecise data bus)")
182-
if bfsr & (1 << 1): bits.append("PRECISERR (precise data bus)")
183-
if bfsr & (1 << 0): bits.append("IBUSERR (instruction bus fault)")
184-
# MMFSR (bits 7:0)
185-
mmfsr = cfsr & 0xFF
186-
if mmfsr & (1 << 7): bits.append("MMARVALID (MMFAR holds address)")
187-
if mmfsr & (1 << 5): bits.append("MLSPERR (FP lazy stack MPU)")
188-
if mmfsr & (1 << 4): bits.append("MSTKERR (stack push MPU)")
189-
if mmfsr & (1 << 3): bits.append("MUNSTKERR (stack pop MPU)")
190-
if mmfsr & (1 << 1): bits.append("DACCVIOL (data access violation)")
191-
if mmfsr & (1 << 0): bits.append("IACCVIOL (instruction access violation)")
192-
return bits
194+
ufsr = (cfsr >> 16) & 0xFFFF
195+
bfsr = (cfsr >> 8) & 0xFF
196+
mmfsr = cfsr & 0xFF
197+
result = []
198+
for bit, name in _UFSR_BITS:
199+
if ufsr & (1 << bit):
200+
result.append(name)
201+
for bit, name in _BFSR_BITS:
202+
if bfsr & (1 << bit):
203+
result.append(name)
204+
for bit, name in _MMFSR_BITS:
205+
if mmfsr & (1 << bit):
206+
result.append(name)
207+
return result
193208

194209
# ---------------------------------------------------------------------------
195210
# Report rendering

tools/make_mock_dump.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import argparse
2020
import struct
21-
import sys
2221
import zlib
2322
from pathlib import Path
2423

0 commit comments

Comments
 (0)