@dbalster/gdb-plugin 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/gdb.js ADDED
@@ -0,0 +1,1709 @@
1
+ // MIT License
2
+ //
3
+ // Copyright (c) 2026 Daniel Balster
4
+ //
5
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ // of this software and associated documentation files (the "Software"), to deal
7
+ // in the Software without restriction, including without limitation the rights
8
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ // copies of the Software, and to permit persons to whom the Software is
10
+ // furnished to do so, subject to the following conditions:
11
+ //
12
+ // The above copyright notice and this permission notice shall be included in all
13
+ // copies or substantial portions of the Software.
14
+ //
15
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ // SOFTWARE.
22
+ import { tool } from "@kilocode/plugin";
23
+ import { spawn } from "node:child_process";
24
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
25
+ import { join } from "node:path";
26
+ import { createInterface } from "node:readline";
27
+ // ─── State ───────────────────────────────────────────────────────────────────
28
+ const STATE_DIR = "/tmp/kilo-gdb";
29
+ const STATE_FILE = join(STATE_DIR, "state.json");
30
+ function saveState(s) {
31
+ if (!existsSync(STATE_DIR))
32
+ mkdirSync(STATE_DIR, { recursive: true });
33
+ writeFileSync(STATE_FILE, JSON.stringify(s, null, 2));
34
+ }
35
+ function loadState() {
36
+ try {
37
+ if (existsSync(STATE_FILE))
38
+ return JSON.parse(readFileSync(STATE_FILE, "utf-8"));
39
+ }
40
+ catch { /* ignore */ }
41
+ return {
42
+ status: "not_started",
43
+ breakpoints: [],
44
+ threads: [],
45
+ varObjs: [],
46
+ token: 0,
47
+ };
48
+ }
49
+ function freshState() {
50
+ return {
51
+ status: "stopped",
52
+ breakpoints: [],
53
+ threads: [],
54
+ varObjs: [],
55
+ token: 0,
56
+ };
57
+ }
58
+ const TOKEN_CHAR_MAP = {
59
+ "{": "lbrace", "}": "rbrace",
60
+ "[": "lbracket", "]": "rbracket",
61
+ ",": "comma", "=": "equals",
62
+ };
63
+ function miTokenize(input) {
64
+ const tokens = [];
65
+ let i = 0;
66
+ while (i < input.length) {
67
+ const ch = input[i];
68
+ if (ch in TOKEN_CHAR_MAP) {
69
+ tokens.push({ type: TOKEN_CHAR_MAP[ch] });
70
+ i++;
71
+ }
72
+ else if (ch === '"') {
73
+ i++;
74
+ let val = "";
75
+ while (i < input.length && input[i] !== '"') {
76
+ if (input[i] === "\\" && i + 1 < input.length) {
77
+ const esc = input[i + 1];
78
+ if (esc === "n")
79
+ val += "\n";
80
+ else if (esc === "t")
81
+ val += "\t";
82
+ else if (esc === "r")
83
+ val += "\r";
84
+ else if (esc === '"')
85
+ val += '"';
86
+ else if (esc === "\\")
87
+ val += "\\";
88
+ else
89
+ val += esc;
90
+ i += 2;
91
+ }
92
+ else {
93
+ val += input[i];
94
+ i++;
95
+ }
96
+ }
97
+ if (i < input.length)
98
+ i++;
99
+ tokens.push({ type: "string", value: val });
100
+ }
101
+ else if (ch === "\n") {
102
+ tokens.push({ type: "nl" });
103
+ i++;
104
+ }
105
+ else if (ch === " " || ch === "\t" || ch === "\r") {
106
+ i++;
107
+ }
108
+ else {
109
+ let val = "";
110
+ while (i < input.length && !"{}[]=, \"\t\n\r".includes(input[i])) {
111
+ val += input[i];
112
+ i++;
113
+ }
114
+ tokens.push({ type: "string", value: val });
115
+ }
116
+ }
117
+ tokens.push({ type: "eof" });
118
+ return tokens;
119
+ }
120
+ function miParseValue(tokens, idx) {
121
+ const t = tokens[idx.i];
122
+ idx.i++;
123
+ if (!t || t.type === "eof")
124
+ return "";
125
+ if (t.type === "string")
126
+ return t.value;
127
+ if (t.type === "lbrace") {
128
+ const obj = {};
129
+ while (idx.i < tokens.length) {
130
+ const tok = tokens[idx.i];
131
+ if (tok.type === "rbrace") {
132
+ idx.i++;
133
+ break;
134
+ }
135
+ if (tok.type === "string") {
136
+ const key = tok.value;
137
+ idx.i++;
138
+ const eq = tokens[idx.i];
139
+ if (eq && eq.type === "equals") {
140
+ idx.i++;
141
+ obj[key] = miParseValue(tokens, idx);
142
+ const comma = tokens[idx.i];
143
+ if (comma && comma.type === "comma")
144
+ idx.i++;
145
+ else if (comma && comma.type === "rbrace")
146
+ continue;
147
+ else
148
+ continue;
149
+ }
150
+ else
151
+ continue;
152
+ }
153
+ else {
154
+ idx.i++;
155
+ }
156
+ }
157
+ return obj;
158
+ }
159
+ if (t.type === "lbracket") {
160
+ const arr = [];
161
+ while (idx.i < tokens.length) {
162
+ const tok = tokens[idx.i];
163
+ if (tok.type === "rbracket") {
164
+ idx.i++;
165
+ break;
166
+ }
167
+ if (tok.type === "lbrace") {
168
+ arr.push(miParseValue(tokens, idx));
169
+ const comma = tokens[idx.i];
170
+ if (comma && comma.type === "comma")
171
+ idx.i++;
172
+ }
173
+ else if (tok.type === "lbracket") {
174
+ arr.push(miParseValue(tokens, idx));
175
+ const comma = tokens[idx.i];
176
+ if (comma && comma.type === "comma")
177
+ idx.i++;
178
+ }
179
+ else if (tok.type === "string") {
180
+ const label = tok.value;
181
+ idx.i++;
182
+ const eq = tokens[idx.i];
183
+ if (eq && eq.type === "equals") {
184
+ idx.i++;
185
+ arr.push(miParseValue(tokens, idx));
186
+ }
187
+ else {
188
+ arr.push(label);
189
+ }
190
+ const comma = tokens[idx.i];
191
+ if (comma && comma.type === "comma")
192
+ idx.i++;
193
+ }
194
+ else {
195
+ idx.i++;
196
+ }
197
+ }
198
+ return arr;
199
+ }
200
+ return "";
201
+ }
202
+ function miParseEntry(input) {
203
+ const tokens = miTokenize(input.trim());
204
+ const obj = {};
205
+ const idx = { i: 0 };
206
+ while (idx.i < tokens.length) {
207
+ const tok = tokens[idx.i];
208
+ if (tok.type === "eof")
209
+ break;
210
+ if (tok.type === "string") {
211
+ const key = tok.value;
212
+ idx.i++;
213
+ const eq = tokens[idx.i];
214
+ if (eq && eq.type === "equals") {
215
+ idx.i++;
216
+ obj[key] = miParseValue(tokens, idx);
217
+ const comma = tokens[idx.i];
218
+ if (comma && comma.type === "comma")
219
+ idx.i++;
220
+ else if (comma && comma.type === "eof")
221
+ break;
222
+ }
223
+ else {
224
+ idx.i++;
225
+ }
226
+ }
227
+ else {
228
+ idx.i++;
229
+ }
230
+ }
231
+ return obj;
232
+ }
233
+ function miFlattenValue(v, indent = "") {
234
+ if (typeof v === "string")
235
+ return v;
236
+ if (Array.isArray(v)) {
237
+ if (v.length === 0)
238
+ return "[]";
239
+ return v.map((e, i) => {
240
+ if (typeof e === "string")
241
+ return `[${i}]: ${e}`;
242
+ if (Array.isArray(e))
243
+ return `[${i}]: ${miFlattenValue(e, indent + " ")}`;
244
+ return `[${i}]: ${miFlattenTuple(e, indent + " ")}`;
245
+ }).join("\n" + indent);
246
+ }
247
+ return miFlattenTuple(v, indent);
248
+ }
249
+ function miFlattenTuple(t, indent = "") {
250
+ return Object.entries(t).map(([k, v]) => {
251
+ if (typeof v === "string")
252
+ return `${indent}${k}: ${v}`;
253
+ if (Array.isArray(v))
254
+ return `${indent}${k}: [${v.length} items]`;
255
+ return `${indent}${k}: {${Object.keys(v).length} fields}`;
256
+ }).join("\n");
257
+ }
258
+ class GdbManager {
259
+ proc = null;
260
+ state;
261
+ pending = null;
262
+ cmdQueue = [];
263
+ cmdBusy = false;
264
+ lines = [];
265
+ records = [];
266
+ promptSeen = false;
267
+ constructor() {
268
+ this.state = loadState();
269
+ }
270
+ getState() {
271
+ return this.state;
272
+ }
273
+ isRunning() {
274
+ return this.proc !== null && this.proc.exitCode === null;
275
+ }
276
+ async start(binary, gdbArgs, cwd) {
277
+ this.forceKill();
278
+ this.state = freshState();
279
+ this.state.binary = binary || this.state.binary;
280
+ this.state.cwd = cwd || this.state.cwd;
281
+ this.records = [];
282
+ this.lines = [];
283
+ this.promptSeen = false;
284
+ this.pending = null;
285
+ this.cmdQueue = [];
286
+ this.cmdBusy = true;
287
+ const args = ["--interpreter=mi3", "--quiet"];
288
+ if (gdbArgs)
289
+ args.push(...gdbArgs.split(/\s+/));
290
+ if (binary)
291
+ args.push(binary);
292
+ this.proc = spawn("gdb", args, {
293
+ cwd: cwd || undefined,
294
+ stdio: ["pipe", "pipe", "pipe"],
295
+ });
296
+ const rl = createInterface({ input: this.proc.stdout });
297
+ const errChunks = [];
298
+ let stderrSize = 0;
299
+ const MAX_STDERR = 102400;
300
+ this.proc.stderr.on("data", (chunk) => {
301
+ const s = chunk.toString();
302
+ stderrSize += s.length;
303
+ if (stderrSize <= MAX_STDERR)
304
+ errChunks.push(s);
305
+ });
306
+ rl.on("line", (line) => this.handleLine(line));
307
+ this.proc.on("exit", (code) => {
308
+ if (!this.pending)
309
+ return;
310
+ if (this.pending.timer)
311
+ clearTimeout(this.pending.timer);
312
+ this.pending.reject(new Error(`GDB exited with code ${code}`));
313
+ this.pending = null;
314
+ this.proc = null;
315
+ this.state.status = "exited";
316
+ saveState(this.state);
317
+ });
318
+ await this.waitForPrompt(10000);
319
+ await this.rawCommand("-gdb-set pagination off");
320
+ await this.rawCommand("-gdb-set print pretty on");
321
+ await this.rawCommand("-gdb-set print thread-events on");
322
+ const out = errChunks.join("");
323
+ const info = [
324
+ this.state.binary ? `GDB loaded: ${this.state.binary}` : "GDB started (no binary)",
325
+ out ? `stderr: ${out}` : "",
326
+ ].filter(Boolean).join("\n");
327
+ saveState(this.state);
328
+ this.cmdBusy = false;
329
+ return info;
330
+ }
331
+ forceKill() {
332
+ if (this.pending) {
333
+ clearTimeout(this.pending.timer);
334
+ this.pending = null;
335
+ }
336
+ if (this.proc) {
337
+ try {
338
+ this.proc.stdin?.write("-gdb-exit\n");
339
+ }
340
+ catch { /* ignore */ }
341
+ try {
342
+ this.proc.kill("SIGKILL");
343
+ }
344
+ catch { /* ignore */ }
345
+ this.proc = null;
346
+ }
347
+ this.state.status = "not_started";
348
+ this.state.threads = [];
349
+ this.state.varObjs = [];
350
+ this.state.lastStopReason = undefined;
351
+ this.state.lastSignal = undefined;
352
+ this.lines = [];
353
+ this.records = [];
354
+ this.promptSeen = false;
355
+ this.pending = null;
356
+ this.cmdBusy = false;
357
+ for (const q of this.cmdQueue) {
358
+ q.resolve({
359
+ token: this.state.token, command: q.cmd, raw: "GDB killed",
360
+ resultClass: "error", resultData: { msg: "GDB process was killed" },
361
+ records: [], lines: [],
362
+ });
363
+ }
364
+ this.cmdQueue = [];
365
+ saveState(this.state);
366
+ }
367
+ stop() {
368
+ if (this.proc) {
369
+ try {
370
+ this.proc.stdin?.write("-gdb-exit\n");
371
+ }
372
+ catch { /* ignore */ }
373
+ try {
374
+ this.proc.kill("SIGKILL");
375
+ }
376
+ catch { /* ignore */ }
377
+ this.proc = null;
378
+ }
379
+ this.state.status = "not_started";
380
+ this.state.threads = [];
381
+ this.state.varObjs = [];
382
+ this.state.lastStopReason = undefined;
383
+ this.state.lastSignal = undefined;
384
+ this.pending = null;
385
+ saveState(this.state);
386
+ return "GDB terminated";
387
+ }
388
+ ensureAlive() {
389
+ if (!this.proc)
390
+ throw new Error("GDB is not running. Call gdbInit first.");
391
+ if (this.proc.exitCode !== null) {
392
+ this.state.status = "exited";
393
+ saveState(this.state);
394
+ throw new Error(`GDB process exited with code ${this.proc.exitCode}. Call gdbInit to restart.`);
395
+ }
396
+ }
397
+ requireStopped() {
398
+ if (this.state.status === "running") {
399
+ throw new Error("Program is currently running. Interrupt it (gdbInterrupt) before using this command.");
400
+ }
401
+ if (this.state.status === "exited" || this.state.status === "not_started") {
402
+ throw new Error(`Target program is ${this.state.status}. Start it with gdbRun first.`);
403
+ }
404
+ }
405
+ drainStaleResponse() {
406
+ if (this.pending) {
407
+ clearTimeout(this.pending.timer);
408
+ this.pending = null;
409
+ }
410
+ }
411
+ interrupt() {
412
+ if (!this.proc)
413
+ throw new Error("GDB not running");
414
+ this.drainStaleResponse();
415
+ this.lines = [];
416
+ this.records = [];
417
+ this.promptSeen = false;
418
+ return new Promise((resolve, reject) => {
419
+ const timer = setTimeout(() => {
420
+ if (this.pending === p) {
421
+ this.pending = null;
422
+ reject(new Error("GDB interrupt timed out."));
423
+ }
424
+ }, 10000);
425
+ const p = { resolve, reject, token: -1, command: "interrupt", timer };
426
+ this.pending = p;
427
+ for (const q of this.cmdQueue) {
428
+ q.resolve({
429
+ token: this.state.token, command: q.cmd, raw: "Interrupted by gdbInterrupt",
430
+ resultClass: "error", resultData: { msg: "Interrupted" },
431
+ records: [], lines: [],
432
+ });
433
+ }
434
+ this.cmdQueue = [];
435
+ this.cmdBusy = false;
436
+ this.proc.kill("SIGINT");
437
+ });
438
+ }
439
+ async command(cmd, needsStopped = false, timeout = 30000) {
440
+ try {
441
+ this.ensureAlive();
442
+ if (needsStopped)
443
+ this.requireStopped();
444
+ }
445
+ catch (e) {
446
+ const msg = e instanceof Error ? e.message : String(e);
447
+ return { token: this.state.token, command: cmd, raw: msg, resultClass: "error", resultData: { msg }, records: [], lines: [] };
448
+ }
449
+ return new Promise((resolve, reject) => {
450
+ this.cmdQueue.push({ cmd, needsStopped, timeout, resolve, reject });
451
+ this.processQueue();
452
+ });
453
+ }
454
+ async processQueue() {
455
+ if (this.cmdBusy || this.cmdQueue.length === 0)
456
+ return;
457
+ this.cmdBusy = true;
458
+ const item = this.cmdQueue.shift();
459
+ try {
460
+ this.drainStaleResponse();
461
+ this.promptSeen = false;
462
+ const result = await this.rawCommand(item.cmd, item.timeout);
463
+ item.resolve(result);
464
+ }
465
+ catch (e) {
466
+ const msg = e instanceof Error ? e.message : String(e);
467
+ item.resolve({ token: this.state.token, command: item.cmd, raw: msg, resultClass: "error", resultData: { msg }, records: [], lines: [] });
468
+ }
469
+ finally {
470
+ this.cmdBusy = false;
471
+ this.processQueue();
472
+ }
473
+ }
474
+ async rawCommand(cmd, timeoutMs = 30000) {
475
+ if (!this.proc || !this.proc.stdin)
476
+ throw new Error("GDB not running.");
477
+ const token = ++this.state.token;
478
+ const fullCmd = `${token}${cmd}\n`;
479
+ this.lines = [];
480
+ this.records = [];
481
+ this.promptSeen = false;
482
+ return new Promise((resolve, reject) => {
483
+ const timer = setTimeout(() => {
484
+ if (this.pending === p) {
485
+ this.pending = null;
486
+ const hint = this.state.status === "running"
487
+ ? " (target program is still running — interrupt it first)"
488
+ : "";
489
+ reject(new Error(`GDB command timed out after ${timeoutMs}ms: ${cmd}${hint}`));
490
+ }
491
+ }, timeoutMs);
492
+ const p = { resolve, reject, token, command: cmd, timer };
493
+ this.pending = p;
494
+ this.proc.stdin.write(fullCmd);
495
+ });
496
+ }
497
+ async waitForPrompt(timeout = 30000) {
498
+ const start = Date.now();
499
+ while (!this.promptSeen && this.proc?.exitCode === null) {
500
+ if (Date.now() - start > timeout)
501
+ throw new Error("Timeout waiting for GDB prompt");
502
+ await new Promise((r) => setTimeout(r, 10));
503
+ }
504
+ }
505
+ handleLine(line) {
506
+ this.lines.push(line);
507
+ if (line.startsWith("(gdb)")) {
508
+ this.promptSeen = true;
509
+ this.records.push({ type: "prompt" });
510
+ if (this.pending) {
511
+ clearTimeout(this.pending.timer);
512
+ const p = this.pending;
513
+ this.pending = null;
514
+ const resultClass = this.findResultClass();
515
+ const resultData = this.findResultData();
516
+ const resp = {
517
+ token: p.token,
518
+ command: p.command,
519
+ raw: this.lines.join("\n"),
520
+ resultClass,
521
+ resultData,
522
+ records: [...this.records],
523
+ lines: [...this.lines],
524
+ };
525
+ this.updateStateFromRecords(resp);
526
+ p.resolve(resp);
527
+ }
528
+ return;
529
+ }
530
+ const resultMatch = line.match(/^(\d+)\^(done|running|error|connected|exit)((?:,.+)*)$/);
531
+ if (resultMatch) {
532
+ const token = parseInt(resultMatch[1]);
533
+ const cls = resultMatch[2];
534
+ const rest = resultMatch[3];
535
+ const data = rest ? miParseEntry(rest.startsWith(",") ? rest.slice(1) : rest) : {};
536
+ this.records.push({ type: "result", token, recordClass: cls, data });
537
+ return;
538
+ }
539
+ const execMatch = line.match(/^\*(\w+)((?:,.+)*)$/);
540
+ if (execMatch) {
541
+ const cls = execMatch[1];
542
+ const rest = execMatch[2];
543
+ const data = rest ? miParseEntry(rest.startsWith(",") ? rest.slice(1) : rest) : {};
544
+ this.records.push({ type: "exec", recordClass: cls, data });
545
+ if (cls === "stopped") {
546
+ this.state.lastStopReason = data.reason || "";
547
+ this.state.lastSignal = data.signal || data.signalName || "";
548
+ this.state.lastStopFrame = data.frame || {};
549
+ if (this.state.lastStopReason === "exited" || this.state.lastStopReason === "exited-signalled") {
550
+ this.state.status = "exited";
551
+ }
552
+ else if (this.state.lastStopReason === "signal-received") {
553
+ this.state.status = "signal";
554
+ }
555
+ else {
556
+ this.state.status = "paused";
557
+ }
558
+ if (data["thread-id"]) {
559
+ this.state.currentThreadId = parseInt(data["thread-id"]);
560
+ }
561
+ this.state.currentFrameLevel = 0;
562
+ saveState(this.state);
563
+ }
564
+ else if (cls === "running") {
565
+ this.state.status = "running";
566
+ saveState(this.state);
567
+ }
568
+ return;
569
+ }
570
+ const notifyMatch = line.match(/^=(\w+)((?:,.+)*)$/);
571
+ if (notifyMatch) {
572
+ const cls = notifyMatch[1];
573
+ const rest = notifyMatch[2];
574
+ const data = rest ? miParseEntry(rest.startsWith(",") ? rest.slice(1) : rest) : {};
575
+ this.records.push({ type: "notify", recordClass: cls, data });
576
+ if (cls === "breakpoint-modified" || cls === "breakpoint-created" || cls === "breakpoint-deleted") {
577
+ this.syncBreakpoints();
578
+ }
579
+ return;
580
+ }
581
+ const streamMatch = line.match(/^([~@&])"(.*)"\s*$/s);
582
+ if (streamMatch) {
583
+ const typeMap = { "~": "console", "@": "target", "&": "log" };
584
+ const text = streamMatch[2].replace(/\\(.)/g, (_, c) => c === "n" ? "\n" : c === "t" ? "\t" : c === '"' ? '"' : c === "\\" ? "\\" : c);
585
+ this.records.push({ type: typeMap[streamMatch[1]], text });
586
+ return;
587
+ }
588
+ }
589
+ findResultClass() {
590
+ for (const r of this.records) {
591
+ if (r.type === "result" && r.token === this.state.token)
592
+ return r.recordClass || "unknown";
593
+ }
594
+ return "unknown";
595
+ }
596
+ findResultData() {
597
+ for (const r of this.records) {
598
+ if (r.type === "result" && r.token === this.state.token)
599
+ return r.data || {};
600
+ }
601
+ return {};
602
+ }
603
+ updateStateFromRecords(resp) {
604
+ for (const r of resp.records) {
605
+ if (r.type === "exec" && r.recordClass === "stopped") {
606
+ const d = r.data || {};
607
+ this.state.lastStopReason = d.reason || "";
608
+ this.state.lastSignal = d.signal || d.signalName || "";
609
+ this.state.lastStopFrame = d.frame || {};
610
+ if (this.state.lastStopReason === "exited") {
611
+ this.state.status = "exited";
612
+ }
613
+ else if (this.state.lastStopReason === "signal-received") {
614
+ this.state.status = "signal";
615
+ }
616
+ else if (this.state.lastStopReason?.includes("breakpoint") ||
617
+ this.state.lastStopReason === "function-finished" ||
618
+ this.state.lastStopReason === "end-stepping-range" ||
619
+ this.state.lastStopReason === "watchpoint") {
620
+ this.state.status = "paused";
621
+ }
622
+ else {
623
+ this.state.status = "crashed";
624
+ }
625
+ if (d["thread-id"]) {
626
+ this.state.currentThreadId = parseInt(d["thread-id"]);
627
+ }
628
+ this.state.currentFrameLevel = 0;
629
+ }
630
+ if (r.type === "exec" && r.recordClass === "running") {
631
+ this.state.status = "running";
632
+ }
633
+ if (r.type === "result" && r.token === this.state.token) {
634
+ if (r.recordClass === "running")
635
+ this.state.status = "running";
636
+ if (r.recordClass === "error") {
637
+ const msg = r.data?.msg || "";
638
+ if (msg.includes("No such process") || msg.includes(" exited")) {
639
+ this.state.status = "exited";
640
+ }
641
+ }
642
+ }
643
+ }
644
+ saveState(this.state);
645
+ }
646
+ addVarObj(info) {
647
+ this.state.varObjs.push(info);
648
+ saveState(this.state);
649
+ }
650
+ removeVarObj(name) {
651
+ this.state.varObjs = this.state.varObjs.filter((v) => v.name !== name);
652
+ saveState(this.state);
653
+ }
654
+ setCurrentThread(id) {
655
+ this.state.currentThreadId = id;
656
+ this.state.currentFrameLevel = 0;
657
+ saveState(this.state);
658
+ }
659
+ setCurrentFrame(level) {
660
+ this.state.currentFrameLevel = level;
661
+ saveState(this.state);
662
+ }
663
+ setArgs(args) {
664
+ this.state.args = args;
665
+ saveState(this.state);
666
+ }
667
+ async syncBreakpoints() {
668
+ try {
669
+ const r = await this.command("-break-list", false);
670
+ if (r.resultClass === "done" && r.resultData?.BreakpointTable) {
671
+ const table = r.resultData.BreakpointTable;
672
+ const body = table.body;
673
+ if (Array.isArray(body)) {
674
+ this.state.breakpoints = body
675
+ .filter((b) => typeof b === "object" && !Array.isArray(b) && b !== null)
676
+ .map((e) => ({
677
+ number: parseInt(e.number || "0"),
678
+ type: e.type || "",
679
+ enabled: e.enabled === "y",
680
+ func: e.func,
681
+ file: e.file,
682
+ fullname: e.fullname,
683
+ line: e.line ? parseInt(e.line) : undefined,
684
+ addr: e.addr,
685
+ pending: e.pending === "y",
686
+ times: parseInt(e.times || "0"),
687
+ condition: e.cond,
688
+ ignore: e.ignore ? parseInt(e.ignore) : undefined,
689
+ what: e.what,
690
+ at: e.at,
691
+ }));
692
+ }
693
+ }
694
+ }
695
+ catch { /* ignore */ }
696
+ saveState(this.state);
697
+ }
698
+ async syncThreads() {
699
+ try {
700
+ const r = await this.command("-thread-info", false);
701
+ if (r.resultClass === "done" && r.resultData?.threads) {
702
+ const threads = r.resultData.threads;
703
+ if (Array.isArray(threads)) {
704
+ this.state.threads = threads.map((t) => {
705
+ const e = t;
706
+ return {
707
+ id: parseInt(e.id || "0"),
708
+ targetId: e.targetId || e["target-id"] || "",
709
+ state: e.state || "",
710
+ frame: e.frame ? this.parseFrameInfo(e.frame) : undefined,
711
+ };
712
+ });
713
+ if (r.resultData["current-thread-id"]) {
714
+ this.state.currentThreadId = parseInt(r.resultData["current-thread-id"]);
715
+ }
716
+ }
717
+ }
718
+ }
719
+ catch { /* ignore */ }
720
+ saveState(this.state);
721
+ }
722
+ parseFrameInfo(f) {
723
+ return {
724
+ level: f.level ? parseInt(f.level) : 0,
725
+ func: f.func || "",
726
+ file: f.file,
727
+ fullname: f.fullname,
728
+ line: f.line ? parseInt(f.line) : undefined,
729
+ addr: f.addr,
730
+ from: f.from,
731
+ };
732
+ }
733
+ getConsoleOutput() {
734
+ return this.records
735
+ .filter((r) => (r.type === "console" || r.type === "target" || r.type === "log") && r.text !== undefined)
736
+ .map((r) => r.text)
737
+ .join("");
738
+ }
739
+ formatResponse(resp) {
740
+ const parts = [];
741
+ const consoleOut = this.getConsoleOutput();
742
+ if (resp.resultClass === "error") {
743
+ const msg = resp.resultData?.msg || "";
744
+ const logMsg = resp.records
745
+ .filter((r) => r.type === "log" && r.text !== undefined)
746
+ .map((r) => r.text)
747
+ .join("")
748
+ .trim();
749
+ const errText = msg || consoleOut.trim() || logMsg || "unknown error";
750
+ parts.push(`ERROR: ${errText}`);
751
+ }
752
+ if (consoleOut && resp.resultClass !== "error") {
753
+ parts.push(consoleOut);
754
+ }
755
+ return parts.join("\n") || `OK (${resp.resultClass})`;
756
+ }
757
+ formatError(err) {
758
+ return `GDB Error: ${err instanceof Error ? err.message : String(err)}`;
759
+ }
760
+ formatStoppedInfo(r, s) {
761
+ const out = [];
762
+ if (s.lastStopReason)
763
+ out.push(`Reason: ${s.lastStopReason}`);
764
+ if (s.lastSignal)
765
+ out.push(`Signal: ${s.lastSignal}`);
766
+ if (s.lastStopFrame) {
767
+ const f = s.lastStopFrame;
768
+ const func = f.func || "?";
769
+ const file = f.file || f.fullname || "";
770
+ const line = f.line || "";
771
+ if (file)
772
+ out.push(`At: ${func} (${file}:${line})`);
773
+ else
774
+ out.push(`At: ${func}`);
775
+ }
776
+ for (const rec of r.records) {
777
+ if (rec.type === "console" && rec.text)
778
+ out.push(rec.text.replace(/\n$/, ""));
779
+ }
780
+ if (out.length === 0)
781
+ out.push(`Result: ${r.resultClass}`);
782
+ return out;
783
+ }
784
+ }
785
+ // ─── Plugin ───────────────────────────────────────────────────────────────────
786
+ const manager = new GdbManager();
787
+ export default async function gdbPlugin(input) {
788
+ const { $, worktree } = input;
789
+ const $cwd = $.nothrow().cwd(worktree);
790
+ return {
791
+ tool: {
792
+ gdbInit: tool({
793
+ description: "Start GDB with a binary executable. Must be called before any other gdb command.",
794
+ args: {
795
+ binary: tool.schema.string().optional().describe("Path to the executable to debug"),
796
+ gdbArgs: tool.schema.string().optional().describe("Extra GDB arguments (e.g. '-q')"),
797
+ cwd: tool.schema.string().optional().describe("Working directory for GDB (default: project root)"),
798
+ },
799
+ async execute(args, ctx) {
800
+ const msg = await manager.start(args.binary, args.gdbArgs, args.cwd);
801
+ return { output: msg };
802
+ },
803
+ }),
804
+ gdbLoad: tool({
805
+ description: "Load a new binary/symbol file into GDB (replaces existing target).",
806
+ args: {
807
+ binary: tool.schema.string().describe("Path to the executable or symbol file"),
808
+ args: tool.schema.string().optional().describe("Program arguments"),
809
+ },
810
+ async execute(args, ctx) {
811
+ const r = await manager.command(`-file-exec-and-symbols ${args.binary}`);
812
+ if (r.resultClass === "error")
813
+ return { output: `ERROR: ${r.resultData.msg}` };
814
+ if (args.args) {
815
+ const r2 = await manager.command(`-exec-arguments ${args.args}`);
816
+ if (r2.resultClass === "error")
817
+ return { output: `Binary loaded but args error: ${r2.resultData.msg}` };
818
+ }
819
+ return { output: `Loaded: ${args.binary}` };
820
+ },
821
+ }),
822
+ gdbRun: tool({
823
+ description: "Run (or restart) the debugged program from the beginning.",
824
+ args: {},
825
+ async execute(_args, ctx) {
826
+ const r = await manager.command("-exec-run", false);
827
+ if (r.resultClass === "error")
828
+ return { output: `ERROR: ${r.resultData.msg}` };
829
+ if (r.resultClass === "running")
830
+ return { output: "Program running..." };
831
+ return { output: manager.formatResponse(r) };
832
+ },
833
+ }),
834
+ gdbContinue: tool({
835
+ description: "Continue execution of a paused/stopped program.",
836
+ args: {},
837
+ async execute(_args, ctx) {
838
+ const r = await manager.command("-exec-continue", false);
839
+ if (r.resultClass === "error")
840
+ return { output: `ERROR: ${r.resultData.msg}` };
841
+ return { output: "Continuing..." };
842
+ },
843
+ }),
844
+ gdbInterrupt: tool({
845
+ description: "Interrupt the running program (send SIGINT to GDB).",
846
+ args: {},
847
+ async execute(_args, ctx) {
848
+ try {
849
+ const r = await manager.interrupt();
850
+ const s = manager.getState();
851
+ const reason = s.lastStopReason || "interrupted";
852
+ const lines = manager.formatStoppedInfo(r, s);
853
+ return { output: [`Interrupted (${reason})`, ...lines].join("\n") };
854
+ }
855
+ catch (e) {
856
+ return { output: manager.formatError(e) };
857
+ }
858
+ },
859
+ }),
860
+ gdbStep: tool({
861
+ description: "Step into the next source line (enter function calls).",
862
+ args: {
863
+ count: tool.schema.number().optional().describe("Number of steps (default: 1)"),
864
+ },
865
+ async execute(args, ctx) {
866
+ const n = args.count ?? 1;
867
+ const r = await manager.command(`-exec-step ${n}`, true);
868
+ if (r.resultClass === "error")
869
+ return { output: `ERROR: ${r.resultData.msg}` };
870
+ const s = manager.getState();
871
+ const lines = manager.formatStoppedInfo(r, s);
872
+ return { output: lines.join("\n") };
873
+ },
874
+ }),
875
+ gdbNext: tool({
876
+ description: "Step over the next source line (skip function calls).",
877
+ args: {
878
+ count: tool.schema.number().optional().describe("Number of steps (default: 1)"),
879
+ },
880
+ async execute(args, ctx) {
881
+ const n = args.count ?? 1;
882
+ const r = await manager.command(`-exec-next ${n}`, true);
883
+ if (r.resultClass === "error")
884
+ return { output: `ERROR: ${r.resultData.msg}` };
885
+ const s = manager.getState();
886
+ const lines = manager.formatStoppedInfo(r, s);
887
+ return { output: lines.join("\n") };
888
+ },
889
+ }),
890
+ gdbFinish: tool({
891
+ description: "Step out of the current function (execute until return).",
892
+ args: {},
893
+ async execute(_args, ctx) {
894
+ const r = await manager.command("-exec-finish", true);
895
+ if (r.resultClass === "error")
896
+ return { output: `ERROR: ${r.resultData.msg}` };
897
+ const s = manager.getState();
898
+ const lines = manager.formatStoppedInfo(r, s);
899
+ return { output: lines.join("\n") };
900
+ },
901
+ }),
902
+ gdbBreak: tool({
903
+ description: "Set a breakpoint at a location (function, file:line, or address).",
904
+ args: {
905
+ location: tool.schema.string().describe("Breakpoint location (e.g. 'main', 'file.cpp:42', '*0x400000')"),
906
+ condition: tool.schema.string().optional().describe("Breakpoint condition"),
907
+ ignore: tool.schema.number().optional().describe("Ignore count"),
908
+ enabled: tool.schema.boolean().optional().describe("Start enabled (default: true)"),
909
+ thread: tool.schema.number().optional().describe("Thread-specific breakpoint"),
910
+ },
911
+ async execute(args, ctx) {
912
+ let cmd = `-break-insert`;
913
+ if (args.thread)
914
+ cmd += ` --thread ${args.thread}`;
915
+ if (args.enabled === false)
916
+ cmd += ` --disabled`;
917
+ cmd += ` ${args.location}`;
918
+ const r = await manager.command(cmd);
919
+ if (r.resultClass === "error")
920
+ return { output: `ERROR: ${r.resultData.msg}` };
921
+ await manager.syncBreakpoints();
922
+ const bps = manager.getState().breakpoints;
923
+ const bp = bps[bps.length - 1];
924
+ const info = bp ? `Breakpoint ${bp.number} at ${bp.func || bp.file || args.location} (${bp.fullname || ""}:${bp.line || ""})` : `Breakpoint set at ${args.location}`;
925
+ if (args.condition) {
926
+ if (!bp)
927
+ return { output: `${info}\nCondition NOT set (no breakpoint number found).` };
928
+ const r2 = await manager.command(`-break-condition ${bp.number} ${args.condition}`);
929
+ if (r2.resultClass === "error")
930
+ return { output: `${info}\nCondition error: ${r2.resultData.msg}` };
931
+ }
932
+ if (args.ignore && bp) {
933
+ await manager.command(`-break-after ${bp.number} ${args.ignore}`);
934
+ }
935
+ return { output: info, metadata: { breakpoint: bp } };
936
+ },
937
+ }),
938
+ gdbBreakpointList: tool({
939
+ description: "List all breakpoints and watchpoints.",
940
+ args: {},
941
+ async execute(_args, ctx) {
942
+ await manager.syncBreakpoints();
943
+ const bps = manager.getState().breakpoints;
944
+ if (bps.length === 0)
945
+ return { output: "No breakpoints." };
946
+ const lines = bps.map((bp) => {
947
+ const status = bp.enabled ? "enabled" : "disabled";
948
+ const loc = bp.func ? `${bp.func}()` : bp.file ? `${bp.file}:${bp.line}` : bp.what || bp.at || bp.addr || "?";
949
+ const hits = bp.times > 0 ? ` (hit ${bp.times}x)` : "";
950
+ const cond = bp.condition ? ` if ${bp.condition}` : "";
951
+ const ign = bp.ignore && bp.ignore > 0 ? ` (ignore ${bp.ignore})` : "";
952
+ return ` #${bp.number} ${status} ${loc}${cond}${ign}${hits}`;
953
+ });
954
+ return { output: `Breakpoints:\n${lines.join("\n")}` };
955
+ },
956
+ }),
957
+ gdbBreakpointDelete: tool({
958
+ description: "Delete one or more breakpoints by number.",
959
+ args: {
960
+ number: tool.schema.string().describe("Breakpoint number or range (e.g. '1', '1-5')"),
961
+ },
962
+ async execute(args, ctx) {
963
+ const r = await manager.command(`-break-delete ${args.number}`);
964
+ if (r.resultClass === "error")
965
+ return { output: `ERROR: ${r.resultData.msg}` };
966
+ await manager.syncBreakpoints();
967
+ return { output: `Deleted breakpoint(s) ${args.number}` };
968
+ },
969
+ }),
970
+ gdbBreakpointEnable: tool({
971
+ description: "Enable a breakpoint by number.",
972
+ args: { number: tool.schema.number().describe("Breakpoint number") },
973
+ async execute(args, ctx) {
974
+ const r = await manager.command(`-break-enable ${args.number}`);
975
+ if (r.resultClass === "error")
976
+ return { output: `ERROR: ${r.resultData.msg}` };
977
+ await manager.syncBreakpoints();
978
+ return { output: `Enabled breakpoint ${args.number}` };
979
+ },
980
+ }),
981
+ gdbBreakpointDisable: tool({
982
+ description: "Disable a breakpoint by number.",
983
+ args: { number: tool.schema.number().describe("Breakpoint number") },
984
+ async execute(args, ctx) {
985
+ const r = await manager.command(`-break-disable ${args.number}`);
986
+ if (r.resultClass === "error")
987
+ return { output: `ERROR: ${r.resultData.msg}` };
988
+ await manager.syncBreakpoints();
989
+ return { output: `Disabled breakpoint ${args.number}` };
990
+ },
991
+ }),
992
+ gdbBreakpointCondition: tool({
993
+ description: "Set or clear a condition on an existing breakpoint.",
994
+ args: {
995
+ number: tool.schema.number().describe("Breakpoint number"),
996
+ condition: tool.schema.string().describe("Condition expression (empty to clear)"),
997
+ },
998
+ async execute(args, ctx) {
999
+ const cond = args.condition || "";
1000
+ const r = await manager.command(`-break-condition ${args.number} ${cond}`);
1001
+ if (r.resultClass === "error")
1002
+ return { output: `ERROR: ${r.resultData.msg}` };
1003
+ await manager.syncBreakpoints();
1004
+ return { output: `Breakpoint ${args.number} condition: ${cond || "(cleared)"}` };
1005
+ },
1006
+ }),
1007
+ gdbWatch: tool({
1008
+ description: "Set a watchpoint on a variable (break when value changes).",
1009
+ args: {
1010
+ expression: tool.schema.string().describe("Variable expression to watch"),
1011
+ type: tool.schema.enum(["write", "read", "access"]).optional().describe("Watch type (default: write)"),
1012
+ },
1013
+ async execute(args, ctx) {
1014
+ const type = args.type || "write";
1015
+ const cmdMap = { write: "-break-watch", read: "-break-watch -r", access: "-break-watch -a" };
1016
+ const miCmd = cmdMap[type] || cmdMap.write;
1017
+ const r = await manager.command(`${miCmd} ${args.expression}`, true);
1018
+ if (r.resultClass === "error")
1019
+ return { output: `ERROR: ${r.resultData.msg}` };
1020
+ await manager.syncBreakpoints();
1021
+ return { output: `Watchpoint set on ${args.expression} (${type})` };
1022
+ },
1023
+ }),
1024
+ gdbCatch: tool({
1025
+ description: "Set a catchpoint for exceptions, signals, or events.",
1026
+ args: {
1027
+ event: tool.schema.string().describe("Event to catch (e.g. 'throw', 'catch', 'syscall', 'signal SIGSEGV')"),
1028
+ },
1029
+ async execute(args, ctx) {
1030
+ const r = await manager.command(`-catch-${args.event}`, true);
1031
+ if (r.resultClass === "error")
1032
+ return { output: `ERROR: ${r.resultData.msg}` };
1033
+ await manager.syncBreakpoints();
1034
+ return { output: `Catchpoint set on ${args.event}` };
1035
+ },
1036
+ }),
1037
+ gdbBacktrace: tool({
1038
+ description: "Print a backtrace of the current thread (call stack).",
1039
+ args: {
1040
+ count: tool.schema.number().optional().describe("Number of frames to show (default: all)"),
1041
+ full: tool.schema.boolean().optional().describe("Show full frame info including arguments (default: false)"),
1042
+ },
1043
+ async execute(args, ctx) {
1044
+ const count = args.count ?? 0;
1045
+ const full = args.full ?? false;
1046
+ const cmd = count > 0 ? `-stack-list-frames 0 ${count - 1}` : "-stack-list-frames";
1047
+ const r = await manager.command(cmd, true);
1048
+ if (r.resultClass === "error")
1049
+ return { output: `ERROR: ${r.resultData.msg}` };
1050
+ const stackVal = r.resultData?.stack;
1051
+ let frames = [];
1052
+ if (Array.isArray(stackVal)) {
1053
+ frames = stackVal;
1054
+ }
1055
+ else if (typeof stackVal === "object" && stackVal !== null) {
1056
+ const st = stackVal;
1057
+ const f = st.frame;
1058
+ frames = Array.isArray(f) ? f : [f];
1059
+ }
1060
+ if (!Array.isArray(frames) || frames.length === 0)
1061
+ return { output: "Empty call stack" };
1062
+ const lines = [];
1063
+ for (let i = 0; i < frames.length; i++) {
1064
+ const f = frames[i];
1065
+ const level = f.level || String(i);
1066
+ const func = f.func || "??";
1067
+ const file = f.file || "";
1068
+ const fline = f.line || "";
1069
+ const from = f.from || "";
1070
+ const loc = file ? `${file}:${fline}` : from || "??";
1071
+ lines.push(` #${level} ${func} at ${loc}`);
1072
+ if (full) {
1073
+ try {
1074
+ const vr = await manager.command(`-stack-list-variables --thread ${manager.getState().currentThreadId || 1} --frame ${level} --simple-values`, true);
1075
+ if (vr.resultClass === "done" && vr.resultData?.variables) {
1076
+ const vars = vr.resultData.variables;
1077
+ if (Array.isArray(vars)) {
1078
+ for (const v of vars) {
1079
+ const ve = v;
1080
+ lines.push(` ${ve.name} = ${ve.value || ve.type || "?"}`);
1081
+ }
1082
+ }
1083
+ }
1084
+ }
1085
+ catch { /* skip */ }
1086
+ }
1087
+ }
1088
+ return { output: `Backtrace (${frames.length} frames):\n${lines.join("\n")}` };
1089
+ },
1090
+ }),
1091
+ gdbLocals: tool({
1092
+ description: "Show local variables in the current stack frame.",
1093
+ args: {
1094
+ thread: tool.schema.number().optional().describe("Thread ID (default: current)"),
1095
+ frame: tool.schema.number().optional().describe("Frame level (default: current)"),
1096
+ simple: tool.schema.boolean().optional().describe("Simple values only (default: true)"),
1097
+ },
1098
+ async execute(args, ctx) {
1099
+ const tid = args.thread ?? manager.getState().currentThreadId ?? 1;
1100
+ const fid = args.frame ?? manager.getState().currentFrameLevel ?? 0;
1101
+ const simple = args.simple !== false;
1102
+ const flags = simple ? "--simple-values" : "--all-values";
1103
+ const r = await manager.command(`-stack-list-variables --thread ${tid} --frame ${fid} ${flags}`, true);
1104
+ if (r.resultClass === "error")
1105
+ return { output: `ERROR: ${r.resultData.msg}` };
1106
+ const vars = r.resultData?.variables;
1107
+ if (!Array.isArray(vars) || vars.length === 0)
1108
+ return { output: "No local variables." };
1109
+ const lines = vars.map((v) => {
1110
+ const e = v;
1111
+ const name = e.name || "?";
1112
+ const val = e.value;
1113
+ const typ = e.type;
1114
+ if (val !== undefined)
1115
+ return ` ${name} = ${val}${typ ? ` (${typ})` : ""}`;
1116
+ return ` ${name} (${typ || "?"})`;
1117
+ });
1118
+ return { output: `Locals (thread ${tid}, frame ${fid}):\n${lines.join("\n")}` };
1119
+ },
1120
+ }),
1121
+ gdbEvaluate: tool({
1122
+ description: "Evaluate an expression in the current debugged context.",
1123
+ args: {
1124
+ expression: tool.schema.string().describe("C/C++ expression to evaluate"),
1125
+ thread: tool.schema.number().optional().describe("Thread ID"),
1126
+ frame: tool.schema.number().optional().describe("Frame level"),
1127
+ },
1128
+ async execute(args, ctx) {
1129
+ const tid = args.thread ?? manager.getState().currentThreadId;
1130
+ const fid = args.frame ?? manager.getState().currentFrameLevel;
1131
+ const opts = tid !== undefined ? ` --thread ${tid} --frame ${fid ?? 0}` : "";
1132
+ const cmd = `-data-evaluate-expression${opts} ${args.expression}`;
1133
+ const r = await manager.command(cmd, true);
1134
+ if (r.resultClass === "error")
1135
+ return { output: `ERROR: ${r.resultData.msg}` };
1136
+ const val = r.resultData?.value || "(no value)";
1137
+ return { output: `${args.expression} = ${val}` };
1138
+ },
1139
+ }),
1140
+ gdbSet: tool({
1141
+ description: "Set a variable or memory to a new value.",
1142
+ args: {
1143
+ expression: tool.schema.string().describe("Variable or memory expression"),
1144
+ value: tool.schema.string().describe("New value"),
1145
+ },
1146
+ async execute(args, ctx) {
1147
+ const r = await manager.command(`-gdb-set ${args.expression}=${args.value}`, true);
1148
+ if (r.resultClass === "error")
1149
+ return { output: `ERROR: ${r.resultData.msg}` };
1150
+ return { output: `${args.expression} = ${args.value}` };
1151
+ },
1152
+ }),
1153
+ gdbDisplay: tool({
1154
+ description: "Create a variable object for watching across steps (like GDB display).",
1155
+ args: {
1156
+ expression: tool.schema.string().describe("Expression to watch"),
1157
+ name: tool.schema.string().optional().describe("Optional name for the variable object"),
1158
+ },
1159
+ async execute(args, ctx) {
1160
+ const name = args.name || `var${manager.getState().varObjs.length + 1}`;
1161
+ const r = await manager.command(`-var-create ${name} * ${args.expression}`, true);
1162
+ if (r.resultClass === "error")
1163
+ return { output: `ERROR: ${r.resultData.msg}` };
1164
+ const info = {
1165
+ name: r.resultData?.name || name,
1166
+ expression: args.expression,
1167
+ type: r.resultData?.type,
1168
+ value: r.resultData?.value,
1169
+ numChildren: parseInt(r.resultData?.numchild || "0"),
1170
+ hasMore: r.resultData?.has_more === "1",
1171
+ };
1172
+ manager.addVarObj(info);
1173
+ return { output: `Watching ${args.expression}: ${info.value || "?"} (${info.type || "?"})`, metadata: { varObj: info } };
1174
+ },
1175
+ }),
1176
+ gdbDisplayList: tool({
1177
+ description: "List all watched variable objects.",
1178
+ args: {},
1179
+ async execute(_args, ctx) {
1180
+ const state = manager.getState();
1181
+ if (state.varObjs.length === 0)
1182
+ return { output: "No watched variables." };
1183
+ const lines = state.varObjs.map((v) => ` ${v.name}: ${v.expression} = ${v.value || "?"} (${v.type || "?"})`);
1184
+ return { output: `Display:\n${lines.join("\n")}` };
1185
+ },
1186
+ }),
1187
+ gdbDisplayDelete: tool({
1188
+ description: "Delete a watched variable object.",
1189
+ args: {
1190
+ name: tool.schema.string().describe("Variable object name (from gdb-display)"),
1191
+ },
1192
+ async execute(args, ctx) {
1193
+ const r = await manager.command(`-var-delete ${args.name}`, true);
1194
+ if (r.resultClass === "error")
1195
+ return { output: `ERROR: ${r.resultData.msg}` };
1196
+ manager.removeVarObj(args.name);
1197
+ return { output: `Deleted display ${args.name}` };
1198
+ },
1199
+ }),
1200
+ gdbDisplayUpdate: tool({
1201
+ description: "Update all watched variable objects with current values.",
1202
+ args: {
1203
+ name: tool.schema.string().optional().describe("Specific variable object name to update (default: all)"),
1204
+ },
1205
+ async execute(args, ctx) {
1206
+ const state = manager.getState();
1207
+ const names = args.name ? [args.name] : state.varObjs.map((v) => v.name);
1208
+ if (names.length === 0)
1209
+ return { output: "No watched variables." };
1210
+ const results = [];
1211
+ for (const name of names) {
1212
+ const r = await manager.command(`-var-update ${name}`, true);
1213
+ if (r.resultClass === "error") {
1214
+ results.push(` ${name}: ERROR - ${r.resultData.msg}`);
1215
+ continue;
1216
+ }
1217
+ const changelist = r.resultData?.changelist;
1218
+ if (Array.isArray(changelist) && changelist.length > 0) {
1219
+ for (const change of changelist) {
1220
+ const c = change;
1221
+ const vname = c.name;
1222
+ const val = c.value;
1223
+ const in_scope = c.in_scope !== "false";
1224
+ if (in_scope && val !== undefined) {
1225
+ results.push(` ${vname}: ${val}`);
1226
+ const vo = state.varObjs.find((v) => v.name === vname || v.name === name);
1227
+ if (vo)
1228
+ vo.value = val;
1229
+ }
1230
+ else if (!in_scope) {
1231
+ results.push(` ${vname}: <out of scope>`);
1232
+ }
1233
+ }
1234
+ }
1235
+ else {
1236
+ const ev = await manager.command(`-var-evaluate-expression ${name}`, true);
1237
+ if (ev.resultClass === "done" && ev.resultData?.value) {
1238
+ const val = ev.resultData.value;
1239
+ results.push(` ${name}: ${val}`);
1240
+ const vo = state.varObjs.find((v) => v.name === name);
1241
+ if (vo)
1242
+ vo.value = val;
1243
+ }
1244
+ }
1245
+ }
1246
+ saveState(state);
1247
+ return { output: results.length > 0 ? `Updated:\n${results.join("\n")}` : "No changes." };
1248
+ },
1249
+ }),
1250
+ gdbThreads: tool({
1251
+ description: "List all threads in the debugged process.",
1252
+ args: {},
1253
+ async execute(_args, ctx) {
1254
+ await manager.syncThreads();
1255
+ const threads = manager.getState().threads;
1256
+ if (threads.length === 0)
1257
+ return { output: "No threads (program not running)." };
1258
+ const cur = manager.getState().currentThreadId;
1259
+ const lines = threads.map((t) => {
1260
+ const marker = t.id === cur ? "*" : " ";
1261
+ const func = t.frame?.func || "??";
1262
+ const file = t.frame?.file ? ` at ${t.frame.file}:${t.frame.line}` : t.frame?.from ? ` from ${t.frame.from}` : "";
1263
+ return ` ${marker} Thread ${t.id} (${t.targetId}): ${func}${file} [${t.state}]`;
1264
+ });
1265
+ return { output: `Threads:\n${lines.join("\n")}` };
1266
+ },
1267
+ }),
1268
+ gdbSelectThread: tool({
1269
+ description: "Switch to a specific thread for subsequent commands.",
1270
+ args: {
1271
+ id: tool.schema.number().describe("Thread ID to select"),
1272
+ },
1273
+ async execute(args, ctx) {
1274
+ const r = await manager.command(`-thread-select ${args.id}`, true);
1275
+ if (r.resultClass === "error")
1276
+ return { output: `ERROR: ${r.resultData.msg}` };
1277
+ manager.setCurrentThread(args.id);
1278
+ return { output: `Switched to thread ${args.id}` };
1279
+ },
1280
+ }),
1281
+ gdbSelectFrame: tool({
1282
+ description: "Select a stack frame in the current thread for inspection.",
1283
+ args: {
1284
+ level: tool.schema.number().describe("Frame level (0 is innermost)"),
1285
+ },
1286
+ async execute(args, ctx) {
1287
+ const r = await manager.command(`-stack-select-frame ${args.level}`, true);
1288
+ if (r.resultClass === "error")
1289
+ return { output: `ERROR: ${r.resultData.msg}` };
1290
+ manager.setCurrentFrame(args.level);
1291
+ return { output: `Switched to frame #${args.level}` };
1292
+ },
1293
+ }),
1294
+ gdbDisassemble: tool({
1295
+ description: "Disassemble code at a location or around the current PC.",
1296
+ args: {
1297
+ location: tool.schema.string().optional().describe("Function or address range (e.g. 'main', 'main,+20')"),
1298
+ count: tool.schema.number().optional().describe("Number of instructions (default: 20)"),
1299
+ mode: tool.schema.enum(["mixed", "source", "assembly"]).optional().describe("Display mode (default: assembly)"),
1300
+ },
1301
+ async execute(args, ctx) {
1302
+ const state = manager.getState();
1303
+ let loc = args.location;
1304
+ if (!loc) {
1305
+ const r = await manager.command("-stack-info-frame", true);
1306
+ if (r.resultClass === "done" && r.resultData?.frame) {
1307
+ const f = r.resultData.frame;
1308
+ loc = f.addr || f.func;
1309
+ }
1310
+ if (!loc)
1311
+ return { output: "Cannot determine current location. Specify --location." };
1312
+ }
1313
+ const modeMap = { assembly: "0", mixed: "1", source: "2" };
1314
+ const mode = modeMap[args.mode || "assembly"] || "0";
1315
+ const count = args.count ?? 20;
1316
+ const miCmd = `-data-disassemble -s ${loc} -e ${loc}+${count * 4} -- ${mode}`;
1317
+ const r2 = await manager.command(miCmd, true);
1318
+ if (r2.resultClass === "error")
1319
+ return { output: `ERROR: ${r2.resultData.msg}` };
1320
+ const asm = r2.resultData?.asm_insns;
1321
+ if (!Array.isArray(asm))
1322
+ return { output: "No disassembly available." };
1323
+ const lines = [];
1324
+ for (const srcBlock of asm) {
1325
+ const block = srcBlock;
1326
+ const file = block.file;
1327
+ const line = block.line;
1328
+ const insns = block.insns;
1329
+ if (file && mode !== "assembly")
1330
+ lines.push(`${file}:${line}`);
1331
+ if (Array.isArray(insns)) {
1332
+ for (const insn of insns) {
1333
+ const i = insn;
1334
+ const addr = i.address || "";
1335
+ const funcName = i["func-name"];
1336
+ const offset = i.offset;
1337
+ const inst = i.inst || "";
1338
+ const func = funcName ? `<${funcName}+${offset}>` : "";
1339
+ lines.push(` ${addr} ${func} ${inst}`);
1340
+ }
1341
+ }
1342
+ }
1343
+ return { output: `Disassembly of ${loc}:\n${lines.join("\n")}` };
1344
+ },
1345
+ }),
1346
+ gdbRegisters: tool({
1347
+ description: "Print CPU register values for the current thread/frame.",
1348
+ args: {
1349
+ group: tool.schema.string().optional().describe("Register group (e.g. 'general', 'float', 'vector')"),
1350
+ },
1351
+ async execute(args, ctx) {
1352
+ const cmd = args.group ? `-data-list-register-values x ${args.group}` : "-data-list-register-names";
1353
+ const r = await manager.command(cmd, true);
1354
+ if (r.resultClass === "error")
1355
+ return { output: `ERROR: ${r.resultData.msg}` };
1356
+ const names = r.resultData?.registerNames;
1357
+ if (!Array.isArray(names))
1358
+ return { output: "Register data:\n" + manager.formatResponse(r) };
1359
+ const vals = await manager.command("-data-list-register-values x", true);
1360
+ if (vals.resultClass === "error")
1361
+ return { output: `ERROR: ${vals.resultData.msg}` };
1362
+ const valArr = vals.resultData?.["register-values"];
1363
+ if (!Array.isArray(valArr))
1364
+ return { output: "No register values." };
1365
+ const lines = valArr.map((v) => {
1366
+ const e = v;
1367
+ const num = parseInt(e.number || "0");
1368
+ const val = e.value || "??";
1369
+ const name = num < names.length ? names[num] : `r${num}`;
1370
+ return ` ${name}: ${val}`;
1371
+ });
1372
+ return { output: `Registers:\n${lines.join("\n")}` };
1373
+ },
1374
+ }),
1375
+ gdbStatus: tool({
1376
+ description: "Show current GDB session status, loaded binary, breakpoints, threads.",
1377
+ args: {},
1378
+ async execute(_args, ctx) {
1379
+ const s = manager.getState();
1380
+ const alive = manager.isRunning();
1381
+ const lines = [
1382
+ `GDB running: ${alive}`,
1383
+ `Status: ${s.status}`,
1384
+ `Binary: ${s.binary || "(none)"}`,
1385
+ `Args: ${s.args || "(none)"}`,
1386
+ `CWD: ${s.cwd || "(default)"}`,
1387
+ `Breakpoints: ${s.breakpoints.length}`,
1388
+ `Watch variables: ${s.varObjs.length}`,
1389
+ `Current thread: ${s.currentThreadId ?? "?"}`,
1390
+ `Current frame: ${s.currentFrameLevel ?? 0}`,
1391
+ ];
1392
+ if (s.lastStopReason)
1393
+ lines.push(`Last stop: ${s.lastStopReason}`);
1394
+ if (s.lastSignal)
1395
+ lines.push(`Last signal: ${s.lastSignal}`);
1396
+ return { output: lines.join("\n") };
1397
+ },
1398
+ }),
1399
+ gdbArgs: tool({
1400
+ description: "Set program arguments for the next run.",
1401
+ args: {
1402
+ args: tool.schema.string().describe("Command-line arguments for the debugged program"),
1403
+ },
1404
+ async execute(args, ctx) {
1405
+ const r = await manager.command(`-exec-arguments ${args.args}`);
1406
+ if (r.resultClass === "error")
1407
+ return { output: `ERROR: ${r.resultData.msg}` };
1408
+ manager.setArgs(args.args);
1409
+ return { output: `Arguments set: ${args.args}` };
1410
+ },
1411
+ }),
1412
+ gdbCwd: tool({
1413
+ description: "Set the working directory for the debugged program.",
1414
+ args: {
1415
+ dir: tool.schema.string().describe("Working directory path"),
1416
+ },
1417
+ async execute(args, ctx) {
1418
+ const r = await manager.command(`-environment-cd ${args.dir}`);
1419
+ if (r.resultClass === "error")
1420
+ return { output: `ERROR: ${r.resultData.msg}` };
1421
+ return { output: `Working directory: ${args.dir}` };
1422
+ },
1423
+ }),
1424
+ gdbEnv: tool({
1425
+ description: "Set an environment variable for the debugged program.",
1426
+ args: {
1427
+ name: tool.schema.string().describe("Environment variable name"),
1428
+ value: tool.schema.string().describe("Environment variable value"),
1429
+ },
1430
+ async execute(args, ctx) {
1431
+ const r = await manager.command(`-gdb-set environment ${args.name} ${args.value}`);
1432
+ if (r.resultClass === "error")
1433
+ return { output: `ERROR: ${r.resultData.msg}` };
1434
+ return { output: `${args.name}=${args.value}` };
1435
+ },
1436
+ }),
1437
+ gdbSignal: tool({
1438
+ description: "Send a signal to the debugged program.",
1439
+ args: {
1440
+ signal: tool.schema.string().describe("Signal name or number (e.g. 'SIGUSR1', 'SIGINT', '15')"),
1441
+ },
1442
+ async execute(args, ctx) {
1443
+ const r = await manager.command(`-exec-interrupt --signal ${args.signal}`);
1444
+ if (r.resultClass === "error")
1445
+ return { output: `ERROR: ${r.resultData.msg}` };
1446
+ return { output: `Sent ${args.signal}` };
1447
+ },
1448
+ }),
1449
+ gdbAttach: tool({
1450
+ description: "Attach GDB to a running process by PID.",
1451
+ args: {
1452
+ pid: tool.schema.number().describe("Process ID to attach to"),
1453
+ },
1454
+ async execute(args, ctx) {
1455
+ const r = await manager.command(`-target-attach ${args.pid}`);
1456
+ if (r.resultClass === "error")
1457
+ return { output: `ERROR: ${r.resultData.msg}` };
1458
+ return { output: `Attached to PID ${args.pid}` };
1459
+ },
1460
+ }),
1461
+ gdbDetach: tool({
1462
+ description: "Detach GDB from the debugged process (process continues running).",
1463
+ args: {},
1464
+ async execute(_args, ctx) {
1465
+ const r = await manager.command("-target-detach");
1466
+ if (r.resultClass === "error")
1467
+ return { output: `ERROR: ${r.resultData.msg}` };
1468
+ return { output: "Detached from process." };
1469
+ },
1470
+ }),
1471
+ gdbCore: tool({
1472
+ description: "Generate a core dump of the debugged process.",
1473
+ args: {
1474
+ path: tool.schema.string().optional().describe("Output path for core file (default: /tmp/core)"),
1475
+ },
1476
+ async execute(args, ctx) {
1477
+ const path = args.path || "/tmp/core";
1478
+ const r = await manager.command(`-gdb-generate-core ${path}`, true);
1479
+ if (r.resultClass === "error")
1480
+ return { output: `ERROR: ${r.resultData.msg}` };
1481
+ return { output: `Core dumped to ${path}` };
1482
+ },
1483
+ }),
1484
+ gdbSource: tool({
1485
+ description: "Source (execute) a GDB script file.",
1486
+ args: {
1487
+ file: tool.schema.string().describe("Path to GDB script file"),
1488
+ },
1489
+ async execute(args, ctx) {
1490
+ const r = await manager.command(`-source ${args.file}`);
1491
+ if (r.resultClass === "error")
1492
+ return { output: `ERROR: ${r.resultData.msg}` };
1493
+ return { output: `Sourced ${args.file}` };
1494
+ },
1495
+ }),
1496
+ gdbPstack: tool({
1497
+ description: "Print stack traces for all threads (like 'thread apply all bt').",
1498
+ args: {
1499
+ count: tool.schema.number().optional().describe("Number of frames per thread (default: all)"),
1500
+ },
1501
+ async execute(args, ctx) {
1502
+ await manager.syncThreads();
1503
+ const threads = manager.getState().threads;
1504
+ if (threads.length === 0)
1505
+ return { output: "No threads (program not running)." };
1506
+ const output = [];
1507
+ for (const t of threads) {
1508
+ const r = await manager.command(`--thread ${t.id} -stack-list-frames${args.count ? ` 0 ${args.count - 1}` : ""}`, true);
1509
+ if (r.resultClass === "error") {
1510
+ output.push(`Thread ${t.id} (${t.targetId}): ERROR - ${r.resultData.msg}`);
1511
+ continue;
1512
+ }
1513
+ const stack = r.resultData?.stack;
1514
+ const frames = stack?.frame || [];
1515
+ output.push(`Thread ${t.id} (${t.targetId}):`);
1516
+ for (let i = 0; i < frames.length; i++) {
1517
+ const f = frames[i];
1518
+ const func = f.func || "??";
1519
+ const file = f.file || f.from || "??";
1520
+ const line = f.line;
1521
+ output.push(` #${i} ${func} at ${file}${line ? `:${line}` : ""}`);
1522
+ }
1523
+ }
1524
+ return { output: output.join("\n") };
1525
+ },
1526
+ }),
1527
+ gdbRecord: tool({
1528
+ description: "Start or stop reverse execution recording.",
1529
+ args: {
1530
+ action: tool.schema.enum(["start", "stop"]).describe("Start or stop recording"),
1531
+ },
1532
+ async execute(args, ctx) {
1533
+ const cmd = args.action === "start" ? "target record-full" : "target record-stop";
1534
+ const r = await manager.command(`-interpreter-exec console "${cmd}"`, true);
1535
+ if (r.resultClass === "error")
1536
+ return { output: `ERROR: ${r.resultData.msg || manager.formatResponse(r)}` };
1537
+ return { output: `Recording ${args.action === "start" ? "started" : "stopped"}.` };
1538
+ },
1539
+ }),
1540
+ gdbReverse: tool({
1541
+ description: "Reverse-step or reverse-continue (requires recording).",
1542
+ args: {
1543
+ command: tool.schema.enum(["reverse-step", "reverse-next", "reverse-continue", "reverse-finish"]).describe("Reverse execution command"),
1544
+ },
1545
+ async execute(args, ctx) {
1546
+ const r = await manager.command(`-exec-${args.command.replace("reverse-", "")} --reverse`, true);
1547
+ if (r.resultClass === "error")
1548
+ return { output: `ERROR: ${r.resultData.msg}` };
1549
+ const s = manager.getState();
1550
+ const lines = manager.formatStoppedInfo(r, s);
1551
+ return { output: lines.join("\n") };
1552
+ },
1553
+ }),
1554
+ gdbFind: tool({
1555
+ description: "Search memory for a pattern.",
1556
+ args: {
1557
+ pattern: tool.schema.string().describe("Pattern to search for (hex bytes or string)"),
1558
+ start: tool.schema.string().optional().describe("Start address (default: $sp)"),
1559
+ end: tool.schema.string().optional().describe("End address (default: $sp + 4096)"),
1560
+ },
1561
+ async execute(args, ctx) {
1562
+ const start = args.start || "$sp";
1563
+ const end = args.end || "$sp + 4096";
1564
+ const cmd = `find ${start},${end},${args.pattern}`;
1565
+ const r = await manager.command(`-interpreter-exec console "${cmd}"`, true);
1566
+ const output = manager.formatResponse(r) || r.raw;
1567
+ return { output: output || "No matches." };
1568
+ },
1569
+ }),
1570
+ gdbMem: tool({
1571
+ description: "Read memory at an address and display as hex/ASCII.",
1572
+ args: {
1573
+ address: tool.schema.string().describe("Address expression (e.g. '0x7fff...', '$rbp-0x20', '&variable')"),
1574
+ count: tool.schema.number().optional().describe("Number of bytes (default: 64)"),
1575
+ },
1576
+ async execute(args, ctx) {
1577
+ const count = args.count ?? 64;
1578
+ const r = await manager.command(`-data-read-memory-bytes ${args.address} ${count}`, true);
1579
+ if (r.resultClass === "error")
1580
+ return { output: `ERROR: ${r.resultData?.msg || r.raw.substring(0, 300)}` };
1581
+ const mem = r.resultData?.memory;
1582
+ if (!Array.isArray(mem))
1583
+ return { output: "No memory data." };
1584
+ const out = [];
1585
+ for (const block of mem) {
1586
+ const b = block;
1587
+ const begin = parseInt(b.begin || "0");
1588
+ const contents = b.contents;
1589
+ if (!Array.isArray(contents)) {
1590
+ const addrStr = `0x${begin.toString(16).padStart(16, "0")}`;
1591
+ const bytes = contents;
1592
+ if (Array.isArray(bytes)) {
1593
+ const hex = bytes.map((bb) => typeof bb === "string" ? bb : String(bb)).join(" ");
1594
+ out.push(` ${addrStr}: ${hex}`);
1595
+ }
1596
+ continue;
1597
+ }
1598
+ for (const entry of contents) {
1599
+ const e = entry;
1600
+ const bytes = Array.isArray(e.bytes) ? e.bytes : e.data || [];
1601
+ const ascii = e.ascii || e.data || "";
1602
+ let addrVal = begin;
1603
+ const hexParts = [];
1604
+ for (const b2 of bytes) {
1605
+ if (typeof b2 === "object" && b2 !== null) {
1606
+ const b3 = b2;
1607
+ const v = b3.data || b3.value;
1608
+ if (v)
1609
+ hexParts.push(v);
1610
+ }
1611
+ else {
1612
+ hexParts.push(String(b2));
1613
+ }
1614
+ }
1615
+ if (hexParts.length === 0) {
1616
+ out.push(` 0x${addrVal.toString(16).padStart(16, "0")}: ${ascii}`);
1617
+ continue;
1618
+ }
1619
+ const hex = hexParts.join(" ");
1620
+ const addrStr = `0x${addrVal.toString(16).padStart(16, "0")}`;
1621
+ const pad = hex.length < 48 ? hex + " ".repeat(48 - hex.length) : hex;
1622
+ out.push(` ${addrStr}: ${pad} ${ascii}`);
1623
+ }
1624
+ }
1625
+ return { output: `Memory at ${args.address} (${count} bytes):\n${out.join("\n")}` };
1626
+ },
1627
+ }),
1628
+ gdbJump: tool({
1629
+ description: "Jump to a specific address/line and continue execution from there.",
1630
+ args: {
1631
+ location: tool.schema.string().describe("Location to jump to (address, function, or file:line)"),
1632
+ },
1633
+ async execute(args, ctx) {
1634
+ const r = await manager.command(`-exec-jump ${args.location}`, true);
1635
+ if (r.resultClass === "error")
1636
+ return { output: `ERROR: ${r.resultData.msg}` };
1637
+ return { output: `Jumped to ${args.location}` };
1638
+ },
1639
+ }),
1640
+ gdbReturn: tool({
1641
+ description: "Force-return from the current function with a value.",
1642
+ args: {
1643
+ expression: tool.schema.string().optional().describe("Return value expression (omit for void return)"),
1644
+ },
1645
+ async execute(args, ctx) {
1646
+ const cmd = args.expression ? `-exec-return ${args.expression}` : "-exec-return";
1647
+ const r = await manager.command(cmd, true);
1648
+ if (r.resultClass === "error")
1649
+ return { output: `ERROR: ${r.resultData.msg}` };
1650
+ return { output: `Returned${args.expression ? ` ${args.expression}` : ""}` };
1651
+ },
1652
+ }),
1653
+ gdbExit: tool({
1654
+ description: "Terminate GDB and clean up the debugging session.",
1655
+ args: {},
1656
+ async execute(_args, ctx) {
1657
+ const msg = manager.stop();
1658
+ return { output: msg };
1659
+ },
1660
+ }),
1661
+ gdbRaw: tool({
1662
+ description: "Send a raw MI command to GDB (for advanced use). Returns the full GDB response text.",
1663
+ args: {
1664
+ command: tool.schema.string().describe("GDB/MI command (without token prefix, e.g. '-stack-info-depth')"),
1665
+ },
1666
+ async execute(args, ctx) {
1667
+ const r = await manager.command(args.command);
1668
+ if (r.resultClass === "error")
1669
+ return { output: `ERROR: ${r.resultData.msg}` };
1670
+ return { output: r.raw || manager.formatResponse(r) };
1671
+ },
1672
+ }),
1673
+ gdbInfo: tool({
1674
+ description: "Run a general GDB 'info' command (e.g. 'info threads', 'info functions', 'info shared').",
1675
+ args: {
1676
+ what: tool.schema.string().describe("What to get info about (e.g. 'threads', 'functions', 'shared', 'args', 'locals', 'registers')"),
1677
+ },
1678
+ async execute(args, ctx) {
1679
+ const cliCmd = `info ${args.what.replace(/"/g, '\\"')}`;
1680
+ const r = await manager.command(`-interpreter-exec console "${cliCmd}"`);
1681
+ if (r.resultClass === "error")
1682
+ return { output: `ERROR: ${r.resultData.msg}` };
1683
+ const consoleOut = r.records
1684
+ .filter((rec) => (rec.type === "console" || rec.type === "target" || rec.type === "log") && rec.text !== undefined)
1685
+ .map((rec) => rec.text)
1686
+ .join("");
1687
+ return { output: consoleOut || r.raw };
1688
+ },
1689
+ }),
1690
+ gdbExec: tool({
1691
+ description: "Execute an arbitrary GDB CLI command (not MI). Useful for custom GDB commands.",
1692
+ args: {
1693
+ command: tool.schema.string().describe("GDB CLI command (e.g. 'print myvar', 'list', 'frame 2')"),
1694
+ },
1695
+ async execute(args, ctx) {
1696
+ const r = await manager.command(`-interpreter-exec console "${args.command.replace(/"/g, '\\"')}"`);
1697
+ if (r.resultClass === "error")
1698
+ return { output: `ERROR: ${r.resultData.msg}` };
1699
+ const console = r.records
1700
+ .filter((rec) => (rec.type === "console" || rec.type === "target" || rec.type === "log") && rec.text !== undefined)
1701
+ .map((rec) => rec.text)
1702
+ .join("");
1703
+ return { output: console || `OK (${r.resultClass})` };
1704
+ },
1705
+ }),
1706
+ },
1707
+ };
1708
+ }
1709
+ //# sourceMappingURL=gdb.js.map