agentleFS
KlievanKlievan/jlink-mcp/skills/embedded-debugging/SKILL.md

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

  • Other versions3 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 3

    1. ptsilivis/autonomousguy★ 33same name
    2. shangliny10-lab/embedded-skills★ 28same name
    3. 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.

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
  1. Very specific:

    When two channels disagree, that is information, not noise: if read_registers and probe_command { commands: ["halt","regs"] } report …

    line 126
  2. Distinctive:

    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 …

    line 174

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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:282

One 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 flash

This 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 core

Results 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_crash halts 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:

BeliefHow 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 died

diagnose_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 call

Take 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_session is 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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.