From 4f8ae4eb24e1a064d8448c0c78eb2c96253f810d Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Sat, 31 Dec 2016 15:16:12 +0100 Subject: [PATCH] Attach over ssh with gdb (fix #83) --- package.json | 60 ++++++++++++++++++++++++++++++++++++++ src/backend/backend.ts | 2 +- src/backend/mi2/mi2.ts | 32 +++++++++++++------- src/backend/mi2/mi2lldb.ts | 10 ++++--- src/gdb.ts | 56 ++++++++++++++++++++++++++--------- src/lldb.ts | 2 +- 6 files changed, 132 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 12352c1..84c5250 100644 --- a/package.json +++ b/package.json @@ -220,6 +220,66 @@ "type": "array", "description": "GDB commands to run when starting to debug", "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." + } + } } } } diff --git a/src/backend/backend.ts b/src/backend/backend.ts index 5551487..d2738a7 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -38,7 +38,7 @@ export interface SSHArguments { export interface IBackend { load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable; - ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string): Thenable; + ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable; attach(cwd: string, executable: string, target: string): Thenable; connect(cwd: string, executable: string, target: string): Thenable; start(): Thenable; diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 7840355..22f45de 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -72,7 +72,7 @@ export class MI2 extends EventEmitter implements IBackend { }); } - ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string): Thenable { + ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable { return new Promise((resolve, reject) => { this.isSSH = true; this.sshReady = false; @@ -125,6 +125,8 @@ export class MI2 extends EventEmitter implements IBackend { } let sshCMD = this.application + " " + this.preargs.join(" "); if (args.bootstrap) sshCMD = args.bootstrap + " && " + sshCMD; + if (attach) + sshCMD += " -p " + target; this.sshConn.exec(sshCMD, execArgs, (err, stream) => { if (err) { 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.sshConn.end(); }).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) + "\"")); - if (procArgs && procArgs.length) + if (procArgs && procArgs.length && !attach) promises.push(this.sendCommand("exec-arguments " + procArgs)); Promise.all(promises).then(() => { 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 (!path.isAbsolute(target)) target = path.join(cwd, target); @@ -168,11 +170,13 @@ export class MI2 extends EventEmitter implements IBackend { if (!nativePath.isAbsolute(target)) target = nativePath.join(cwd, target); } - return [ - this.sendCommand("gdb-set target-async on"), - this.sendCommand("environment-directory \"" + escape(cwd) + "\""), - this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"") + var cmds = [ + this.sendCommand("gdb-set target-async on", true), + this.sendCommand("environment-directory \"" + escape(cwd) + "\"", true) ]; + if (!attach) + cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")); + return cmds; } attach(cwd: string, executable: string, target: string): Thenable { @@ -651,12 +655,18 @@ export class MI2 extends EventEmitter implements IBackend { this.process.stdin.write(raw + "\n"); } - sendCommand(command: string): Thenable { + sendCommand(command: string, suppressFailure: boolean = false): Thenable { let sel = this.currentToken++; return new Promise((resolve, reject) => { this.handlers[sel] = (node: MINode) => { - if (node && node.resultRecords && node.resultRecords.resultClass === "error") - reject(node.result("msg") || "Internal error"); + if (node && node.resultRecords && node.resultRecords.resultClass === "error") { + if (suppressFailure) { + this.log("stderr", "WARNING: Error executing command '" + command + "'"); + resolve(node); + } + else + reject((node.result("msg") || "Internal error") + " (from " + command + ")"); + } else resolve(node); }; diff --git a/src/backend/mi2/mi2lldb.ts b/src/backend/mi2/mi2lldb.ts index b5a7f82..6cb5263 100644 --- a/src/backend/mi2/mi2lldb.ts +++ b/src/backend/mi2/mi2lldb.ts @@ -6,7 +6,7 @@ import * as nativePath from "path" let path = posix; 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 (!path.isAbsolute(target)) target = path.join(cwd, target); @@ -15,10 +15,12 @@ export class MI2_LLDB extends MI2 { if (!nativePath.isAbsolute(target)) target = nativePath.join(cwd, target); } - return [ - this.sendCommand("gdb-set target-async on"), - this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"") + var cmds = [ + this.sendCommand("gdb-set target-async on") ]; + if (!attach) + cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")); + return cmds; } attach(cwd: string, executable: string, target: string): Thenable { diff --git a/src/gdb.ts b/src/gdb.ts index 767c549..428d6f4 100644 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -25,6 +25,7 @@ export interface AttachRequestArguments { executable: string; remote: boolean; autorun: string[]; + ssh: SSHArguments; printCalls: boolean; showDevDebugOutput: boolean; } @@ -67,7 +68,7 @@ class GDBDebugSession extends MI2DebugSession { this.isSSH = true; this.trimCWD = args.cwd.replace(/\\/g, "/"); 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) args.autorun.forEach(command => { this.miDebugger.sendUserInput(command); @@ -120,27 +121,56 @@ class GDBDebugSession extends MI2DebugSession { this.debugReady = false; this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; - if (args.remote) { - this.miDebugger.connect(args.cwd, args.executable, args.target).then(() => { + if (args.ssh !== undefined) { + 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) args.autorun.forEach(command => { this.miDebugger.sendUserInput(command); }); + setTimeout(() => { + this.miDebugger.emit("ui-break-done"); + }, 50); this.sendResponse(response); }, err => { - this.sendErrorResponse(response, 102, `Failed to attach: ${err.toString()}`) + this.sendErrorResponse(response, 102, `Failed to SSH: ${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()}`) - }); + if (args.remote) { + this.miDebugger.connect(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, 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()}`) + }); + } } } } diff --git a/src/lldb.ts b/src/lldb.ts index 14910b4..bb9ccd6 100644 --- a/src/lldb.ts +++ b/src/lldb.ts @@ -63,7 +63,7 @@ class LLDBDebugSession extends MI2DebugSession { this.isSSH = true; this.trimCWD = args.cwd.replace(/\\/g, "/"); 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) args.autorun.forEach(command => { this.miDebugger.sendUserInput(command);