UNPKG

jlink-mcp

Version:

MCP server for SEGGER J-Link debug probes — LLM-driven embedded debugging with RTT, GDB server, and Trice/Pigweed support

193 lines 9.12 kB
"use strict"; /** * ProbeBackend is the abstraction layer for debug probes. * Each probe type (J-Link, OpenOCD, Black Magic Probe, probe-rs) * implements this interface. The MCP server calls only these methods. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ProbeBackend = void 0; exports.parseLittleEndian32 = parseLittleEndian32; exports.decodeFaultRegisters = decodeFaultRegisters; /** * Abstract base for all debug probe backends. * Implementations only need to override the abstract methods. * Shared utilities (register parsing, fault decoding, memory parsing) * are provided by the base class. */ class ProbeBackend { // ── RTT support (optional - not all probes support this) ───────── /** Whether this probe supports RTT */ supportsRTT() { return false; } /** RTT telnet port when GDB server is running (-1 if not supported) */ getRTTPort() { return -1; } // ══════════════════════════════════════════════════════════════════ // SHARED UTILITIES (used by all backends) // ══════════════════════════════════════════════════════════════════ /** Parse register dump text into structured key-value pairs */ parseRegisters(raw) { const regs = {}; for (const line of raw.split("\n")) { const trimmed = line.trim(); if (!trimmed) continue; // "R0 = 20060050, R1 = 00000000, ..." // "PC = 0000BF54, CycleCnt = 0000855D" // "SP(R13)= 20062880" const simple = /(\w[\w()]*)\s*=\s*([0-9A-Fa-f]{2,8})/g; let match; while ((match = simple.exec(trimmed)) !== null) { let name = match[1]; const value = match[2]; // Normalize SP(R13) → SP const parenMatch = name.match(/^(\w+)\(\w+\)$/); if (parenMatch) name = parenMatch[1]; regs[name] = `0x${value}`; } // "XPSR = 41000000: APSR = nZcvq, ..." const xpsrMatch = trimmed.match(/APSR\s*=\s*(\w+)/); if (xpsrMatch) regs["APSR"] = xpsrMatch[1]; } return Object.keys(regs).length > 0 ? regs : null; } /** Format registers as a compact, LLM-friendly summary */ formatRegistersCompact(regs) { const core = ["PC", "SP", "LR", "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R11", "R12"]; const status = ["XPSR", "CONTROL", "PRIMASK", "BASEPRI", "FAULTMASK"]; const stack = ["MSP", "PSP", "MSPLIM", "PSPLIM"]; const lines = []; const coreVals = core.filter((r) => regs[r]).map((r) => `${r}=${regs[r]}`); if (coreVals.length > 0) lines.push("Core: " + coreVals.join(" ")); const statusVals = status.filter((r) => regs[r]).map((r) => `${r}=${regs[r]}`); if (statusVals.length > 0) lines.push("Status: " + statusVals.join(" ")); const stackVals = stack.filter((r) => regs[r]).map((r) => `${r}=${regs[r]}`); if (stackVals.length > 0) lines.push("Stack: " + stackVals.join(" ")); const fpNonZero = Object.entries(regs) .filter(([k, v]) => k.startsWith("FPS") && v !== "0x00000000") .map(([k, v]) => `${k}=${v}`); if (fpNonZero.length > 0) lines.push("FP (non-zero): " + fpNonZero.join(" ")); return lines.join("\n"); } /** Parse hex dump lines from probe output */ parseMemoryDump(raw) { const results = []; for (const line of raw.split("\n")) { // J-Link format: "E000ED28 = 00 00 00 00 ..." const jlinkMatch = line.match(/^([0-9A-Fa-f]{8})\s*=\s*(.+?)\s{2,}(.*)$/); if (jlinkMatch) { results.push({ address: `0x${jlinkMatch[1]}`, hex: jlinkMatch[2].trim(), ascii: jlinkMatch[3].trim() }); continue; } // OpenOCD / GDB format: "0xe000ed28: 00 00 00 00 ..." const ocdMatch = line.match(/^(0x[0-9a-fA-F]+)\s*:\s*(.+?)(?:\s{2,}(.*))?$/); if (ocdMatch) { results.push({ address: ocdMatch[1], hex: ocdMatch[2].trim(), ascii: (ocdMatch[3] || "").trim() }); } } return results; } /** Read fault registers and decode them (ARM Cortex-M specific) */ async readFaultRegisters() { const result = await this.readMemory(0xE000ED28, 20); const dump = this.parseMemoryDump(result.rawOutput); let cfsr = 0, hfsr = 0, mmfar = 0, bfar = 0; if (dump.length > 0) { const allHex = dump.map((d) => d.hex).join(" "); const bytes = allHex.split(/\s+/).filter(Boolean); if (bytes.length >= 16) { cfsr = parseLittleEndian32(bytes, 0); hfsr = parseLittleEndian32(bytes, 4); mmfar = parseLittleEndian32(bytes, 12); bfar = parseLittleEndian32(bytes, 16); } } return { result, decoded: decodeFaultRegisters(cfsr, hfsr, mmfar, bfar), raw: { cfsr, hfsr, mmfar, bfar } }; } } exports.ProbeBackend = ProbeBackend; // ══════════════════════════════════════════════════════════════════════ // Shared free functions // ══════════════════════════════════════════════════════════════════════ function parseLittleEndian32(bytes, offset) { if (offset + 3 >= bytes.length) return 0; return ((parseInt(bytes[offset], 16)) | (parseInt(bytes[offset + 1], 16) << 8) | (parseInt(bytes[offset + 2], 16) << 16) | (parseInt(bytes[offset + 3], 16) << 24)) >>> 0; } function decodeFaultRegisters(cfsr, hfsr, mmfar, bfar) { const lines = []; const mmfsr = cfsr & 0xFF; const bfsr = (cfsr >> 8) & 0xFF; const ufsr = (cfsr >> 16) & 0xFFFF; if (cfsr === 0 && hfsr === 0) { lines.push("No faults detected (CFSR=0, HFSR=0)"); return lines.join("\n"); } if (mmfsr) { lines.push("## MemManage Fault (MMFSR):"); if (mmfsr & 0x01) lines.push(" - IACCVIOL: Instruction access violation"); if (mmfsr & 0x02) lines.push(" - DACCVIOL: Data access violation"); if (mmfsr & 0x08) lines.push(" - MUNSTKERR: MemManage on unstacking"); if (mmfsr & 0x10) lines.push(" - MSTKERR: MemManage on stacking"); if (mmfsr & 0x20) lines.push(" - MLSPERR: MemManage during FP lazy state preservation"); if (mmfsr & 0x80) lines.push(` - MMARVALID: Faulting address = 0x${mmfar.toString(16).padStart(8, "0")}`); } if (bfsr) { lines.push("## BusFault (BFSR):"); if (bfsr & 0x01) lines.push(" - IBUSERR: Instruction bus error"); if (bfsr & 0x02) lines.push(" - PRECISERR: Precise data bus error"); if (bfsr & 0x04) lines.push(" - IMPRECISERR: Imprecise data bus error"); if (bfsr & 0x08) lines.push(" - UNSTKERR: BusFault on unstacking"); if (bfsr & 0x10) lines.push(" - STKERR: BusFault on stacking"); if (bfsr & 0x20) lines.push(" - LSPERR: BusFault during FP lazy state preservation"); if (bfsr & 0x80) lines.push(` - BFARVALID: Faulting address = 0x${bfar.toString(16).padStart(8, "0")}`); } if (ufsr) { lines.push("## UsageFault (UFSR):"); if (ufsr & 0x0001) lines.push(" - UNDEFINSTR: Undefined instruction"); if (ufsr & 0x0002) lines.push(" - INVSTATE: Invalid state (e.g., Thumb bit)"); if (ufsr & 0x0004) lines.push(" - INVPC: Invalid PC load (bad EXC_RETURN)"); if (ufsr & 0x0008) lines.push(" - NOCP: No coprocessor"); if (ufsr & 0x0010) lines.push(" - STKOF: Stack overflow detected"); if (ufsr & 0x0100) lines.push(" - UNALIGNED: Unaligned memory access"); if (ufsr & 0x0200) lines.push(" - DIVBYZERO: Division by zero"); } if (hfsr) { lines.push("## HardFault (HFSR):"); if (hfsr & 0x02) lines.push(" - VECTTBL: Vector table read fault"); if (hfsr & 0x40000000) lines.push(" - FORCED: Forced HardFault (escalated from configurable fault)"); if (hfsr & 0x80000000) lines.push(" - DEBUGEVT: Debug event triggered HardFault"); } return lines.join("\n"); } //# sourceMappingURL=backend.js.map