From ab5166b64cbd09356428ac98f3be6868f2f7c16d Mon Sep 17 00:00:00 2001 From: Raul Giacoman Date: Sat, 19 Mar 2016 20:19:55 -0500 Subject: [PATCH] Added support for remote debugging. --- package.json | 20 +++- src/debugAdapter/goDebug.ts | 193 +++++++++++++++++++++++------------- 2 files changed, 141 insertions(+), 72 deletions(-) diff --git a/package.json b/package.json index 67351db47..daaf10c2a 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,9 @@ "type": "go", "request": "launch", "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", "program": "${workspaceRoot}", "env": {}, "args": [] @@ -140,7 +143,7 @@ }, "mode": { "type": "string", - "description": "One of 'debug', 'test', 'exec'.", + "description": "One of 'debug', 'debug_remote', 'test', 'exec'.", "default": "debug" }, "stopOnEntry": { @@ -175,6 +178,21 @@ "type": "string", "description": "Init file, executed by the terminal client.", "default": "" + }, + "remotePath": { + "type": "string", + "description": "If remote debugging, the path to the source code on the remote machine, if different from the local machine.", + "default": "" + }, + "port": { + "type": "number", + "description": "The port that the delve debugger will be listening on.", + "default": 2345 + }, + "host": { + "type": "string", + "description": "The host name of the machine the delve debugger will be listening on.", + "default": "127.0.0.1" } } } diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 05c0e83c5..eff9c8dc5 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -127,6 +127,9 @@ interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { cwd?: string; env?: { [key: string]: string; }; mode?: string; + remotePath?: string; + port?: number; + host?: string; buildFlags?: string; init?: string; } @@ -141,57 +144,69 @@ function log(msg?: any, ...args) { } class Delve { + program: string; + remotePath: string; debugProcess: ChildProcess; connection: Promise; onstdout: (str: string) => void; onstderr: (str: string) => void; - constructor(mode: string, program: string, args: string[], cwd: string, env: { [key: string]: string }, buildFlags: string, init: string) { - this.connection = new Promise((resolve, reject) => { - let serverRunning = false; - let dlv = getBinPath('dlv'); - log('Using dlv at: ', dlv); - if (!existsSync(dlv)) { - return reject('Cannot find Delve debugger. Ensure it is in your `GOPATH/bin` or `PATH`.'); - } - let dlvEnv: Object = null; - if (env) { - dlvEnv = {}; - for (let k in process.env) { - dlvEnv[k] = process.env[k]; - } - for (let k in env) { - dlvEnv[k] = env[k]; - } - } - let dlvArgs = [mode || 'debug']; - if (mode === 'exec') { - dlvArgs = dlvArgs.concat([program]); - } - dlvArgs = dlvArgs.concat(['--headless=true', '--listen=127.0.0.1:2345', '--log']); - if (buildFlags) { - dlvArgs = dlvArgs.concat(['--build-flags=' + buildFlags]); - } - if (init) { - dlvArgs = dlvArgs.concat(['--init=' + init]); - } - if (args) { - dlvArgs = dlvArgs.concat(['--', ...args]); - } - - let dlvCwd = dirname(program); - try { - if (lstatSync(program).isDirectory()) { - dlvCwd = program; - } - } catch (e) { } - this.debugProcess = spawn(dlv, dlvArgs, { - cwd: dlvCwd, - env: dlvEnv, - }); - - function connectClient() { - let client = Client.$create(2345, '127.0.0.1'); + constructor(mode: string, remotePath: string, port: number, host:string, program: string, args: string[], cwd: string, env: { [key: string]: string }, buildFlags: string, init: string) { + this.program = program; + this.remotePath = remotePath; + this.connection = new Promise((resolve, reject) => { + let serverRunning = false; + if (mode === 'debug_remote') { + this.debugProcess = null; + serverRunning = true; //assume server is running when in debug_remote mode + connectClient(port, host); + } + else + { + let dlv = getBinPath('dlv'); + log('Using dlv at: ', dlv); + if (!existsSync(dlv)) { + return reject('Cannot find Delve debugger. Ensure it is in your `GOPATH/bin` or `PATH`.'); + } + let dlvEnv: Object = null; + if (env) { + dlvEnv = {}; + for (let k in process.env) { + dlvEnv[k] = process.env[k]; + } + for (let k in env) { + dlvEnv[k] = env[k]; + } + } + let dlvArgs = [mode || 'debug']; + if (mode === 'exec') { + dlvArgs = dlvArgs.concat([program]); + } + dlvArgs = dlvArgs.concat(['--headless=true', '--listen=' + host + ':' + port.toString(), '--log']); + if (buildFlags) { + dlvArgs = dlvArgs.concat(['--build-flags=' + buildFlags]); + } + if (init) { + dlvArgs = dlvArgs.concat(['--init=' + init]); + } + if (args) { + dlvArgs = dlvArgs.concat(['--', ...args]); + } + + let dlvCwd = dirname(program); + try { + if (lstatSync(program).isDirectory()) { + dlvCwd = program; + } + } catch (e) { } + this.debugProcess = spawn(dlv, dlvArgs, { + cwd: dlvCwd, + env: dlvEnv, + }); + } + + function connectClient(port: number, host: string) { + let client = Client.$create(port, host); client.connectSocket((err, conn) => { if (err) return reject(err); // Add a slight delay to avoid issues on Linux with @@ -200,27 +215,29 @@ class Delve { resolve(conn), 200); }); + } + + if (this.debugProcess != null) { + this.debugProcess.stderr.on('data', chunk => { + let str = chunk.toString(); + if (this.onstderr) { this.onstderr(str); } + if (!serverRunning) { + serverRunning = true; + connectClient(port, host); + } + }); + this.debugProcess.stdout.on('data', chunk => { + let str = chunk.toString(); + if (this.onstdout) { this.onstdout(str); } + }); + this.debugProcess.on('close', function(code) { + // TODO: Report `dlv` crash to user. + console.error('Process exiting with code: ' + code); + }); + this.debugProcess.on('error', function(err) { + reject(err); + }); } - - this.debugProcess.stderr.on('data', chunk => { - let str = chunk.toString(); - if (this.onstderr) { this.onstderr(str); } - if (!serverRunning) { - serverRunning = true; - connectClient(); - } - }); - this.debugProcess.stdout.on('data', chunk => { - let str = chunk.toString(); - if (this.onstdout) { this.onstdout(str); } - }); - this.debugProcess.on('close', function(code) { - // TODO: Report `dlv` crash to user. - console.error('Process exiting with code: ' + code); - }); - this.debugProcess.on('error', function(err) { - reject(err); - }); }); } @@ -246,7 +263,22 @@ class Delve { } close() { - this.debugProcess.kill(); + if (this.debugProcess === null) { + this.call('Command', [{ name: 'halt' }], (err, state) => { + if (err) { + console.error('Failed to halt.'); + } + // else TODO: Can't find a way to kill remote delve. + // { + // this.call('Detach', [true], (err, ret) => { + // if (err) { + // console.error('Failed to detach.'); + // } + // }); + // } + }); + } + else this.debugProcess.kill(); } } @@ -280,7 +312,10 @@ class GoDebugSession extends DebugSession { protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { // Launch the Delve debugger on the program - this.delve = new Delve(args.mode, args.program, args.args, args.cwd, args.env, args.buildFlags, args.init); + let remotePath = typeof args.remotePath === 'undefined' ? "":args.remotePath; + let port = typeof args.port === 'undefined' ? 2345:args.port; + let host = typeof args.host === 'undefined' ? "127.0.0.1":args.host; + this.delve = new Delve(args.mode, remotePath, port, host, args.program, args.args, args.cwd, args.env, args.buildFlags, args.init); this.delve.onstdout = (str: string) => { this.sendEvent(new OutputEvent(str, 'stdout')); }; @@ -321,6 +356,18 @@ class GoDebugSession extends DebugSession { this.sendResponse(response); log('ExceptionBreakPointsResponse'); } + + protected toDebuggerPath(path): string { + if (this.delve.remotePath.length === 0) + return this.convertClientPathToDebugger(path); + return path.replace(this.delve.program, this.delve.remotePath); + } + + protected toLocalPath(path): string { + if (this.delve.remotePath.length === 0) + return this.convertDebuggerPathToClient(path); + return path.replace(this.delve.remotePath, this.delve.program); + } protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { log('SetBreakPointsRequest'); @@ -328,6 +375,7 @@ class GoDebugSession extends DebugSession { this.breakpoints.set(args.source.path, []); } let file = args.source.path; + let remotePath = this.toDebuggerPath(file) let existingBPs = this.breakpoints.get(file); Promise.all(this.breakpoints.get(file).map(existingBP => { log('Clearing: ' + existingBP.id); @@ -335,8 +383,10 @@ class GoDebugSession extends DebugSession { })).then(() => { log('All cleared'); return Promise.all(args.lines.map(line => { - log('Creating on: ' + file + ':' + line); - return this.delve.callPromise('CreateBreakpoint', [{ file, line }]).catch(err => null); + if (this.delve.remotePath.length == 0) + log('Creating on: ' + file + ':' + line); + else log('Creating on: ' + file + ' (' + remotePath + ') :' + line); + return this.delve.callPromise('CreateBreakpoint', [{ file: remotePath, line }]).catch(err => null); })); }).then(newBreakpoints => { log('All set:' + JSON.stringify(newBreakpoints)); @@ -394,7 +444,8 @@ class GoDebugSession extends DebugSession { location.function ? location.function.name : '', new Source( basename(location.file), - this.convertDebuggerPathToClient(location.file) + this.toLocalPath(location.file) + //this.convertDebuggerPathToClient(location.file) ), location.line, 0