UNPKG

jlink-mcp

Version:

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

124 lines 5.48 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BlackMagicBackend = void 0; const child_process_1 = require("child_process"); const backend_1 = require("./backend"); const logger_1 = require("../utils/logger"); /** * Black Magic Probe backend. * BMP is unique: it has a built-in GDB server on a serial port. * We interact with it by running arm-none-eabi-gdb with commands. * No separate GDB server process is needed. */ class BlackMagicBackend extends backend_1.ProbeBackend { type = "blackmagic"; displayName = "Black Magic Probe"; config; processManager; constructor(config, processManager) { super(); this.processManager = processManager; this.config = { gdbPath: config.gdbPath || "arm-none-eabi-gdb", serialPort: config.serialPort || "/dev/ttyACM0", targetIndex: config.targetIndex || 1, gdbPort: config.gdbPort || 2331, }; } /** Execute GDB commands against the Black Magic Probe */ async gdbExec(gdbCommands) { const fullCommands = [ `target extended-remote ${this.config.serialPort}`, "monitor version", `monitor swdp_scan`, `attach ${this.config.targetIndex}`, ...gdbCommands, "detach", "quit", ]; // Write commands to a temp batch const batchContent = fullCommands.join("\n"); const args = [ "--batch", "--nx", "-ex", `target extended-remote ${this.config.serialPort}`, "-ex", `monitor swdp_scan`, "-ex", `attach ${this.config.targetIndex}`, ]; for (const cmd of gdbCommands) { args.push("-ex", cmd); } args.push("-ex", "detach", "-ex", "quit"); (0, logger_1.log)(`[BMP] ${gdbCommands.join("; ")}`); return new Promise((resolve) => { const proc = (0, child_process_1.spawn)(this.config.gdbPath, args, { stdio: ["pipe", "pipe", "pipe"] }); let stdout = "", stderr = ""; proc.stdout?.on("data", (d) => { stdout += d.toString(); }); proc.stderr?.on("data", (d) => { stderr += d.toString(); }); proc.on("error", (err) => { resolve({ success: false, rawOutput: stdout, output: stdout, error: `Failed to spawn GDB: ${err.message}` }); }); proc.on("exit", (code) => { resolve({ success: code === 0, rawOutput: stdout, output: stdout, error: stderr || undefined }); }); setTimeout(() => { proc.kill("SIGTERM"); resolve({ success: false, rawOutput: stdout, output: stdout, error: "GDB timed out" }); }, 30000); }); } // ── ProbeBackend implementation ────────────────────────────────── async getDeviceInfo() { return this.gdbExec(["info target", "info registers"]); } async halt() { return this.gdbExec(["monitor halt"]); } async resume() { return this.gdbExec(["continue &"]); } async reset(halt = false) { return this.gdbExec([halt ? "monitor reset halt" : "monitor reset"]); } async step() { return this.gdbExec(["stepi"]); } async readMemory(address, length) { const wordCount = Math.ceil(length / 4); return this.gdbExec([`x/${wordCount}xw 0x${address.toString(16)}`]); } async writeMemory(address, value) { return this.gdbExec([`set *(unsigned int *)0x${address.toString(16)} = 0x${value.toString(16)}`]); } async readAllRegisters() { return this.gdbExec(["info registers"]); } async readRegister(name) { return this.gdbExec([`info register ${name}`]); } async flash(filePath, baseAddress) { const loadCmd = baseAddress !== undefined ? `load ${filePath} 0x${baseAddress.toString(16)}` : `load ${filePath}`; return this.gdbExec([loadCmd, "compare-sections"]); } async erase() { return this.gdbExec(["monitor erase_mass"]); } async setBreakpoint(address) { return this.gdbExec([`hbreak *0x${address.toString(16)}`]); } async clearBreakpoints() { return this.gdbExec(["delete breakpoints"]); } async executeRaw(commands) { return this.gdbExec(commands); } // ── GDB Server (BMP has built-in GDB server on serial port) ────── async startGDBServer() { // BMP doesn't need a separate GDB server - it IS the GDB server return { success: true, message: `BMP GDB server is built-in at ${this.config.serialPort}` }; } stopGDBServer() { return { success: true, message: "BMP GDB server is built-in (nothing to stop)" }; } isGDBServerRunning() { // BMP is always "running" if the serial port exists return true; } getGDBServerStatus() { return { running: true, gdbPort: 0, rttTelnetPort: -1 }; } getGDBServerOutput(_lines = 50) { return ["BMP uses built-in GDB server on serial port"]; } supportsRTT() { return false; } isDeviceConfigured() { return !!this.config.serialPort; } getDeviceName() { return this.config.serialPort; } setDevice(device) { this.config.serialPort = device; } async listDevices() { return this.gdbExec(["monitor swdp_scan"]); } dispose() { } } exports.BlackMagicBackend = BlackMagicBackend; //# sourceMappingURL=blackmagic.js.map