embedded-debugging
Skill40 starsChanged 3 months agoMITView on GitHub
Debug embedded firmware on real hardware through the jlink-mcp server — crashes, hangs, peripheral misconfiguration, silent devices. Covers loading ELF symbols for source-level backtraces, supplying an SVD for named peripheral fields, and the halt/read/resume discipline. Use whenever debugging a microcontroller over a J-Link, OpenOCD, or Black Magic probe.
3 other projects use a version of this.
Adopted by
Most recently taken up by
- shangliny10-lab/embedded-skillstheir own version, first seen Oct 20, 2026
- ddddjaak/ae-skillstheir own version, first seen Oct 17, 2026
- ptsilivis/autonomousguytheir own version, first seen Jul 4, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- ptsilivis/autonomousguy★ 33same name
- shangliny10-lab/embedded-skills★ 28same name
- ddddjaak/ae-skills★ 4same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repositoryOne other file it gives its agents.
- CLAUDE.mdCLAUDE.md
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 126
When two channels disagree, that is information, not noise: if read_registers and probe_command { commands: ["halt","regs"] } report …
- Distinctive: line 174
A probe serves one client at a time: If something reports the probe busy, another GDB server or a stray JLinkExe has it — that is a real …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Embedded debugging over a debug probe
You are talking to a physical microcontroller through a debug probe. It is slower, more stateful, and far less forgiving than a process on your machine.
The single rule that matters: the device is the source of truth, and it is cheap to ask it. Nearly every wrong answer in this domain comes from reasoning about what the hardware "should" be doing instead of reading it.
Before you debug anything: two files that change everything
Most sessions are needlessly blind because these were never supplied. Check
get_config at the start and ask for whichever is missing.
The ELF gives you names
Without it you do not merely lose names — you lose the stack. Measured on an
nRF52840, the same halted target, one gdb_load apart:
# no symbols
#0 0x00000048 in ?? ()
# after gdb_load
#0 0x00000048 in rtt_puts (s=s@entry=0x520 "] <") at src/fixture.c:124
#1 0x000001c2 in log_line (level=0x59b "inf", module=0x54a "hil_fixture",
msg=0x58d "fixture ready") at src/fixture.c:158
#2 0x000002fa in main () at src/fixture.c:282One anonymous frame becomes three named ones with arguments and line numbers. GDB cannot unwind past the top frame without the debug info, so "the caller" is not a question you can even ask until the ELF is loaded.
gdb_load { elfFile: "build/zephyr/zephyr.elf" } # symbols only, does not touch flashThis does not reprogram the device — it loads symbols into GDB. Pass
flash: true only when you actually intend to program it. The server halts
the target briefly to run it and puts it back, so you do not have to. Load the ELF that
matches the firmware currently on the target; a stale ELF gives confidently
wrong function names, which is worse than ??.
Once loaded: gdb_backtrace, and gdb_command for info locals,
print myStruct, x/16xw &buffer, and turning a faulting address into a line:
info line *0x2a4
Line 91 of "src/fixture.c" starts at address 0x2a4 <main> and ends at 0x2a8 <main+4>.The SVD gives you meanings
Without it, a peripheral read is a hex word you have to decode by hand against
a 2000-page reference manual. With it, read_peripheral and decode_register
split the value into named fields with their enumerated meanings — ENABLE = 4
(Enabled) instead of 0x00000004.
Set SVD_PATH (standalone) or jlinkMcp.svdPath (VSCode) to a CMSIS-SVD file
for the exact part. Vendors publish them; they are also in CMSIS device family
packs and the cmsis-svd collection. list_peripherals tells you immediately
whether one is loaded.
While you are there: JLINK_RTT_ADDR
Set it to your firmware's _SEGGER_RTT symbol. The probe locates the RTT
control block by scanning RAM and never reports the address back, so without
this RTT cannot be recovered after a reset or a flash — and a stream that
stopped being collected looks exactly like a device that has gone quiet.
Do not guess a part number
set_device needs J-Link's exact name for the chip, and a wrong one fails in
a way that looks like broken hardware rather than a typo. J-Link knows the
list — around 9800 parts — so ask it:
search_devices { query: "stm32f407" }
search_devices { query: "nordic" } # by manufacturer
search_devices { query: "cortex-m33" } # by coreResults carry flash and RAM sizes, which is what separates the variants that
differ by nothing else — an STM32F407IE from an STM32F407IG. Pass the name
back to set_device exactly as written.
The halt rule
Reading a running target is impossible, not slow. The GDB server is a synchronous remote: while the target runs it stops reading commands, and reads are refused rather than queued.
So every inspection is: halt → read → resume. If a read comes back refused, that is not a flaky probe, it is you asking at the wrong moment.
Two traps:
- A core spinning in its fault handler counts as "running." It will refuse
reads exactly like healthy firmware. Halt first, then read — this is why
diagnose_crashhalts before it reads anything. - You cannot hold a core halted while RTT is being collected. The probe collects RTT in stop mode by default: it halts the core, reads the buffer, and starts it again. A core "stopped at the reset vector" will not stay there. Disconnect RTT if the target genuinely has to stay put.
Verify, do not infer
Embedded systems punish assumption. For every belief you form, there is a way to ask the hardware, and it takes one tool call. Some worked examples:
| Belief | How to actually check it |
| "The core is running" | Halt, read a counter, resume, halt, read again. Moving is running. Same PC twice is not proof of a hang — it may be a delay loop. |
| "The reset worked" | Reset, resume, then read a .bss variable that only climbs. A halting reset stops before startup zeroes .bss, so checking without resuming proves nothing. |
| "The device stopped logging" | rtt_status. It reads the control block and separates the two cases: WrOff is the target's write pointer, RdOff the host's. WrOff moving while RdOff sits still means the firmware is fine and the probe stopped collecting. |
| "It's hung" | Check for armed breakpoints. A stale FPB comparator raises a debug event that, with no debugger attached, escalates to HardFault — indistinguishable from a hang. clear_breakpoints and re-run. |
| "That register is 0" | Confirm the read succeeded. A refused read and a genuine zero look identical once the value reaches you. |
| "The firmware on the device is the one I built" | Read the vector table or a known constant and compare against your build. Flashing can silently not have happened. |
When two channels disagree, that is information, not noise: if read_registers
and probe_command { commands: ["halt","regs"] } report different PCs, the core
was moving between the two reads.
Workflows
A crash
diagnose_crash # halts, decodes CFSR/HFSR/DFSR, unwinds the frame
gdb_backtrace # needs the ELF, or this is addresses only
gdb_command { command: "info line *0x<faulting PC>" }
rtt_search { level: "err" } # what the device said before it dieddiagnose_crash reporting no faults is only meaningful if the read succeeded.
It says so explicitly when it could not read — that is not a clean bill of health.
A hang, or "it does nothing"
snapshot # registers + faults + stack + RTT in one callTake it twice, a second apart, and compare the PC. Then rule out, in order:
armed breakpoints (clear_breakpoints), a fault handler loop (PC inside your
HardFault_Handler), and a genuine wait (PC in a delay or a semaphore).
A peripheral behaving oddly
list_peripherals { filter: "uart" }
read_peripheral { peripheral: "UARTE0" }
decode_register { peripheral: "UARTE0", register: "ENABLE" }Compare what the registers say against what the code intended to write. The gap between the two is usually the bug — a clock left off, a pin not routed, a peripheral enabled before it was configured.
A device that boots and then goes silent
Distinguish three cases before theorising: the firmware stopped, the probe stopped collecting, or the log level filters it.
rtt_status # separates "quiet device" from "probe not collecting"
snapshot # is the PC still moving?
rtt_search {} # is it logging below your filter?Session hygiene
start_debug_sessionis one call for GDB server + RTT + boot log. Prefer it over assembling the pieces.- A probe serves one client at a time. If something reports the probe busy, another GDB server or a stray JLinkExe has it — that is a real constraint, not a transient error to retry blindly.
- Clear breakpoints before you leave. A comparator left armed outlives your session and will fault the next firmware that happens to execute that address, which is a genuinely horrible bug to inherit.
- Tools report failure in their reply text. Read it. A tool that says "Reset failed: ..." has not reset anything, whatever you do next.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

