Skip to main content

target-debug

Use when firmware misbehaves on real hardware and its own state must explain it — a device that wedges, STALLs, NAKs forever, drops data, an endpoint stuck busy, a HardFault, a stuck ISR, a spinning core. Evidence over a debug probe: logs, GDB autopsy, RAM trace, PC-sampling, SWO.

来源信息

仓库
hathach/agentrc
最近来源活动
2026年10月13日 08:44
检测到的 SKILL.md 语言
英语
星标
1
分支
2

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
11 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
target-debug
description
Use when firmware misbehaves on real hardware and its own state must explain it — a device that wedges, STALLs, NAKs forever, drops data, an endpoint stuck busy, a HardFault, a stuck ISR, a spinning core. Evidence over a debug probe: logs, GDB autopsy, RAM trace, PC-sampling, SWO.
# target-debug — target-side capture & debugging over a debug probe The **target** is the MCU running the code under test. The methods here work for any firmware on a debug probe; the worked cases are USB stacks (device role, host role, or both), where the link peer is not always a Linux PC: an MCU host may face another board or a Linux gadget (e.g. a Raspberry Pi). ## Which skill answers what | Skill | Answers | Needs | |----------------------|------------------------------------------------------------------|---------------------------------------------------------| | **`target-debug`** | **what the target did** (driver state, faults, where the core spins) | a debug probe on the target | | `rtt` | the target's log/console stream, and a post-mortem ring dump | a probe, firmware that logs over SEGGER RTT | | `esp-target-debug` | the same methods on Espressif's built-in USB-JTAG | an ESP32-S3/P4 — read it FIRST there: other gdb, other openocd | | `etm-trace` | exactly which instructions ran (profile, coverage, history) | a SEGGER J-Trace wired to this board; follow `etm-trace`'s confirmation rule | | `usb-sniffer` | what crossed the wire (PIDs, handshakes, resets) | the hardware tap cabled in; role-agnostic | | a project's `sysview` | where CPU time goes: task/ISR schedule, per-context load, switch and ready→run timing, or what ran right before a crash/hang | the project ships the skill; a J-Link/J-Trace for live capture, or an OpenOCD probe for raw RTT capture | | `usb-kernel-debug` | what a Linux host exchanged (usbmon URBs) and why its kernel acted (dynamic debug) | Linux on either end: PC host or gadget peer | ## What the project supplies These skills know probes, not projects. Three things come from the project, and none of them is ever guessed: - **How to build a debug variant** (logging over RTT, trace init, where the ELF lands, the J-Link device name or OpenOCD config of a board): the project's `Build contract:` line in its `CLAUDE.md` names the skill or document that says. No contract: use the ELF and device/config the caller gave, and ask for what is missing. - **How to take a rig board**: the project's `HIL contract:` line names its rig skill, which owns lock and release and says which HIL config json describes the host you are on. No contract: follow the procedure the caller gave; on a shared rig with none, ask before touching a board. - **What its symbols and symptoms mean**: kept here, in `projects/`, for the projects this skill has been used on — see "Project notes" at the end. A HIL config json maps board → probe, and `rtt.py` and `pc_sample.py` read it directly (`--hil-config <file> --board <name>`; a flag given as well must agree): | `flasher` field | Means | Becomes | |---|---|---| | `name` | how the rig flashes: `jlink`, `openocd`, `stlink` are probe routes; `esptool`, `lm4flash` are refused: they name no debug probe | the backend (`stlink` → openocd, and then `--cfg` is yours to give) | | `uid` | the probe's serial | `--probe` | | `args` | jlink: exactly `-device <name>`; openocd: its `-f`/`-c` arguments | `--device`, or `--cfg` whole | | `vid_pid` | the probe model's USB IDs, openocd only | `--vid-pid` | A J-Link entry supplies neither interface nor speed. On the J-Link route `--interface` and `--speed` stay explicit; on the OpenOCD route they live inside `--cfg` and the two flags are refused. `pc_sample.py` takes J-Link entries only; `probe_state.py` reads only the HIL config and takes `jlink` and `openocd` entries. `etm_capture.py` does not read it — a J-Trace is one probe moved between boards, named with `--probe`. ## Delegated sessions When an agent dispatches this work (chief's `hw-validator` and `hw-debugger` units), the prompt is the scope and these rules bind the unit; its role file adds only what it may edit and when it stops. - **Scope.** Stay within the prompt's board, operations and source paths; resolve the build and access through the project's contracts or the ELF, device/config and procedure it supplies. The dispatch supplies hardware task scope; no human grant or verbatim exchange is needed for its hardware operations. Missing technical inputs or scope return `blocked` with what the caller must supply. Use `needs-user` for human-only action or a permission still required for publishing, destructive actions on data or third-party access, instead of waiting for an answer. Establish the physical setup a technique needs before using it. If wiring cannot be established from current evidence or the supplied setup information, name the missing setup and proposed experiment in `next`: for ETM, follow `etm-trace`'s per-board wiring rule. - **Identity.** Verify worktree, branch and HEAD, then board, probe serial and host against the prompt, or the probe against the HIL config entry it names, before acting. Resolve the board's family and debug backend from the project's metadata before choosing probe tools, never from the board name or its flashing backend. - **Lock.** Ordinary hardware sessions hold the project's board lock for the whole hardware phase; a refused hold waits within the lock-wait budget, then returns `blocked` with the holder. Forced-lock recovery is a separate, explicitly scoped operation naming the holder, affected resources and recovery procedure; a refusal alone never adds that scope, and it never includes stopping the CI runner. A reproducer that takes the lock itself (tinyusb's `hil_test.py`) never runs under your hold, and its guard is bypassed only under that scoped forced-lock recovery. Reuse a supplied HIL reproduction while its firmware, configuration, reproducer and relevant rig inputs match; otherwise resolve equivalent manual steps through the HIL contract, and if there are none return `blocked` naming the exact HIL reproduction needed, so the caller arranges it through the project's HIL role. A later manual session re-establishes firmware identity and state and never treats a snapshot taken after that handoff as the earlier failure state without evidence. - **Builds.** Through the build contract into a private build directory, leaving firmware staged for HIL untouched. Before the first hardware change, save the restoration artifact apart from later build outputs and pin it: its revision, configuration and hash, recorded as `cleanup.pristine`; cleanup restores that identity, and later commits do not move it. When the project's HIL contract defines a cleanup state instead of a restoration firmware, record that state there. Tracked paths a build may rewrite are in the prompt's temporary scope, or reported before building. - **Instrumentation.** Only the listed paths, from a clean checkout, uncommitted, each diff saved as a patch in the artifact directory. From a previous unit's dirty state (a recovery prompt): inspect the recorded patch and current changes first, restore only what is attributable to it, report ambiguous ownership untouched. - **Observation.** The prompt's observation window is the ceiling on one reproducer attempt; the criterion states the required operations or minimum exposure, and a completed deterministic operation ends the attempt. Every reproducer invocation, including smoke checks, cleanup checks that invoke it and failed or truncated captures, is accounted for in `runs` and counts toward the repetition ceiling; a state read or run-state observation that does not invoke the reproducer costs time, not a repetition. A `runs` row aggregates identical attempts and splits where purpose, firmware, conditions or outcome differ. An early decisive observation may settle the claim; otherwise a shortened capture is partial evidence and cannot establish a pass that needs the full exposure. Anchor every state reading with the backend's validity check (Cortex-M: DHCSR) and record whether attaching halted or reset the target; a post-reset snapshot is never the failure state. - **Host.** Leave the host as found: never clear kernel logs (`dmesg -C`) or other shared history, and restore every setting you change (dynamic debug, module parameters). - **Budget.** Stop starting experiments when the remaining time cannot cover the experiment plus cleanup; never cut a restoration short for the deadline, report the overrun. - **Cleanup, before releasing the lock.** Stop owned capture and debug clients before opening the restoration flasher or running the contract's cleanup, dispose of the source as your role says, establish the pinned restoration image on the board (retain the programmed image when verification with the backend's procedure establishes that it matches the pinned artifact and nothing since changed its contents or made them uncertain, citing that verification; otherwise program the pinned artifact and verify its programmed contents, the artifact hash recorded apart from that result) or, when `cleanup.pristine` records the HIL contract's cleanup state, run the contract's cleanup instead, verified only by the run-state observation below; remove owned breakpoints and watchpoints, restore the firmware's expected run state and record an observation that establishes it (a verified flash alone does not; for a USB device, the expected function, such as an application exchange; a claim that needs re-enumeration records the enumeration events, and a device number alone establishes neither, since the host may reuse it across a reset), close any flasher you opened, restore host settings, release the lock. `restorationFlashes` counts actual programming attempts, a contract cleanup's write included. Run state not established is a failed cleanup. Do only the cleanup your own actions or the explicitly assigned recovery state call for, establishing ownership before restoring a predecessor's state. Blocked before touching hardware with no recovery state assigned, the untouched actions are `n-a`, nothing is flashed and another holder's lock stays. If restoration cannot complete, keep the lock where you can, report the exact remaining source, firmware, process and lock state, and do not call the board ready. - **Return.** The tested firmware identity, decisive observations, artifact locations, cleanup state and budget used; sampling done by hand on a native probe comes back with its command, and the limits name only what was actually unavailable. ## Rig discipline — lock first, always On a shared rig, an ordinary session holds the project's board lock for the WHOLE manual session (instrument, build, flash, capture, GDB), forcing it only under a scoped forced-lock recovery (Delegated sessions' Lock), and never stops its CI runner. Rigs and benches run many identical probes: - Select the probe by serial: J-Link `-SelectEmuBySN <uid>`, its GDB server `-select usb=<uid>`, OpenOCD `-c 'adapter serial <uid>'`. - Run on the host that owns the probe. - One probe serves one client: quit JLinkExe before starting JLinkGDBServer on the same probe. ## Pick the least intrusive technique that can answer the question Observation can mask the bug: one can change behavior under logging *and* under the debugger. If the bug disappears when instrumented, that IS a finding (timing-sensitive): move down in intrusiveness, not up. | Technique | Intrusiveness | Reach for it when | |------------------------------------|--------------------------------|---------------------------------------------------------| | PC-sampling | none — no halt, no code change | core wedged/spinning somewhere unknown | | SWO exception trace / hw PC-sample | none — needs SWO pin wired | ISR ordering/timing with zero code change | | DWT data trace | none — needs SWO pin wired | stream one address's accesses: value + accessor PC | | Vector catch | none until a fault fires | crash-shaped wedges — autopsy AT the faulting pc | | RAM ring-buffer | ~tens of cycles per event | ISR ordering/timing bugs | | SystemView (project's `sysview`) | recorder in the firmware, per event | scheduling-shaped bugs: throughput jitter, a task starved or preempted, an ISR too long — not logic/state bugs | | Log lines (RTT) | µs per line | logic bugs that survive logging (J-Link or OpenOCD rtt) | | Log lines (UART) | ms per line — blocking write | same, when no debug-probe RTT path | | dprintf / conditional breakpoint | halt+resume per hit (~ms) | low-rate probes post-wedge; never ISR-rate events | | GDB halt / breakpoints | stops USB service entirely | post-mortem state autopsy once wedged | Each technique's recipe, read when you reach for it (files beside this one): - PC-sampling: `techniques.md`, "PC-sampling" — `pc_sample.py`, reading the histogram, native probes by hand. - Probe-state snapshot (DHCSR anchor, PCSR samples, image read-back for Cleanup's verification): `techniques.md`, "Probe state" — `probe_state.py`. - SWO exception trace, hardware PC-sampling, DWT data trace: `swo-dwt.md`. - Vector catch and the fault registers: `fault-autopsy.md`. - RAM ring-buffer: `techniques.md`, "RAM ring-buffer trace" — the ring code, the `nm` check, the GDB dump. - SystemView: the project's `sysview` skill. - Log lines (RTT, UART): `techniques.md`, "Log capture" — capture commands, the drain model; the channel itself is the `rtt` skill's. - Chip and family constraints (flash reads, reset domains, probe misreads): `families.md`. - dprintf, GDB halt and breakpoints: `gdb.md` — GDB servers, OpenOCD batch sessions, the hardware breakpoint budget, watchpoints, FreeRTOS threads, J-Link Commander quirks. - USB enumeration or transfer bugs: capture both sides at once by default — `techniques.md`, "USB: dual-side capture". - Manuals (J-Link UM08001, OpenOCD, GDB): `techniques.md`, "Manuals". ## Warnings - **Halting via the probe does NOT disconnect the device**: a halt resets no peripheral, so the USB controller keeps its pull-up, the host's stuck URBs stay stuck and a wedged DUT stays wedged — recover the Linux host side (the rig's own procedure; its HIL contract names it). A probe reset disconnects only when the reset it issues reaches the USB controller or its PHY: check the part (`families.md`: DWC2, STM32F4). - **A bug that vanishes under logging is a timing bug**, not fixed: switch to the ring buffer; if it vanishes under GDB too, PC-sampling only. - **UART logging blocks in the write path** (worst perturbation, including inside the ISR); RTT is much cheaper but not free; verbose logging multiplies both. - **`pkill -f PATTERN` matches its own caller**: the pattern is in your shell's (or ssh's) command line, so it kills the command running it. Bracket one character (`pkill -f '[J]LinkGDBServer'`) or kill by PID. - Flash/GDB only with the board lock held; a hold refused because CI is mid-test on that board means wait, and force only under an explicitly scoped forced-lock recovery (Delegated sessions' Lock). - **Instrumentation is temporary**: before `release`, restore the pinned restoration firmware or cleanup state as Delegated sessions' Cleanup says (the next CI run must not inherit a debug build) and revert the instrumentation diff. Handing the diff over with the diagnosis preserves the evidence; it does not count as cleanup. - **A register snapshot without a validity anchor lies**: J-Link tool sessions can reset or briefly halt the DUT as a side effect, and a snapshot of a freshly-reset chip (e.g. NVIC ISER = 0) reads like a smoking gun. Read DHCSR (0xE000EDF0: bit 17 S_HALT, bit 25 S_RESET_ST) with every snapshot, and cross-check against something the device demonstrably still does. - **"Flash OK" can lie** (silent no-op — old firmware keeps running). When behavior contradicts the flashed code: `objcopy -O binary fw.elf /tmp/fw.bin`, then `verifybin /tmp/fw.bin,<flash-base>` (J-Link, verified) or `verify_image` (OpenOCD); on mismatch reflash before debugging further. Some targets need a particular halt state before flash reads (RP2040: a flash-resident halt); `families.md` has them. - **A marginal link can fake a deterministic firmware bug** — down to failing the same test at the same iteration twice. "USB disconnect" in dmesg on a freshly re-cabled port (high devnum = churn) means the plug, not the code: first sustained bulk traffic is when a bad contact drops. Before declaring a regression, re-run the OLD build on the SAME link state — and if a bisect exonerates every hunk, believe it: re-test the exact failing binary. ## Project notes What is specific to the projects these skills have been used on, one file each, for all six skills: their symbols, typical issues and the behaviour of their code, releases and CI firmware; a project's build, rig and battery procedure stays in its own contracts. Read it with `families.md`'s section for the board's chip, when it has one. Match the project by its repository identity (`git remote get-url origin`), not by a directory name: | Repository | Notes | |---|---| | `hathach/tinyusb` | `projects/tinyusb.md` | | `adafruit/tinyuf2` | `projects/tinyuf2.md` | Every entry there says how it is known (verified when, on which revision and board, how — or only carried over). Check a symbol exists in the checkout in front of you before relying on it. When a session establishes something reusable — reproduced, or read in a source — add it with the same evidence, or correct the entry it contradicts, where it holds: only for this project, its file here; for any firmware on the chip, `families.md` (Espressif: `esp-target-debug`); for a probe or tool, the technique file (RTT and trace per-board caveats: `rtt/boards.md`, `etm-trace/boards.md`). Leave out the session's chronology and dead ends. A project with no file: work from its source, and start one when there is something worth keeping.
在 GitHub 查看