rawDebugSession.ts 10.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import nls = require('vs/nls');
E
Erich Gamma 已提交
7 8 9 10 11
import cp = require('child_process');
import fs = require('fs');
import net = require('net');
import platform = require('vs/base/common/platform');
import errors = require('vs/base/common/errors');
I
isidor 已提交
12
import { TPromise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
13 14 15 16 17
import severity from 'vs/base/common/severity';
import debug = require('vs/workbench/parts/debug/common/debug');
import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
import v8 = require('vs/workbench/parts/debug/node/v8Protocol');
import stdfork = require('vs/base/node/stdFork');
I
isidor 已提交
18
import { IMessageService } from 'vs/platform/message/common/message';
E
Erich Gamma 已提交
19 20 21 22 23
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';

export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSession {
	private serverProcess: cp.ChildProcess;
	private socket: net.Socket = null;
I
isidor 已提交
24
	private cachedInitServer: TPromise<void>;
E
Erich Gamma 已提交
25 26
	private startTime: number;
	private stopServerPending: boolean;
27
	public isAttach: boolean;
28
	public capablities: DebugProtocol.Capabilites;
E
Erich Gamma 已提交
29 30 31 32 33 34 35 36 37 38

	constructor(
		private messageService: IMessageService,
		private telemetryService: ITelemetryService,
		private debugServerPort: number,
		private adapter: Adapter
	) {
		super();
	}

I
isidor 已提交
39
	private initServer(): TPromise<void> {
E
Erich Gamma 已提交
40 41 42 43 44 45 46 47 48
		if (this.cachedInitServer) {
			return this.cachedInitServer;
		}

		const serverPromise = this.debugServerPort ? this.connectServer(this.debugServerPort) : this.startServer();
		this.cachedInitServer = serverPromise.then(() => {
				this.startTime = new Date().getTime();
			}, err => {
				this.cachedInitServer = null;
I
isidor 已提交
49
				return TPromise.wrapError(err);
E
Erich Gamma 已提交
50 51 52 53 54 55 56 57
			}
		);

		return this.cachedInitServer;
	}

	protected send(command: string, args: any): TPromise<DebugProtocol.Response> {
		return this.initServer().then(() => super.send(command, args).then(response => response, (errorResponse: DebugProtocol.ErrorResponse) => {
I
isidor 已提交
58 59
			const error = errorResponse.body ? errorResponse.body.error : null;
			const message = error ? debug.formatPII(error.format, false, error.variables) : errorResponse.message;
E
Erich Gamma 已提交
60 61 62 63
			if (error && error.sendTelemetry) {
				this.telemetryService.publicLog('debugProtocolErrorResponse', { error : message });
			}

I
isidor 已提交
64
			return TPromise.wrapError(new Error(message));
E
Erich Gamma 已提交
65 66 67 68
		}));
	}

	public initialize(args: DebugProtocol.InitializeRequestArguments): TPromise<DebugProtocol.InitializeResponse> {
I
isidor 已提交
69
		return this.send('initialize', args).then(response => {
I
isidor 已提交
70
			this.capablities = response.body || { };
I
isidor 已提交
71 72
			return response;
		});
E
Erich Gamma 已提交
73 74 75
	}

	public launch(args: DebugProtocol.LaunchRequestArguments): TPromise<DebugProtocol.LaunchResponse> {
76
		this.isAttach = false;
E
Erich Gamma 已提交
77 78 79 80
		return this.sendAndLazyEmit('launch', args);
	}

	public attach(args: DebugProtocol.AttachRequestArguments): TPromise<DebugProtocol.AttachResponse> {
81
		this.isAttach = true;
E
Erich Gamma 已提交
82 83 84
		return this.sendAndLazyEmit('attach', args);
	}

85
	public next(args: DebugProtocol.NextArguments): TPromise<DebugProtocol.NextResponse> {
E
Erich Gamma 已提交
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
		return this.sendAndLazyEmit('next', args);
	}

	public stepIn(args: DebugProtocol.StepInArguments): TPromise<DebugProtocol.StepInResponse> {
		return this.sendAndLazyEmit('stepIn', args);
	}

	public stepOut(args: DebugProtocol.StepOutArguments): TPromise<DebugProtocol.StepOutResponse> {
		return this.sendAndLazyEmit('stepOut', args);
	}

	public continue(args: DebugProtocol.ContinueArguments): TPromise<DebugProtocol.ContinueResponse> {
		return this.sendAndLazyEmit('continue', args);
	}

I
isidor 已提交
101 102 103
	// node sometimes sends "stopped" events earlier than the response for the "step" request.
	// due to this we only emit "continued" if we did not miss a stopped event.
	// we do not emit straight away to reduce viewlet flickering.
E
Erich Gamma 已提交
104
	private sendAndLazyEmit(command: string, args: any, eventType = debug.SessionEvents.CONTINUED): TPromise<DebugProtocol.Response> {
I
isidor 已提交
105
		const count = this.flowEventsCount;
E
Erich Gamma 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
		return this.send(command, args).then(response => {
			setTimeout(() => {
				if (this.flowEventsCount === count) {
					this.emit(eventType);
				}
			}, 500);

			return response;
		});
	}

	public pause(args: DebugProtocol.PauseArguments): TPromise<DebugProtocol.PauseResponse> {
		return this.send('pause', args);
	}

121 122 123 124 125
	public disconnect(restart = false, force = false): TPromise<DebugProtocol.DisconnectResponse> {
		if (this.stopServerPending && force) {
			return this.stopServer();
		}

E
Erich Gamma 已提交
126
		if ((this.serverProcess || this.socket) && !this.stopServerPending) {
127 128
			// point of no return: from now on don't report any errors
			this.stopServerPending = true;
I
isidor 已提交
129
			return this.send('disconnect', { restart: restart }).then(() => this.stopServer(), () => this.stopServer());
E
Erich Gamma 已提交
130 131
		}

A
Alex Dima 已提交
132
		return TPromise.as(null);
E
Erich Gamma 已提交
133 134 135 136 137 138
	}

	public setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): TPromise<DebugProtocol.SetBreakpointsResponse> {
		return this.send('setBreakpoints', args);
	}

I
isidor 已提交
139 140 141 142
	public setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments): TPromise<DebugProtocol.SetFunctionBreakpointsResponse> {
		return this.send('setFunctionBreakpoints', args);
	}

E
Erich Gamma 已提交
143 144 145 146
	public setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): TPromise<DebugProtocol.SetExceptionBreakpointsResponse> {
		return this.send('setExceptionBreakpoints', args);
	}

147 148 149 150
	public configurationDone(): TPromise<DebugProtocol.ConfigurationDoneResponse> {
		return this.send('configurationDone', null);
	}

E
Erich Gamma 已提交
151 152 153 154 155 156 157 158
	public stackTrace(args: DebugProtocol.StackTraceArguments): TPromise<DebugProtocol.StackTraceResponse> {
		return this.send('stackTrace', args);
	}

	public scopes(args: DebugProtocol.ScopesArguments): TPromise<DebugProtocol.ScopesResponse> {
		return this.send('scopes', args);
	}

159
	public variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse> {
E
Erich Gamma 已提交
160 161 162
		return this.send('variables', args);
	}

163
	public source(args: DebugProtocol.SourceArguments): TPromise<DebugProtocol.SourceResponse> {
E
Erich Gamma 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
		return this.send('source', args);
	}

	public threads(): TPromise<DebugProtocol.ThreadsResponse> {
		return this.send('threads', null);
	}

	public evaluate(args: DebugProtocol.EvaluateArguments): TPromise<DebugProtocol.EvaluateResponse> {
		return this.send('evaluate', args);
	}

	public getLengthInSeconds(): number {
		return (new Date().getTime() - this.startTime) / 1000;
	}

	public getType(): string {
		return this.adapter.type;
	}

I
isidor 已提交
183 184
	private connectServer(port: number): TPromise<void> {
		return new TPromise<void>((c, e) => {
185
			this.socket = net.createConnection(port, '127.0.0.1', () => {
E
Erich Gamma 已提交
186 187 188 189 190 191 192 193 194
				this.connect(this.socket, <any>this.socket);
				c(null);
			});
			this.socket.on('error', (err: any) => {
				e(err);
			});
		});
	}

I
isidor 已提交
195
	private startServer(): TPromise<any> {
E
Erich Gamma 已提交
196
		if (!this.adapter.program) {
I
isidor 已提交
197
			return TPromise.wrapError(new Error(nls.localize('noDebugAdapterExtensionInstalled', "No extension installed for '{0}' debugging.", this.adapter.type)));
E
Erich Gamma 已提交
198 199 200 201 202 203
		}

		return this.getLaunchDetails().then(d => this.launchServer(d).then(() => {
			this.serverProcess.on('error', (err: Error) => this.onServerError(err));
			this.serverProcess.on('exit', (code: number, signal: string) => this.onServerExit());

I
isidor 已提交
204 205 206 207
			const sanitize = (s: string) => s.toString().replace(/\r?\n$/mg, '');
			// this.serverProcess.stdout.on('data', (data: string) => {
			// 	console.log('%c' + sanitize(data), 'background: #ddd; font-style: italic;');
			// });
E
Erich Gamma 已提交
208 209 210 211 212 213 214 215
			this.serverProcess.stderr.on('data', (data: string) => {
				console.log(sanitize(data));
			});

			this.connect(this.serverProcess.stdout, this.serverProcess.stdin);
		}));
	}

I
isidor 已提交
216 217
	private launchServer(launch: { command: string, argv: string[] }): TPromise<void> {
		return new TPromise<void>((c, e) => {
E
Erich Gamma 已提交
218
			if (launch.command === 'node') {
219
				stdfork.fork(launch.argv[0], launch.argv.slice(1), {}, (err, child) => {
E
Erich Gamma 已提交
220
					if (err) {
I
isidor 已提交
221
						e(new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from {0}.", launch.argv[0])));
E
Erich Gamma 已提交
222 223
					}
					this.serverProcess = child;
I
isidor 已提交
224
					c(null);
E
Erich Gamma 已提交
225 226 227 228 229 230 231 232 233
				});
			} else {
				this.serverProcess = cp.spawn(launch.command, launch.argv, {
					stdio: [
						'pipe', 	// stdin
						'pipe', 	// stdout
						'pipe'		// stderr
					],
				});
I
isidor 已提交
234
				c(null);
E
Erich Gamma 已提交
235 236 237 238
			}
		});
	}

I
isidor 已提交
239
	private stopServer(): TPromise<any> {
E
Erich Gamma 已提交
240 241 242 243 244 245 246 247

		if (this.socket !== null) {
			this.socket.end();
			this.cachedInitServer = null;
			this.emit(debug.SessionEvents.SERVER_EXIT);
		}

		if (!this.serverProcess) {
I
isidor 已提交
248
			return TPromise.as(null);
E
Erich Gamma 已提交
249 250 251 252
		}

		this.stopServerPending = true;

I
isidor 已提交
253
		let ret: TPromise<void>;
E
Erich Gamma 已提交
254 255 256 257
		// when killing a process in windows its child
		// processes are *not* killed but become root
		// processes. Therefore we use TASKKILL.EXE
		if (platform.isWindows) {
I
isidor 已提交
258
			ret = new TPromise<void>((c, e) => {
I
isidor 已提交
259
				const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess.pid}`, function (err, stdout, stderr) {
E
Erich Gamma 已提交
260 261 262 263 264 265 266 267 268
					if (err) {
						return e(err);
					}
				});
				killer.on('exit', c);
				killer.on('error', e);
			});
		} else {
			this.serverProcess.kill('SIGTERM');
I
isidor 已提交
269
			ret = TPromise.as(null);
E
Erich Gamma 已提交
270 271 272 273 274 275
		}

		return ret;
	}

	private getLaunchDetails(): TPromise<{ command: string; argv: string[]; }> {
I
isidor 已提交
276
		return new TPromise((c, e) => {
E
Erich Gamma 已提交
277 278
			fs.exists(this.adapter.program, exists => {
				if (exists) {
279
					c(null);
E
Erich Gamma 已提交
280
				} else {
I
isidor 已提交
281
					e(new Error(nls.localize('debugAdapterBinNotFound', "DebugAdapter bin folder not found on path {0}.", this.adapter.program)));
E
Erich Gamma 已提交
282 283
				}
			});
284
		}).then(() => {
E
Erich Gamma 已提交
285 286 287
			if (this.adapter.runtime) {
				return {
					command: this.adapter.runtime,
288
					argv: (this.adapter.runtimeArgs || []).concat([this.adapter.program]).concat(this.adapter.args || [])
E
Erich Gamma 已提交
289 290 291 292
				};
			}

			return {
293 294
				command: this.adapter.program,
				argv: this.adapter.args || []
E
Erich Gamma 已提交
295 296 297 298
			};
		});
	}

299 300
	protected onServerError(err: Error): void {
		this.messageService.show(severity.Error, nls.localize('stoppingDebugAdapter', "{0}. Stopping the debug adapter.", err.message));
E
Erich Gamma 已提交
301 302 303 304 305 306 307
		this.stopServer().done(null, errors.onUnexpectedError);
	}

	private onServerExit(): void {
		this.serverProcess = null;
		this.cachedInitServer = null;
		if (!this.stopServerPending) {
308
			this.messageService.show(severity.Error, nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
E
Erich Gamma 已提交
309 310 311 312
		}
		this.emit(debug.SessionEvents.SERVER_EXIT);
	}
}