rawDebugSession.ts 15.0 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
import cp = require('child_process');
import fs = require('fs');
import net = require('net');
10
import Event, { Emitter } from 'vs/base/common/event';
E
Erich Gamma 已提交
11
import platform = require('vs/base/common/platform');
12
import { Action } from 'vs/base/common/actions';
E
Erich Gamma 已提交
13
import errors = require('vs/base/common/errors');
I
isidor 已提交
14
import { TPromise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
15 16
import severity from 'vs/base/common/severity';
import stdfork = require('vs/base/node/stdFork');
17
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
E
Erich Gamma 已提交
18
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
19 20 21 22
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 { IOutputService } from 'vs/workbench/parts/output/common/output';
J
Joao Moreno 已提交
23
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
24

25
import { shell } from 'electron';
E
Erich Gamma 已提交
26

27 28 29 30 31 32 33 34 35 36 37 38 39 40
export interface SessionExitedEvent extends DebugProtocol.ExitedEvent {
	body: {
		exitCode: number,
		sessionId: string
	};
}

export interface SessionTerminatedEvent extends DebugProtocol.TerminatedEvent {
	body: {
		restart?: boolean,
		sessionId: string
	};
}

E
Erich Gamma 已提交
41
export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSession {
I
isidor 已提交
42 43

	public restarted: boolean;
44 45
	public emittedStopped: boolean;
	public readyForBreakpoints: boolean;
I
isidor 已提交
46

E
Erich Gamma 已提交
47 48
	private serverProcess: cp.ChildProcess;
	private socket: net.Socket = null;
I
isidor 已提交
49
	private cachedInitServer: TPromise<void>;
E
Erich Gamma 已提交
50 51
	private startTime: number;
	private stopServerPending: boolean;
52
	private sentPromises: TPromise<DebugProtocol.Response>[];
I
isidor 已提交
53 54
	private isAttach: boolean;
	private capabilities: DebugProtocol.Capabilites;
E
Erich Gamma 已提交
55

56 57
	private _onDidInitialize: Emitter<DebugProtocol.InitializedEvent>;
	private _onDidStop: Emitter<DebugProtocol.StoppedEvent>;
58 59
	private _onDidTerminateDebugee: Emitter<SessionTerminatedEvent>;
	private _onDidExitAdapter: Emitter<SessionExitedEvent>;
60 61 62
	private _onDidThread: Emitter<DebugProtocol.ThreadEvent>;
	private _onDidOutput: Emitter<DebugProtocol.OutputEvent>;
	private _onDidBreakpoint: Emitter<DebugProtocol.BreakpointEvent>;
63
	private _onDidEvent: Emitter<DebugProtocol.Event>;
64

E
Erich Gamma 已提交
65 66
	constructor(
		private debugServerPort: number,
67
		private adapter: Adapter,
68
		private customTelemetryService: ITelemetryService,
69 70 71
		@IMessageService private messageService: IMessageService,
		@ITelemetryService private telemetryService: ITelemetryService,
		@IOutputService private outputService: IOutputService
E
Erich Gamma 已提交
72 73
	) {
		super();
74 75
		this.emittedStopped = false;
		this.readyForBreakpoints = false;
76
		this.sentPromises = [];
77 78 79

		this._onDidInitialize = new Emitter<DebugProtocol.InitializedEvent>();
		this._onDidStop = new Emitter<DebugProtocol.StoppedEvent>();
80 81
		this._onDidTerminateDebugee = new Emitter<SessionTerminatedEvent>();
		this._onDidExitAdapter = new Emitter<SessionExitedEvent>();
82 83 84
		this._onDidThread = new Emitter<DebugProtocol.ThreadEvent>();
		this._onDidOutput = new Emitter<DebugProtocol.OutputEvent>();
		this._onDidBreakpoint = new Emitter<DebugProtocol.BreakpointEvent>();
85
		this._onDidEvent = new Emitter<DebugProtocol.Event>();
86 87 88 89 90 91 92 93 94 95
	}

	public get onDidInitialize(): Event<DebugProtocol.InitializedEvent> {
		return this._onDidInitialize.event;
	}

	public get onDidStop(): Event<DebugProtocol.StoppedEvent> {
		return this._onDidStop.event;
	}

96
	public get onDidTerminateDebugee(): Event<SessionTerminatedEvent> {
97 98 99
		return this._onDidTerminateDebugee.event;
	}

100
	public get onDidExitAdapter(): Event<SessionExitedEvent> {
101 102 103 104 105 106 107 108 109 110 111 112 113
		return this._onDidExitAdapter.event;
	}

	public get onDidThread(): Event<DebugProtocol.ThreadEvent> {
		return this._onDidThread.event;
	}

	public get onDidOutput(): Event<DebugProtocol.OutputEvent> {
		return this._onDidOutput.event;
	}

	public get onDidBreakpoint(): Event<DebugProtocol.BreakpointEvent> {
		return this._onDidBreakpoint.event;
E
Erich Gamma 已提交
114 115
	}

116 117 118 119
	public get onDidEvent(): Event<DebugProtocol.Event> {
		return this._onDidEvent.event;
	}

I
isidor 已提交
120
	private initServer(): TPromise<void> {
E
Erich Gamma 已提交
121 122 123 124 125 126 127 128 129
		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 已提交
130
				return TPromise.wrapError(err);
E
Erich Gamma 已提交
131 132 133 134 135 136
			}
		);

		return this.cachedInitServer;
	}

137 138 139 140
	public custom(request: string, args: any): TPromise<DebugProtocol.Response> {
		return this.send(request, args);
	}

E
Erich Gamma 已提交
141
	protected send(command: string, args: any): TPromise<DebugProtocol.Response> {
142 143 144 145 146 147
		return this.initServer().then(() => {
			const promise = super.send(command, args).then(response => response, (errorResponse: DebugProtocol.ErrorResponse) => {
				const error = errorResponse.body ? errorResponse.body.error : null;
				const message = error ? debug.formatPII(error.format, false, error.variables) : errorResponse.message;
				if (error && error.sendTelemetry) {
					this.telemetryService.publicLog('debugProtocolErrorResponse', { error: message });
148 149
					if (this.customTelemetryService) {
						this.customTelemetryService.publicLog('debugProtocolErrorResponse', { error: message });
J
Joao Moreno 已提交
150
					}
151
				}
I
isidor 已提交
152 153 154
				if (error && error.showUser === false) {
					return TPromise.as(null);
				}
E
Erich Gamma 已提交
155

156 157 158 159 160 161 162 163 164 165
				if (error && error.url) {
					const label = error.urlLabel ? error.urlLabel : nls.localize('moreInfo', "More Info");
					return TPromise.wrapError(errors.create(message, { actions: [CloseAction, new Action('debug.moreInfo', label, null, true, () => {
						shell.openExternal(error.url);
						return TPromise.as(null);
					})]}));
				}

				return TPromise.wrapError(new Error(message));
			});
166

167 168 169
			this.sentPromises.push(promise);
			return promise;
		});
E
Erich Gamma 已提交
170 171
	}

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
	protected onEvent(event: DebugProtocol.Event): void {
		if (event.body) {
			event.body.sessionId = this.getId();
		} else {
			event.body = { sessionId: this.getId() };
		}

		if (event.event === 'initialized') {
			this.readyForBreakpoints = true;
			this._onDidInitialize.fire(event);
		} else if (event.event === 'stopped') {
			this.emittedStopped = true;
			this._onDidStop.fire(<DebugProtocol.StoppedEvent>event);
		} else if (event.event === 'thread') {
			this._onDidThread.fire(<DebugProtocol.ThreadEvent>event);
		} else if (event.event === 'output') {
			this._onDidOutput.fire(<DebugProtocol.OutputEvent>event);
		} else if (event.event === 'breakpoint') {
			this._onDidBreakpoint.fire(<DebugProtocol.BreakpointEvent>event);
		} else if (event.event === 'terminated') {
192
			this._onDidTerminateDebugee.fire(<SessionTerminatedEvent>event);
193
		} else if (event.event === 'exit') {
194
			this._onDidExitAdapter.fire(<SessionExitedEvent>event);
195
		}
196 197

		this._onDidEvent.fire(event);
198 199
	}

I
isidor 已提交
200 201 202 203 204 205 206 207
	public get configuration(): { type: string, isAttach: boolean, capabilities: DebugProtocol.Capabilites } {
		return {
			type: this.adapter.type,
			isAttach: this.isAttach,
			capabilities: this.capabilities || {}
		};
	}

E
Erich Gamma 已提交
208
	public initialize(args: DebugProtocol.InitializeRequestArguments): TPromise<DebugProtocol.InitializeResponse> {
I
isidor 已提交
209
		return this.send('initialize', args).then(response => {
I
isidor 已提交
210
			this.capabilities = response.body;
I
isidor 已提交
211 212
			return response;
		});
E
Erich Gamma 已提交
213 214 215
	}

	public launch(args: DebugProtocol.LaunchRequestArguments): TPromise<DebugProtocol.LaunchResponse> {
216
		this.isAttach = false;
217
		return this.send('launch', args);
E
Erich Gamma 已提交
218 219 220
	}

	public attach(args: DebugProtocol.AttachRequestArguments): TPromise<DebugProtocol.AttachResponse> {
221
		this.isAttach = true;
222
		return this.send('attach', args);
E
Erich Gamma 已提交
223 224
	}

225
	public next(args: DebugProtocol.NextArguments): TPromise<DebugProtocol.NextResponse> {
226
		return this.send('next', args);
E
Erich Gamma 已提交
227 228 229
	}

	public stepIn(args: DebugProtocol.StepInArguments): TPromise<DebugProtocol.StepInResponse> {
230
		return this.send('stepIn', args);
E
Erich Gamma 已提交
231 232 233
	}

	public stepOut(args: DebugProtocol.StepOutArguments): TPromise<DebugProtocol.StepOutResponse> {
234
		return this.send('stepOut', args);
E
Erich Gamma 已提交
235 236
	}

237 238 239 240
	public stepBack(args: DebugProtocol.StepBackArguments): TPromise<DebugProtocol.StepBackResponse> {
		return this.send('stepBack', args);
	}

E
Erich Gamma 已提交
241
	public continue(args: DebugProtocol.ContinueArguments): TPromise<DebugProtocol.ContinueResponse> {
242
		return this.send('continue', args);
E
Erich Gamma 已提交
243 244 245 246 247 248
	}

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

249 250 251 252
	public disconnect(restart = false, force = false): TPromise<DebugProtocol.DisconnectResponse> {
		if (this.stopServerPending && force) {
			return this.stopServer();
		}
253

254
		// Cancel all sent promises on disconnect so debug trees are not left in a broken state #3666.
255 256 257 258 259
		// Give a 1s timeout to give a chance for some promises to complete.
		setTimeout(() => {
			this.sentPromises.forEach(p => p.cancel());
			this.sentPromises = [];
		}, 1000);
260

E
Erich Gamma 已提交
261
		if ((this.serverProcess || this.socket) && !this.stopServerPending) {
262 263
			// point of no return: from now on don't report any errors
			this.stopServerPending = true;
264
			this.restarted = restart;
I
isidor 已提交
265
			return this.send('disconnect', { restart: restart }).then(() => this.stopServer(), () => this.stopServer());
E
Erich Gamma 已提交
266 267
		}

A
Alex Dima 已提交
268
		return TPromise.as(null);
E
Erich Gamma 已提交
269 270 271 272 273 274
	}

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

I
isidor 已提交
275 276 277 278
	public setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments): TPromise<DebugProtocol.SetFunctionBreakpointsResponse> {
		return this.send('setFunctionBreakpoints', args);
	}

E
Erich Gamma 已提交
279 280 281 282
	public setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): TPromise<DebugProtocol.SetExceptionBreakpointsResponse> {
		return this.send('setExceptionBreakpoints', args);
	}

283 284 285 286
	public configurationDone(): TPromise<DebugProtocol.ConfigurationDoneResponse> {
		return this.send('configurationDone', null);
	}

E
Erich Gamma 已提交
287 288 289 290 291 292 293 294
	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);
	}

295
	public variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse> {
E
Erich Gamma 已提交
296 297 298
		return this.send('variables', args);
	}

299
	public source(args: DebugProtocol.SourceArguments): TPromise<DebugProtocol.SourceResponse> {
E
Erich Gamma 已提交
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
		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;
	}

I
isidor 已提交
315 316
	private connectServer(port: number): TPromise<void> {
		return new TPromise<void>((c, e) => {
317
			this.socket = net.createConnection(port, '127.0.0.1', () => {
E
Erich Gamma 已提交
318 319 320 321 322 323
				this.connect(this.socket, <any>this.socket);
				c(null);
			});
			this.socket.on('error', (err: any) => {
				e(err);
			});
I
isidor 已提交
324
			this.socket.on('close', () => this.onServerExit());
E
Erich Gamma 已提交
325 326 327
		});
	}

I
isidor 已提交
328
	private startServer(): TPromise<any> {
E
Erich Gamma 已提交
329
		if (!this.adapter.program) {
I
isidor 已提交
330
			return TPromise.wrapError(new Error(nls.localize('noDebugAdapterExtensionInstalled', "No extension installed for '{0}' debugging.", this.adapter.type)));
E
Erich Gamma 已提交
331 332 333 334 335 336
		}

		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 已提交
337 338 339 340
			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 已提交
341
			this.serverProcess.stderr.on('data', (data: string) => {
342
				this.outputService.getChannel(ExtensionsChannelId).append(sanitize(data));
E
Erich Gamma 已提交
343 344 345 346 347 348
			});

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

I
isidor 已提交
349 350
	private launchServer(launch: { command: string, argv: string[] }): TPromise<void> {
		return new TPromise<void>((c, e) => {
E
Erich Gamma 已提交
351
			if (launch.command === 'node') {
352
				stdfork.fork(launch.argv[0], launch.argv.slice(1), {}, (err, child) => {
E
Erich Gamma 已提交
353
					if (err) {
354
						e(new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", launch.argv[0])));
E
Erich Gamma 已提交
355 356
					}
					this.serverProcess = child;
I
isidor 已提交
357
					c(null);
E
Erich Gamma 已提交
358 359 360 361 362 363 364 365 366
				});
			} else {
				this.serverProcess = cp.spawn(launch.command, launch.argv, {
					stdio: [
						'pipe', 	// stdin
						'pipe', 	// stdout
						'pipe'		// stderr
					],
				});
I
isidor 已提交
367
				c(null);
E
Erich Gamma 已提交
368 369 370 371
			}
		});
	}

I
isidor 已提交
372
	private stopServer(): TPromise<any> {
E
Erich Gamma 已提交
373 374 375 376

		if (this.socket !== null) {
			this.socket.end();
			this.cachedInitServer = null;
377
			this.onEvent({ event: 'exit', type: 'event', seq: 0 });
E
Erich Gamma 已提交
378 379 380
		}

		if (!this.serverProcess) {
I
isidor 已提交
381
			return TPromise.as(null);
E
Erich Gamma 已提交
382 383 384 385
		}

		this.stopServerPending = true;

I
isidor 已提交
386
		let ret: TPromise<void>;
E
Erich Gamma 已提交
387 388 389 390
		// 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 已提交
391
			ret = new TPromise<void>((c, e) => {
I
isidor 已提交
392
				const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess.pid}`, function (err, stdout, stderr) {
E
Erich Gamma 已提交
393 394 395 396 397 398 399 400 401
					if (err) {
						return e(err);
					}
				});
				killer.on('exit', c);
				killer.on('error', e);
			});
		} else {
			this.serverProcess.kill('SIGTERM');
I
isidor 已提交
402
			ret = TPromise.as(null);
E
Erich Gamma 已提交
403 404 405 406 407 408
		}

		return ret;
	}

	private getLaunchDetails(): TPromise<{ command: string; argv: string[]; }> {
I
isidor 已提交
409
		return new TPromise((c, e) => {
E
Erich Gamma 已提交
410 411
			fs.exists(this.adapter.program, exists => {
				if (exists) {
412
					c(null);
E
Erich Gamma 已提交
413
				} else {
414
					e(new Error(nls.localize('debugAdapterBinNotFound', "Debug adapter executable '{0}' not found.", this.adapter.program)));
E
Erich Gamma 已提交
415 416
				}
			});
417
		}).then(() => {
E
Erich Gamma 已提交
418 419 420
			if (this.adapter.runtime) {
				return {
					command: this.adapter.runtime,
421
					argv: (this.adapter.runtimeArgs || []).concat([this.adapter.program]).concat(this.adapter.args || [])
E
Erich Gamma 已提交
422 423 424 425
				};
			}

			return {
426 427
				command: this.adapter.program,
				argv: this.adapter.args || []
E
Erich Gamma 已提交
428 429 430 431
			};
		});
	}

432 433
	protected onServerError(err: Error): void {
		this.messageService.show(severity.Error, nls.localize('stoppingDebugAdapter', "{0}. Stopping the debug adapter.", err.message));
E
Erich Gamma 已提交
434 435 436 437 438 439 440
		this.stopServer().done(null, errors.onUnexpectedError);
	}

	private onServerExit(): void {
		this.serverProcess = null;
		this.cachedInitServer = null;
		if (!this.stopServerPending) {
441
			this.messageService.show(severity.Error, nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
E
Erich Gamma 已提交
442
		}
443
		this.onEvent({ event: 'exit', type: 'event', seq: 0 });
E
Erich Gamma 已提交
444
	}
I
isidor 已提交
445 446 447 448

	public dispose(): void {
		this.disconnect().done(null, errors.onUnexpectedError);
	}
E
Erich Gamma 已提交
449
}