Attach over ssh with gdb (fix #83)
This commit is contained in:
parent
c9be6b0634
commit
4f8ae4eb24
6 changed files with 132 additions and 30 deletions
60
package.json
60
package.json
|
|
@ -220,6 +220,66 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "GDB commands to run when starting to debug",
|
"description": "GDB commands to run when starting to debug",
|
||||||
"default": []
|
"default": []
|
||||||
|
},
|
||||||
|
"ssh": {
|
||||||
|
"required": [
|
||||||
|
"host",
|
||||||
|
"cwd",
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"description": "If this is set then the extension will connect to an ssh host and run GDB there",
|
||||||
|
"properties": {
|
||||||
|
"host": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Remote host name/ip to connect to"
|
||||||
|
},
|
||||||
|
"cwd": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path of project on the remote"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Remote port number",
|
||||||
|
"default": 22
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username to connect as"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Plain text password (unsafe; if possible use keyfile instead)"
|
||||||
|
},
|
||||||
|
"keyfile": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Absolute path to private key"
|
||||||
|
},
|
||||||
|
"forwardX11": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If true, the server will redirect x11 to the local host",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"x11port": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Port to redirect X11 data to (by default port = display + 6000)",
|
||||||
|
"default": 6000
|
||||||
|
},
|
||||||
|
"x11host": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Hostname/ip to redirect X11 data to",
|
||||||
|
"default": "localhost"
|
||||||
|
},
|
||||||
|
"remotex11screen": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Screen to start the application on the remote side",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"bootstrap": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Content will be executed on the SSH host before the debugger call."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export interface SSHArguments {
|
||||||
|
|
||||||
export interface IBackend {
|
export interface IBackend {
|
||||||
load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable<any>;
|
load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable<any>;
|
||||||
ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string): Thenable<any>;
|
ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable<any>;
|
||||||
attach(cwd: string, executable: string, target: string): Thenable<any>;
|
attach(cwd: string, executable: string, target: string): Thenable<any>;
|
||||||
connect(cwd: string, executable: string, target: string): Thenable<any>;
|
connect(cwd: string, executable: string, target: string): Thenable<any>;
|
||||||
start(): Thenable<boolean>;
|
start(): Thenable<boolean>;
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string): Thenable<any> {
|
ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.isSSH = true;
|
this.isSSH = true;
|
||||||
this.sshReady = false;
|
this.sshReady = false;
|
||||||
|
|
@ -125,6 +125,8 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
}
|
}
|
||||||
let sshCMD = this.application + " " + this.preargs.join(" ");
|
let sshCMD = this.application + " " + this.preargs.join(" ");
|
||||||
if (args.bootstrap) sshCMD = args.bootstrap + " && " + sshCMD;
|
if (args.bootstrap) sshCMD = args.bootstrap + " && " + sshCMD;
|
||||||
|
if (attach)
|
||||||
|
sshCMD += " -p " + target;
|
||||||
this.sshConn.exec(sshCMD, execArgs, (err, stream) => {
|
this.sshConn.exec(sshCMD, execArgs, (err, stream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.log("stderr", "Could not run " + this.application + " over ssh!");
|
this.log("stderr", "Could not run " + this.application + " over ssh!");
|
||||||
|
|
@ -141,9 +143,9 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
this.emit("quit");
|
this.emit("quit");
|
||||||
this.sshConn.end();
|
this.sshConn.end();
|
||||||
}).bind(this));
|
}).bind(this));
|
||||||
let promises = this.initCommands(target, cwd, true);
|
let promises = this.initCommands(target, cwd, true, attach);
|
||||||
promises.push(this.sendCommand("environment-cd \"" + escape(cwd) + "\""));
|
promises.push(this.sendCommand("environment-cd \"" + escape(cwd) + "\""));
|
||||||
if (procArgs && procArgs.length)
|
if (procArgs && procArgs.length && !attach)
|
||||||
promises.push(this.sendCommand("exec-arguments " + procArgs));
|
promises.push(this.sendCommand("exec-arguments " + procArgs));
|
||||||
Promise.all(promises).then(() => {
|
Promise.all(promises).then(() => {
|
||||||
this.emit("debug-ready")
|
this.emit("debug-ready")
|
||||||
|
|
@ -159,7 +161,7 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initCommands(target: string, cwd: string, ssh: boolean = false) {
|
protected initCommands(target: string, cwd: string, ssh: boolean = false, attach: boolean = false) {
|
||||||
if (ssh) {
|
if (ssh) {
|
||||||
if (!path.isAbsolute(target))
|
if (!path.isAbsolute(target))
|
||||||
target = path.join(cwd, target);
|
target = path.join(cwd, target);
|
||||||
|
|
@ -168,11 +170,13 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
if (!nativePath.isAbsolute(target))
|
if (!nativePath.isAbsolute(target))
|
||||||
target = nativePath.join(cwd, target);
|
target = nativePath.join(cwd, target);
|
||||||
}
|
}
|
||||||
return [
|
var cmds = [
|
||||||
this.sendCommand("gdb-set target-async on"),
|
this.sendCommand("gdb-set target-async on", true),
|
||||||
this.sendCommand("environment-directory \"" + escape(cwd) + "\""),
|
this.sendCommand("environment-directory \"" + escape(cwd) + "\"", true)
|
||||||
this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")
|
|
||||||
];
|
];
|
||||||
|
if (!attach)
|
||||||
|
cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\""));
|
||||||
|
return cmds;
|
||||||
}
|
}
|
||||||
|
|
||||||
attach(cwd: string, executable: string, target: string): Thenable<any> {
|
attach(cwd: string, executable: string, target: string): Thenable<any> {
|
||||||
|
|
@ -651,12 +655,18 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
this.process.stdin.write(raw + "\n");
|
this.process.stdin.write(raw + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
sendCommand(command: string): Thenable<MINode> {
|
sendCommand(command: string, suppressFailure: boolean = false): Thenable<MINode> {
|
||||||
let sel = this.currentToken++;
|
let sel = this.currentToken++;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.handlers[sel] = (node: MINode) => {
|
this.handlers[sel] = (node: MINode) => {
|
||||||
if (node && node.resultRecords && node.resultRecords.resultClass === "error")
|
if (node && node.resultRecords && node.resultRecords.resultClass === "error") {
|
||||||
reject(node.result("msg") || "Internal error");
|
if (suppressFailure) {
|
||||||
|
this.log("stderr", "WARNING: Error executing command '" + command + "'");
|
||||||
|
resolve(node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
reject((node.result("msg") || "Internal error") + " (from " + command + ")");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
resolve(node);
|
resolve(node);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import * as nativePath from "path"
|
||||||
let path = posix;
|
let path = posix;
|
||||||
|
|
||||||
export class MI2_LLDB extends MI2 {
|
export class MI2_LLDB extends MI2 {
|
||||||
protected initCommands(target: string, cwd: string, ssh: boolean = false) {
|
protected initCommands(target: string, cwd: string, ssh: boolean = false, attach: boolean = false) {
|
||||||
if (ssh) {
|
if (ssh) {
|
||||||
if (!path.isAbsolute(target))
|
if (!path.isAbsolute(target))
|
||||||
target = path.join(cwd, target);
|
target = path.join(cwd, target);
|
||||||
|
|
@ -15,10 +15,12 @@ export class MI2_LLDB extends MI2 {
|
||||||
if (!nativePath.isAbsolute(target))
|
if (!nativePath.isAbsolute(target))
|
||||||
target = nativePath.join(cwd, target);
|
target = nativePath.join(cwd, target);
|
||||||
}
|
}
|
||||||
return [
|
var cmds = [
|
||||||
this.sendCommand("gdb-set target-async on"),
|
this.sendCommand("gdb-set target-async on")
|
||||||
this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")
|
|
||||||
];
|
];
|
||||||
|
if (!attach)
|
||||||
|
cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\""));
|
||||||
|
return cmds;
|
||||||
}
|
}
|
||||||
|
|
||||||
attach(cwd: string, executable: string, target: string): Thenable<any> {
|
attach(cwd: string, executable: string, target: string): Thenable<any> {
|
||||||
|
|
|
||||||
56
src/gdb.ts
56
src/gdb.ts
|
|
@ -25,6 +25,7 @@ export interface AttachRequestArguments {
|
||||||
executable: string;
|
executable: string;
|
||||||
remote: boolean;
|
remote: boolean;
|
||||||
autorun: string[];
|
autorun: string[];
|
||||||
|
ssh: SSHArguments;
|
||||||
printCalls: boolean;
|
printCalls: boolean;
|
||||||
showDevDebugOutput: boolean;
|
showDevDebugOutput: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +68,7 @@ class GDBDebugSession extends MI2DebugSession {
|
||||||
this.isSSH = true;
|
this.isSSH = true;
|
||||||
this.trimCWD = args.cwd.replace(/\\/g, "/");
|
this.trimCWD = args.cwd.replace(/\\/g, "/");
|
||||||
this.switchCWD = args.ssh.cwd;
|
this.switchCWD = args.ssh.cwd;
|
||||||
this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal).then(() => {
|
this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal, false).then(() => {
|
||||||
if (args.autorun)
|
if (args.autorun)
|
||||||
args.autorun.forEach(command => {
|
args.autorun.forEach(command => {
|
||||||
this.miDebugger.sendUserInput(command);
|
this.miDebugger.sendUserInput(command);
|
||||||
|
|
@ -120,27 +121,56 @@ class GDBDebugSession extends MI2DebugSession {
|
||||||
this.debugReady = false;
|
this.debugReady = false;
|
||||||
this.miDebugger.printCalls = !!args.printCalls;
|
this.miDebugger.printCalls = !!args.printCalls;
|
||||||
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
||||||
if (args.remote) {
|
if (args.ssh !== undefined) {
|
||||||
this.miDebugger.connect(args.cwd, args.executable, args.target).then(() => {
|
if (args.ssh.forwardX11 === undefined)
|
||||||
|
args.ssh.forwardX11 = true;
|
||||||
|
if (args.ssh.port === undefined)
|
||||||
|
args.ssh.port = 22;
|
||||||
|
if (args.ssh.x11port === undefined)
|
||||||
|
args.ssh.x11port = 6000;
|
||||||
|
if (args.ssh.x11host === undefined)
|
||||||
|
args.ssh.x11host = "localhost";
|
||||||
|
if (args.ssh.remotex11screen === undefined)
|
||||||
|
args.ssh.remotex11screen = 0;
|
||||||
|
this.isSSH = true;
|
||||||
|
this.trimCWD = args.cwd.replace(/\\/g, "/");
|
||||||
|
this.switchCWD = args.ssh.cwd;
|
||||||
|
this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, "", undefined, true).then(() => {
|
||||||
if (args.autorun)
|
if (args.autorun)
|
||||||
args.autorun.forEach(command => {
|
args.autorun.forEach(command => {
|
||||||
this.miDebugger.sendUserInput(command);
|
this.miDebugger.sendUserInput(command);
|
||||||
});
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
this.miDebugger.emit("ui-break-done");
|
||||||
|
}, 50);
|
||||||
this.sendResponse(response);
|
this.sendResponse(response);
|
||||||
}, err => {
|
}, err => {
|
||||||
this.sendErrorResponse(response, 102, `Failed to attach: ${err.toString()}`)
|
this.sendErrorResponse(response, 102, `Failed to SSH: ${err.toString()}`)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => {
|
if (args.remote) {
|
||||||
if (args.autorun)
|
this.miDebugger.connect(args.cwd, args.executable, args.target).then(() => {
|
||||||
args.autorun.forEach(command => {
|
if (args.autorun)
|
||||||
this.miDebugger.sendUserInput(command);
|
args.autorun.forEach(command => {
|
||||||
});
|
this.miDebugger.sendUserInput(command);
|
||||||
this.sendResponse(response);
|
});
|
||||||
}, err => {
|
this.sendResponse(response);
|
||||||
this.sendErrorResponse(response, 101, `Failed to attach: ${err.toString()}`)
|
}, err => {
|
||||||
});
|
this.sendErrorResponse(response, 102, `Failed to attach: ${err.toString()}`)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => {
|
||||||
|
if (args.autorun)
|
||||||
|
args.autorun.forEach(command => {
|
||||||
|
this.miDebugger.sendUserInput(command);
|
||||||
|
});
|
||||||
|
this.sendResponse(response);
|
||||||
|
}, err => {
|
||||||
|
this.sendErrorResponse(response, 101, `Failed to attach: ${err.toString()}`)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ class LLDBDebugSession extends MI2DebugSession {
|
||||||
this.isSSH = true;
|
this.isSSH = true;
|
||||||
this.trimCWD = args.cwd.replace(/\\/g, "/");
|
this.trimCWD = args.cwd.replace(/\\/g, "/");
|
||||||
this.switchCWD = args.ssh.cwd;
|
this.switchCWD = args.ssh.cwd;
|
||||||
this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, undefined).then(() => {
|
this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, undefined, false).then(() => {
|
||||||
if (args.autorun)
|
if (args.autorun)
|
||||||
args.autorun.forEach(command => {
|
args.autorun.forEach(command => {
|
||||||
this.miDebugger.sendUserInput(command);
|
this.miDebugger.sendUserInput(command);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue