Added support for attaching & connecting to gdbserver (fix #4, fix #5)

This commit is contained in:
WebFreak001 2016-02-04 15:45:10 +01:00
commit 99217775cf
5 changed files with 139 additions and 8 deletions

View file

@ -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) 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 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 the green start button in the debug sidebar. Multithreading and adding breakpoints
while running does not work at the time of writing. Also stopping the program sometimes while running does not work at the time of writing. However you can add/remove breakpoints
does not work properly. as you wish while the program is paused.
Extending variables is very limited as it does not support child values of variables. 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 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 Some exceptions/signals like segmentation faults will be catched and displayed but
it does not support for example most D exceptions. 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) ## [Issues](https://github.com/WebFreak001/code-debug)

View file

@ -49,6 +49,30 @@
"description": "Path of project" "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": [ "initialConfigurations": [

View file

@ -14,8 +14,11 @@ export interface Stack {
export interface IBackend { export interface IBackend {
load(cwd: string, target: string): Thenable<any>; load(cwd: string, target: string): Thenable<any>;
attach(cwd: string, executable: string, target: string): Thenable<any>;
connect(cwd: string, executable: string, target: string): Thenable<any>;
start(): Thenable<boolean>; start(): Thenable<boolean>;
stop(); stop();
detach();
interrupt(): Thenable<boolean>; interrupt(): Thenable<boolean>;
continue(): Thenable<boolean>; continue(): Thenable<boolean>;
next(): Thenable<boolean>; next(): Thenable<boolean>;

View file

@ -12,6 +12,7 @@ export class MI2 extends EventEmitter implements IBackend {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.process = ChildProcess.spawn(this.application, this.preargs.concat([target]), { cwd: cwd }); this.process = ChildProcess.spawn(this.application, this.preargs.concat([target]), { cwd: cwd });
this.process.stdout.on("data", this.stdout.bind(this)); 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)); this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
Promise.all([ Promise.all([
this.sendCommand("environment-directory \"" + cwd + "\"") this.sendCommand("environment-directory \"" + cwd + "\"")
@ -19,6 +20,40 @@ export class MI2 extends EventEmitter implements IBackend {
}); });
} }
attach(cwd: string, executable: string, target: string): Thenable<any> {
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<any> {
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) { stdout(data) {
this.buffer += data.toString("utf8"); this.buffer += data.toString("utf8");
let end = this.buffer.lastIndexOf('\n'); let end = this.buffer.lastIndexOf('\n');
@ -88,14 +123,25 @@ export class MI2 extends EventEmitter implements IBackend {
stop() { stop() {
let proc = this.process; let proc = this.process;
let to = setTimeout(() => { let to = setTimeout(() => {
proc.kill(); process.kill(-proc.pid);
}, 2222); }, 1000);
this.process.on("exit", function(code) { this.process.on("exit", function(code) {
clearTimeout(to); clearTimeout(to);
}); });
this.sendRaw("-gdb-exit"); 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<boolean> { interrupt(): Thenable<boolean> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.sendCommand("exec-interrupt").then((info) => { this.sendCommand("exec-interrupt").then((info) => {

View file

@ -10,11 +10,19 @@ export interface LaunchRequestArguments {
target: string; target: string;
} }
export interface AttachRequestArguments {
cwd: string;
target: string;
executable: string;
remote: boolean;
}
class MI2DebugSession extends DebugSession { class MI2DebugSession extends DebugSession {
private static THREAD_ID = 1; private static THREAD_ID = 1;
private gdbDebugger: MI2; private gdbDebugger: MI2;
private variableHandles = new Handles<any>(); private variableHandles = new Handles<any>();
private quit: boolean; private quit: boolean;
private attached: boolean;
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) { public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
super(debuggerLinesStartAt1, isServer); super(debuggerLinesStartAt1, isServer);
@ -45,7 +53,7 @@ class MI2DebugSession extends DebugSession {
} }
private stopEvent(info: MINode) { private stopEvent(info: MINode) {
if(!this.quit) if (!this.quit)
this.sendEvent(new StoppedEvent("exception", MI2DebugSession.THREAD_ID)); this.sendEvent(new StoppedEvent("exception", MI2DebugSession.THREAD_ID));
} }
@ -55,6 +63,8 @@ class MI2DebugSession extends DebugSession {
} }
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
this.quit = false;
this.attached = false;
this.gdbDebugger.load(args.cwd, args.target).then(() => { this.gdbDebugger.load(args.cwd, args.target).then(() => {
this.gdbDebugger.start().then(() => { this.gdbDebugger.start().then(() => {
this.sendResponse(response); 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 { 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); this.sendResponse(response);
} }