From 99217775cf27a86dc614853797ba619529a662f7 Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Thu, 4 Feb 2016 15:45:10 +0100 Subject: [PATCH] Added support for attaching & connecting to gdbserver (fix #4, fix #5) --- README.md | 36 +++++++++++++++++++++++++++--- package.json | 24 ++++++++++++++++++++ src/backend/backend.ts | 3 +++ src/backend/mi2/mi2.ts | 50 ++++++++++++++++++++++++++++++++++++++++-- src/gdb.ts | 34 +++++++++++++++++++++++++--- 5 files changed, 139 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b378d71..bb62641 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ Now you need to change `target` to the application you want to debug relative to the cwd. (Which is the workspace root by default) Before debugging you need to compile your application first, then you can run it using -the green start button in the debug sidebar. Multithreading and removing breakpoints -while running does not work at the time of writing. Also stopping the program sometimes -does not work properly. +the green start button in the debug sidebar. Multithreading and adding breakpoints +while running does not work at the time of writing. However you can add/remove breakpoints +as you wish while the program is paused. Extending variables is very limited as it does not support child values of variables. Watching expressions works partially but the result does not get properly parsed and @@ -34,4 +34,34 @@ in `stdout` for the application, `stderr` for errors and `log` for GDB log messa Some exceptions/signals like segmentation faults will be catched and displayed but it does not support for example most D exceptions. +### Attaching to existing processes + +Attaching to existing processes currently only works by specifying the PID in the +`launch.json` and setting `request` to `"attach"`. You also need to specify the executable +path for GDB to find the debug symbols. + +``` +"request": "attach", +"executable": "./bin/executable", +"target": "4285" +``` + +This will attach to PID 4285 which should already run. GDB will pause the program on entering. + +### Using `gdbserver` for remote debugging + +You can also connect to a gdbserver instance and debug using that. For that modify the +`launch.json` by setting `request` to `"attach"` and `remote` to `true` and specifing the +port and optionally hostname in `target`. + +``` +"request": "attach", +"executable": "./bin/executable", +"target": ":2345", +"remote": true +``` + +This will attach to the running process managed by gdbserver on localhost:2345. You might +need to hit the start button in the debug bar at the top first to start the program. + ## [Issues](https://github.com/WebFreak001/code-debug) \ No newline at end of file diff --git a/package.json b/package.json index b02ba9f..7891b7e 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,30 @@ "description": "Path of project" } } + }, + "attach": { + "required": [ + "target" + ], + "properties": { + "target": { + "type": "string", + "description": "PID of running program or program name or connection arguments (eg :2345) if remote is true" + }, + "remote": { + "type": "boolean", + "description": "If true this will connect to a gdbserver instead of attaching to a PID", + "default": false + }, + "executable": { + "type": "string", + "description": "Path of executable for debugging symbols" + }, + "cwd": { + "type": "string", + "description": "Path of project" + } + } } }, "initialConfigurations": [ diff --git a/src/backend/backend.ts b/src/backend/backend.ts index f262b6f..f7a252c 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -14,8 +14,11 @@ export interface Stack { export interface IBackend { load(cwd: string, target: string): Thenable; + attach(cwd: string, executable: string, target: string): Thenable; + connect(cwd: string, executable: string, target: string): Thenable; start(): Thenable; stop(); + detach(); interrupt(): Thenable; continue(): Thenable; next(): Thenable; diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 2d3fcd0..97e7585 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -12,6 +12,7 @@ export class MI2 extends EventEmitter implements IBackend { return new Promise((resolve, reject) => { this.process = ChildProcess.spawn(this.application, this.preargs.concat([target]), { cwd: cwd }); this.process.stdout.on("data", this.stdout.bind(this)); + this.process.stderr.on("data", this.stdout.bind(this)); this.process.on("exit", (() => { this.emit("quit"); }).bind(this)); Promise.all([ this.sendCommand("environment-directory \"" + cwd + "\"") @@ -19,6 +20,40 @@ export class MI2 extends EventEmitter implements IBackend { }); } + attach(cwd: string, executable: string, target: string): Thenable { + return new Promise((resolve, reject) => { + let args = []; + if (!executable) + executable = "-p"; + args = args.concat([executable, target], this.preargs); + this.process = ChildProcess.spawn(this.application, args, { cwd: cwd }); + this.process.stdout.on("data", this.stdout.bind(this)); + this.process.stderr.on("data", this.stdout.bind(this)); + this.process.on("exit", (() => { this.emit("quit"); }).bind(this)); + Promise.all([ + this.sendCommand("environment-directory \"" + cwd + "\"") + ]).then(resolve, reject); + }); + } + + connect(cwd: string, executable: string, target: string): Thenable { + return new Promise((resolve, reject) => { + let args = []; + if (executable) + args = args.concat([executable], this.preargs); + else + args = this.preargs; + this.process = ChildProcess.spawn(this.application, args, { cwd: cwd }); + this.process.stdout.on("data", this.stdout.bind(this)); + this.process.stderr.on("data", this.stdout.bind(this)); + this.process.on("exit", (() => { this.emit("quit"); }).bind(this)); + Promise.all([ + this.sendCommand("environment-directory \"" + cwd + "\""), + this.sendCommand("target-select remote " + target) + ]).then(resolve, reject); + }); + } + stdout(data) { this.buffer += data.toString("utf8"); let end = this.buffer.lastIndexOf('\n'); @@ -88,13 +123,24 @@ export class MI2 extends EventEmitter implements IBackend { stop() { let proc = this.process; let to = setTimeout(() => { - proc.kill(); - }, 2222); + process.kill(-proc.pid); + }, 1000); this.process.on("exit", function(code) { clearTimeout(to); }); this.sendRaw("-gdb-exit"); } + + detach() { + let proc = this.process; + let to = setTimeout(() => { + process.kill(-proc.pid); + }, 1000); + this.process.on("exit", function(code) { + clearTimeout(to); + }); + this.sendRaw("-target-detach"); + } interrupt(): Thenable { return new Promise((resolve, reject) => { diff --git a/src/gdb.ts b/src/gdb.ts index 91d560c..f45d50f 100644 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -10,11 +10,19 @@ export interface LaunchRequestArguments { target: string; } +export interface AttachRequestArguments { + cwd: string; + target: string; + executable: string; + remote: boolean; +} + class MI2DebugSession extends DebugSession { private static THREAD_ID = 1; private gdbDebugger: MI2; private variableHandles = new Handles(); private quit: boolean; + private attached: boolean; public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { super(debuggerLinesStartAt1, isServer); @@ -43,9 +51,9 @@ class MI2DebugSession extends DebugSession { private handleBreak(info: MINode) { this.sendEvent(new StoppedEvent("step", MI2DebugSession.THREAD_ID)); } - + private stopEvent(info: MINode) { - if(!this.quit) + if (!this.quit) this.sendEvent(new StoppedEvent("exception", MI2DebugSession.THREAD_ID)); } @@ -55,6 +63,8 @@ class MI2DebugSession extends DebugSession { } protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { + this.quit = false; + this.attached = false; this.gdbDebugger.load(args.cwd, args.target).then(() => { this.gdbDebugger.start().then(() => { this.sendResponse(response); @@ -62,8 +72,26 @@ class MI2DebugSession extends DebugSession { }); } + protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { + this.quit = false; + this.attached = !args.remote; + if (args.remote) { + this.gdbDebugger.connect(args.cwd, args.executable, args.target).then(() => { + this.sendResponse(response); + }); + } + else { + this.gdbDebugger.attach(args.cwd, args.executable, args.target).then(() => { + this.sendResponse(response); + }); + } + } + protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { - this.gdbDebugger.stop(); + if (this.attached) + this.gdbDebugger.detach(); + else + this.gdbDebugger.stop(); this.sendResponse(response); }