UNPKG

jlink-mcp

Version:

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

403 lines 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.stripBoilerplate = stripBoilerplate; exports.parseRegisters = parseRegisters; exports.formatRegistersCompact = formatRegistersCompact; exports.parseMemoryDump = parseMemoryDump; exports.decodeFaultRegisters = decodeFaultRegisters; exports.executeJLinkCommands = executeJLinkCommands; exports.getDeviceInfo = getDeviceInfo; exports.haltDevice = haltDevice; exports.resumeDevice = resumeDevice; exports.resetDevice = resetDevice; exports.readMemory = readMemory; exports.writeMemory = writeMemory; exports.readRegister = readRegister; exports.readAllRegisters = readAllRegisters; exports.flashFirmware = flashFirmware; exports.eraseChip = eraseChip; exports.setBreakpoint = setBreakpoint; exports.clearBreakpoints = clearBreakpoints; exports.stepInstruction = stepInstruction; exports.executeRawCommands = executeRawCommands; exports.readFaultRegisters = readFaultRegisters; const child_process_1 = require("child_process"); const config_1 = require("../utils/config"); const logger_1 = require("../utils/logger"); // Lines that are JLink connection boilerplate - strip these for clean output const BOILERPLATE_PATTERNS = [ /^SEGGER J-Link Commander/, /^DLL version/, /^J-Link Commander will now exit/, /^Connecting to J-Link via USB/, /^Firmware: J-Link/, /^Hardware version:/, /^J-Link uptime/, /^S\/N:/, /^License\(s\):/, /^USB speed mode:/, /^VTref=/, /^Device ".*" selected/, /^Connecting to target via SWD/, /^Connecting to target via JTAG/, /^ConfigTargetSettings\(\)/, /^InitTarget\(\)/, /^Found SW-DP with ID/, /^DPIDR:/, /^CoreSight/, /^AP map detection/, /^AP\[\d+\]:/, /^CPUID register:/, /^Feature set:/, /^Cache:/, /^Found Cortex-/, /^FPUnit:/, /^Security extension: /, /^Secure debug:/, /^ROMTbl\[\d+\]/, /^\[\d+\]\[\d+\]:/, /^Memory zones:/, /^\s+Zone:/, /^Cortex-M\d+ identified/, /^Type "connect"/, /^Please specify/, /^Specify target/, /^$/, // blank lines /^J-Link>/, // prompt /^J-Link\[\d+\]:/, /^Syntax:/, /^Sleep\(\d+\)/, /^Script processing completed/, ]; /** Strip JLink connection boilerplate from output, returning only meaningful data */ function stripBoilerplate(raw) { const lines = raw.split("\n"); const meaningful = []; for (const line of lines) { const trimmed = line.trim(); if (!trimmed) continue; const isBoilerplate = BOILERPLATE_PATTERNS.some((p) => p.test(trimmed)); if (!isBoilerplate) { meaningful.push(line); } } return meaningful.join("\n").trim(); } /** Parse register dump into structured JSON */ function parseRegisters(raw) { const regs = {}; const regPatterns = [ // "PC = 0000BF54, CycleCnt = 0000855D" /(\w+)\s*=\s*([0-9A-Fa-f]{2,8})(?:,|\s|$)/g, // "SP(R13)= 20062880" /(\w+)\((\w+)\)\s*=\s*([0-9A-Fa-f]{2,8})/g, ]; // Match standard register lines for (const line of raw.split("\n")) { const trimmed = line.trim(); // Skip non-register lines if (!trimmed || trimmed.startsWith("SEGGER") || trimmed.startsWith("Connecting")) continue; // "R0 = 20060050, R1 = 00000000, ..." let match; const simple = /(\w[\w()]*)\s*=\s*([0-9A-Fa-f]{2,8})/g; 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 */ function 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 = []; // Core registers const coreVals = core.filter((r) => regs[r]).map((r) => `${r}=${regs[r]}`); lines.push("Core: " + coreVals.join(" ")); // Status const statusVals = status.filter((r) => regs[r]).map((r) => `${r}=${regs[r]}`); if (statusVals.length > 0) lines.push("Status: " + statusVals.join(" ")); // Stack const stackVals = stack.filter((r) => regs[r]).map((r) => `${r}=${regs[r]}`); if (stackVals.length > 0) lines.push("Stack: " + stackVals.join(" ")); // FP registers - only show non-zero ones 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 memory dump lines into hex string */ function parseMemoryDump(raw) { const results = []; for (const line of raw.split("\n")) { // "E000ED28 = 00 00 00 00 00 00 00 00 01 00 00 00 74 28 06 20 ............t(. " const match = line.match(/^([0-9A-Fa-f]{8})\s*=\s*(.+?)\s{2,}(.*)$/); if (match) { results.push({ address: `0x${match[1]}`, hex: match[2].trim(), ascii: match[3].trim(), }); } } return results; } /** Decode ARM Cortex-M fault status registers */ function decodeFaultRegisters(cfsr, hfsr, mmfar, bfar) { const lines = []; // CFSR = UFSR (bits 16-31) | BFSR (bits 8-15) | MMFSR (bits 0-7) 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"); } // MemManage faults 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 (exception return)"); if (mmfsr & 0x10) lines.push(" - MSTKERR: MemManage on stacking (exception entry)"); 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")}`); } } // BusFault 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")}`); } } // UsageFault 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"); } // HardFault if (hfsr) { lines.push("## HardFault (HFSR):"); if (hfsr & 0x02) lines.push(" - VECTTBL: Vector table read fault on exception processing"); 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"); } /** * Executes J-Link Commander commands by spawning JLinkExe with a script. * Each call opens a new connection, runs the commands, and exits. */ async function executeJLinkCommands(commands, deviceOverride) { const config = (0, config_1.getConfig)(); const jlinkExe = (0, config_1.getJLinkExePath)(config.jlink); const device = deviceOverride || config.jlink.device; const scriptLines = [...commands, "exit"]; const args = [ "-device", device, "-if", config.jlink.interface, "-speed", String(config.jlink.speed), "-autoconnect", "1", "-ExitOnError", "1", "-NoGui", "1", ]; if (config.jlink.serialNumber) { args.push("-SelectEmuBySN", config.jlink.serialNumber); } (0, logger_1.log)(`JLink Commander: ${commands.join("; ")}`); return new Promise((resolve) => { const proc = (0, child_process_1.spawn)(jlinkExe, args, { stdio: ["pipe", "pipe", "pipe"], }); let stdout = ""; let stderr = ""; proc.stdout?.on("data", (data) => { stdout += data.toString(); }); proc.stderr?.on("data", (data) => { stderr += data.toString(); }); const script = scriptLines.join("\n") + "\n"; proc.stdin?.write(script); proc.stdin?.end(); proc.on("error", (err) => { (0, logger_1.logError)("JLink Commander spawn error", err); resolve({ success: false, rawOutput: stdout, output: stdout, error: `Failed to spawn JLinkExe: ${err.message}. Is J-Link installed at ${config.jlink.installDir}?`, }); }); proc.on("exit", (code) => { const success = code === 0; if (!success) { (0, logger_1.logError)(`JLink Commander exited with code ${code}`); } resolve({ success, rawOutput: stdout, output: stripBoilerplate(stdout), error: stderr || undefined, }); }); setTimeout(() => { proc.kill("SIGTERM"); resolve({ success: false, rawOutput: stdout, output: stripBoilerplate(stdout), error: "JLink Commander timed out after 30 seconds", }); }, 30000); }); } /** Connect and get device info - returns a compact summary */ async function getDeviceInfo() { return executeJLinkCommands(["halt", "regs"]); } /** Halt the CPU */ async function haltDevice() { return executeJLinkCommands(["halt"]); } /** Resume (go) the CPU */ async function resumeDevice() { return executeJLinkCommands(["go"]); } /** Reset the device */ async function resetDevice(halt = false) { return executeJLinkCommands(halt ? ["r", "halt"] : ["r", "go"]); } /** Read memory at address */ async function readMemory(address, numBytes) { const addrHex = `0x${address.toString(16)}`; return executeJLinkCommands([`mem ${addrHex}, ${numBytes}`]); } /** Write memory (32-bit words) */ async function writeMemory(address, value) { const addrHex = `0x${address.toString(16)}`; const valHex = `0x${value.toString(16)}`; return executeJLinkCommands([`w4 ${addrHex}, ${valHex}`]); } /** Read a CPU register by name */ async function readRegister(register) { return executeJLinkCommands(["halt", `rreg ${register}`]); } /** Read all registers */ async function readAllRegisters() { return executeJLinkCommands(["halt", "regs"]); } /** Flash a firmware file */ async function flashFirmware(filePath, baseAddress) { const addr = baseAddress !== undefined ? ` 0x${baseAddress.toString(16)}` : ""; return executeJLinkCommands([ "r", "halt", `loadfile ${filePath}${addr}`, "r", "go", ]); } /** Erase the chip */ async function eraseChip() { return executeJLinkCommands(["erase"]); } /** Set a hardware breakpoint */ async function setBreakpoint(address) { return executeJLinkCommands([`SetBP 0x${address.toString(16)}`]); } /** Clear all breakpoints */ async function clearBreakpoints() { return executeJLinkCommands(["ClrBP"]); } /** Step one instruction */ async function stepInstruction() { return executeJLinkCommands(["halt", "s"]); } /** Execute arbitrary J-Link commands */ async function executeRawCommands(rawCommands) { return executeJLinkCommands(rawCommands); } /** Read fault registers and decode them */ async function readFaultRegisters() { // Read CFSR, HFSR, MMFAR, BFAR in one shot (16 bytes from 0xE000ED28) const result = await executeJLinkCommands(["halt", "mem 0xE000ED28, 20"]); const dump = parseMemoryDump(result.rawOutput); let cfsr = 0, hfsr = 0, mmfar = 0, bfar = 0; // Parse the hex bytes from the memory dump if (dump.length > 0) { const allHex = dump.map((d) => d.hex).join(" "); const bytes = allHex.split(/\s+/).filter(Boolean); if (bytes.length >= 16) { // Little-endian: CFSR at offset 0, HFSR at 4, (DFSR at 8), MMFAR at 12, BFAR at 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 }, }; } 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; } //# sourceMappingURL=commander.js.map