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
JavaScript
;
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