export interface GDBResponse { success: boolean; output: string; /** If the target stopped, why (breakpoint, signal, exit, etc.) */ stopReason?: string; error?: string; } /** * Persistent GDB client that connects to a running GDB server. * Maintains a long-lived arm-none-eabi-gdb process and sends commands * via stdin, reading responses from stdout. * * This bridges the gap between GDB's interactive model and MCP's * request/response model. Each command blocks until GDB produces * a complete response or times out. */ export declare class GDBClient { private proc; private gdbPath; private connected; private outputBuffer; /** * The command currently awaiting its MI result record. * * GDB/MI echoes the token you prefix a command with on that command's * result record (`11 info registers` -> `11^error,msg="..."`), which is the * only reliable way to tell one command's response from another's. Matching * on a bare `(gdb)` prompt or any `^done` does not work: GDB prints a prompt * during startup, before a single command has been sent, so the first * command resolves against that stale prompt with an empty buffer and every * subsequent reply is off by one. * * Observed on hardware as every GDB-routed tool returning empty output while * the server reported itself healthy. */ private pending; private tokenCounter; /** * Serializes `command()` calls. * * There is one `pending` slot and one output buffer, so two commands in * flight at once clobber each other: the second overwrites the first's * resolver, and the first only ever settles on its own timeout, with * whatever happened to be in the buffer. MCP tool calls can genuinely * overlap — and the probe backend's own lock does not cover the GDB path, * which returns before `withPreflight` is ever reached. */ private queue; /** * True once a run command has started the target and no stop has been seen. * * While the target runs, GDB is blocked inside its resume loop and does not * read stdin at all — the J-Link GDB Server does not support asynchronous * remote execution, so `set mi-async on` does not change this. Any command * sent in that window is never processed and simply times out. * * Observed on hardware: after one `continue`, every subsequent command sat * for exactly the 10s timeout, and the GDB server logged nothing after * "Starting target CPU...". */ private targetRunning; private stopEvent; private history; private maxHistory; /** Saved connection params for auto-reconnect */ private lastConnectParams; /** Minimum delay between commands to avoid overwhelming slow adapters */ private lastCommandTime; private commandThrottleMs; constructor(gdbPath?: string); /** * Patterns in GDB output that indicate the remote target has gone away * even though the child GDB process is still alive. When we see one of * these in a command response, we invalidate `connected` so the next * call takes the auto-reconnect path. * * Keep these narrow — a fuzzy match here causes false positives on * informational output and produces spurious reconnects. */ private static readonly REMOTE_LOSS_PATTERNS; /** * Start GDB and connect to a remote target (GDB server). * * Fast-path: if the child GDB process is up and we haven't observed * remote-loss on a previous command, treat the session as live. We do * NOT actively ping — a probe here would compete with in-flight * asynchronous MI events (stop notifications, register updates) and * could time out on a perfectly healthy session, causing a needless * teardown. If the session is actually stale, the next real command's * response will match one of REMOTE_LOSS_PATTERNS and trigger the * auto-reconnect path in `command()`. */ connect(host?: string, port?: number, elfFile?: string): Promise; /** * Send a GDB command and wait for the response. * * For commands that cause the target to run (continue, step, next, until, finish), * this will wait up to `timeout` ms for the target to stop. * If the target doesn't stop in time, returns with a "target running" message. */ command(cmd: string, timeout?: number): Promise; private commandUnqueued; /** * Wait for the target to stop (after a continue/step that timed out). * Call this to poll after gdb_command returned "target running". */ wait(timeout?: number): Promise; /** Load an ELF file for symbol-aware debugging */ loadSymbols(elfPath: string): Promise; /** Get a backtrace */ backtrace(full?: boolean): Promise; /** List threads (useful for RTOS debugging) */ listThreads(): Promise; /** Read a C variable by name (requires debug symbols) */ readVariable(name: string): Promise; /** Get recent command history */ getHistory(count?: number): string[]; /** * Stop a running target. * * Sends SIGINT to the GDB process rather than a command on stdin. With a * synchronous remote — which is what the J-Link GDB Server gives us — GDB * blocks in its resume loop while the target executes and does not read * stdin, so `interrupt` and `monitor halt` typed as commands are never seen. * SIGINT is the only channel GDB is still listening on, and it forwards the * interrupt to the remote. */ interrupt(timeout?: number): Promise; /** Check if connected */ isConnected(): boolean; /** Disconnect and kill GDB process */ disconnect(): void; private handleOutput; /** Hand the accumulated buffer to whoever is waiting and clear the slot. */ private settlePending; private formatStopReason; private sendCommand; /** Clean GDB/MI output into human-readable text */ private cleanMI; } //# sourceMappingURL=gdb-client.d.ts.map