OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library debugger; | |
6 | |
7 import "dart:async"; | |
8 import "dart:io"; | |
9 | |
10 import "package:ddbg/commando.dart"; | |
11 import "package:observatory/service_io.dart"; | |
12 | |
13 class Debugger { | |
14 Commando cmdo; | |
15 var _cmdoSubscription; | |
16 | |
17 CommandList _commands; | |
18 | |
19 VM _vm; | |
20 VM get vm => _vm; | |
21 set vm(VM vm) { | |
22 if (_vm == vm) { | |
23 // Do nothing. | |
24 return; | |
25 } | |
26 if (_vm != null) { | |
27 _vm.disconnect(); | |
28 } | |
29 if (vm != null) { | |
30 vm.onConnect.then(_vmConnected); | |
31 vm.onDisconnect.then(_vmDisconnected); | |
32 vm.errors.stream.listen(_onServiceError); | |
33 vm.exceptions.stream.listen(_onServiceException); | |
34 vm.events.stream.listen(_onServiceEvent); | |
35 } | |
36 _vm = vm; | |
37 } | |
38 | |
39 _vmConnected(VM vm) { | |
40 cmdo.print('Connected to vm'); | |
41 } | |
42 | |
43 _vmDisconnected(VM vm) { | |
44 cmdo.print('Disconnected from vm'); | |
45 } | |
46 | |
47 _onServiceError(ServiceError error) { | |
48 cmdo.print('error $error'); | |
49 } | |
50 | |
51 _onServiceException(ServiceException exception) { | |
52 cmdo.print('${exception.message}'); | |
53 } | |
54 | |
55 _onServiceEvent(ServiceEvent event) { | |
56 switch (event.eventType) { | |
57 case 'GC': | |
58 // Ignore GC events for now. | |
59 break; | |
60 default: | |
61 cmdo.print('event $event'); | |
62 break; | |
63 } | |
64 } | |
65 | |
66 VM _isolate; | |
67 VM get isolate => _isolate; | |
68 set isolate(Isolate isolate) { | |
69 _isolate = isolate; | |
70 cmdo.print('Current isolate is now isolate ${getIsolateIndex(_isolate)}'); | |
71 } | |
72 | |
73 Map _isolateIndexMap = new Map(); | |
74 int _nextIsolateIndex = 0; | |
75 int getIsolateIndex(Isolate isolate) { | |
76 var index = _isolateIndexMap[isolate.id]; | |
77 if (index == null) { | |
78 index = _nextIsolateIndex++; | |
79 _isolateIndexMap[isolate.id] = index; | |
80 } | |
81 return index; | |
82 } | |
83 | |
84 void onUncaughtError(error, StackTrace trace) { | |
85 if (error is ServiceException || | |
86 error is ServiceError) { | |
87 // These are handled elsewhere. Ignore. | |
88 return; | |
89 } | |
90 cmdo.print('\n--------\nExiting due to unexpected error:\n' | |
91 ' $error\n$trace\n'); | |
92 quit(); | |
93 } | |
94 | |
95 Debugger() { | |
96 cmdo = new Commando(completer: _completeCommand); | |
97 _cmdoSubscription = cmdo.commands.listen(_processCommand, | |
98 onError: _cmdoError, | |
99 onDone: _cmdoDone); | |
100 _commands = new CommandList(); | |
101 _commands.register(new AttachCommand()); | |
102 _commands.register(new DetachCommand()); | |
103 _commands.register(new HelpCommand(_commands)); | |
104 _commands.register(new IsolateCommand()); | |
105 _commands.register(new QuitCommand()); | |
106 } | |
107 | |
108 Future _closeCmdo() { | |
109 var sub = _cmdoSubscription; | |
110 _cmdoSubscription = null; | |
111 cmdo = null; | |
112 | |
113 var future = sub.cancel(); | |
114 if (future != null) { | |
115 return future; | |
116 } else { | |
117 return new Future.value(); | |
118 } | |
119 } | |
120 | |
121 Future quit() { | |
122 return Future.wait([_closeCmdo()]).then((_) { | |
123 exit(0); | |
124 }); | |
125 } | |
126 | |
127 void _cmdoError(error, StackTrace trace) { | |
128 cmdo.print('\n--------\nExiting due to unexpected error:\n' | |
129 ' $error\n$trace\n'); | |
130 quit(); | |
131 } | |
132 | |
133 void _cmdoDone() { | |
134 quit(); | |
135 } | |
136 | |
137 List<String> _completeCommand(List<String> commandParts) { | |
138 return _commands.complete(commandParts); | |
139 } | |
140 | |
141 void _processCommand(String cmdLine) { | |
142 void huh() { | |
143 cmdo.print("'$cmdLine' not understood, try 'help' for help."); | |
144 } | |
145 | |
146 cmdo.hide(); | |
147 cmdLine = cmdLine.trim(); | |
148 var args = cmdLine.split(' '); | |
149 if (args.length == 0) { | |
150 return; | |
151 } | |
152 var command = args[0]; | |
153 var matches = _commands.match(command, true); | |
154 if (matches.length == 0) { | |
155 huh(); | |
156 cmdo.show(); | |
157 } else if (matches.length == 1) { | |
158 matches[0].run(this, args).then((_) { | |
159 cmdo.show(); | |
160 }); | |
161 } else { | |
162 var matchNames = matches.map((handler) => handler.name); | |
163 cmdo.print("Ambiguous command '$command' : ${matchNames.toList()}"); | |
164 cmdo.show(); | |
165 } | |
166 } | |
167 | |
168 } | |
169 | |
170 // Every debugger command extends this base class. | |
171 abstract class Command { | |
172 String get name; | |
173 String get helpShort; | |
174 void printHelp(Debugger debugger, List<String> args); | |
175 Future run(Debugger debugger, List<String> args); | |
176 List<String> complete(List<String> commandParts) { | |
177 return ["$name ${commandParts.join(' ')}"]; | |
178 | |
179 } | |
180 } | |
181 | |
182 class AttachCommand extends Command { | |
183 final name = 'attach'; | |
184 final helpShort = 'Attach to a running Dart VM'; | |
185 void printHelp(Debugger debugger, List<String> args) { | |
186 debugger.cmdo.print(''' | |
187 ----- attach ----- | |
188 | |
189 Attach to the Dart VM running at the indicated host:port. If no | |
190 host:port is provided, attach to the VM running on the default port. | |
191 | |
192 Usage: | |
193 attach | |
194 attach <host:port> | |
195 '''); | |
196 } | |
197 | |
198 Future run(Debugger debugger, List<String> args) { | |
199 var cmdo = debugger.cmdo; | |
200 if (args.length > 2) { | |
201 cmdo.print('$name expects 0 or 1 arguments'); | |
202 return new Future.value(); | |
203 } | |
204 String hostPort = 'localhost:8181'; | |
205 if (args.length > 1) { | |
206 hostPort = args[1]; | |
207 } | |
208 | |
209 debugger.vm = new WebSocketVM(new WebSocketVMTarget('ws://${hostPort}/ws')); | |
210 return debugger.vm.load().then((vm) { | |
211 if (debugger.isolate == null) { | |
212 for (var isolate in vm.isolates) { | |
213 if (isolate.name == 'root') { | |
214 debugger.isolate = isolate; | |
215 } | |
216 } | |
217 } | |
218 }); | |
219 } | |
220 } | |
221 | |
222 class CommandList { | |
223 List _commands = new List<Command>(); | |
224 | |
225 void register(Command cmd) { | |
226 _commands.add(cmd); | |
227 } | |
228 | |
229 List<Command> match(String commandName, bool exactMatchWins) { | |
230 var matches = []; | |
231 for (var command in _commands) { | |
232 if (command.name.startsWith(commandName)) { | |
233 if (exactMatchWins && command.name == commandName) { | |
234 // Exact match | |
235 return [command]; | |
236 } else { | |
237 matches.add(command); | |
238 } | |
239 } | |
240 } | |
241 return matches; | |
242 } | |
243 | |
244 List<String> complete(List<String> commandParts) { | |
245 var completions = new List<String>(); | |
246 String prefix = commandParts[0]; | |
247 for (var command in _commands) { | |
248 if (command.name.startsWith(prefix)) { | |
249 completions.addAll(command.complete(commandParts.sublist(1))); | |
250 } | |
251 } | |
252 return completions; | |
253 } | |
254 | |
255 void printHelp(Debugger debugger, List<String> args) { | |
256 var cmdo = debugger.cmdo; | |
257 if (args.length <= 1) { | |
258 cmdo.print("\nDebugger commands:\n"); | |
259 for (var command in _commands) { | |
260 cmdo.print(' ${command.name.padRight(11)} ${command.helpShort}'); | |
261 } | |
262 cmdo.print("For more information about a particular command, type:\n\n" | |
263 " help <command>\n"); | |
264 | |
265 cmdo.print("Commands may be abbreviated: e.g. type 'h' for 'help.\n"); | |
266 } else { | |
267 var commandName = args[1]; | |
268 var matches =match(commandName, true); | |
269 if (matches.length == 0) { | |
270 cmdo.print("Command '$commandName' not recognized. " | |
271 "Try 'help' for a list of commands."); | |
272 } else { | |
273 for (var command in matches) { | |
274 command.printHelp(debugger, args); | |
275 } | |
276 } | |
277 } | |
278 } | |
279 } | |
280 | |
281 class DetachCommand extends Command { | |
282 final name = 'detach'; | |
283 final helpShort = 'Detach from a running Dart VM'; | |
284 void printHelp(Debugger debugger, List<String> args) { | |
285 debugger.cmdo.print(''' | |
286 ----- detach ----- | |
287 | |
288 Detach from the Dart VM. | |
289 | |
290 Usage: | |
291 detach | |
292 '''); | |
293 } | |
294 | |
295 Future run(Debugger debugger, List<String> args) { | |
296 var cmdo = debugger.cmdo; | |
297 if (args.length > 1) { | |
298 cmdo.print('$name expects no arguments'); | |
299 return new Future.value(); | |
300 } | |
301 if (debugger.vm == null) { | |
302 cmdo.print('No VM is attached'); | |
303 } else { | |
304 debugger.vm = null; | |
305 } | |
306 return new Future.value(); | |
307 } | |
308 } | |
309 | |
310 class HelpCommand extends Command { | |
311 HelpCommand(this._commands); | |
312 final CommandList _commands; | |
313 | |
314 final name = 'help'; | |
315 final helpShort = 'Show a list of debugger commands'; | |
316 void printHelp(Debugger debugger, List<String> args) { | |
317 debugger.cmdo.print(''' | |
318 ----- help ----- | |
319 | |
320 Show a list of debugger commands or get more information about a | |
321 particular command. | |
322 | |
323 Usage: | |
324 help | |
325 help <command> | |
326 '''); | |
327 } | |
328 | |
329 Future run(Debugger debugger, List<String> args) { | |
330 _commands.printHelp(debugger, args); | |
331 return new Future.value(); | |
332 } | |
333 | |
334 List<String> complete(List<String> commandParts) { | |
335 if (commandParts.isEmpty) { | |
336 return ['$name ']; | |
337 } | |
338 return _commands.complete(commandParts).map((value) { | |
339 return '$name $value'; | |
340 }); | |
341 } | |
342 } | |
343 | |
344 class IsolateCommand extends Command { | |
345 final name = 'isolate'; | |
346 final helpShort = 'Isolate control'; | |
347 void printHelp(Debugger debugger, List<String> args) { | |
348 debugger.cmdo.print(''' | |
349 ----- isolate ----- | |
350 | |
351 List all isolates. | |
352 | |
353 Usage: | |
354 isolate | |
355 isolate list | |
356 | |
357 Set current isolate. | |
358 | |
359 Usage: | |
360 isolate <id> | |
361 '''); | |
362 } | |
363 | |
364 Future run(Debugger debugger, List<String> args) { | |
365 var cmdo = debugger.cmdo; | |
366 if (args.length == 1 || | |
367 (args.length == 2 && args[1] == 'list')) { | |
368 return _listIsolates(debugger); | |
369 } else if (args.length == 2) { | |
370 cmdo.print('UNIMPLEMENTED'); | |
371 return new Future.value(); | |
372 } else { | |
373 if (args.length > 1) { | |
374 cmdo.print('Unrecognized isolate command'); | |
375 printHelp(debugger, []); | |
376 return new Future.value(); | |
377 } | |
378 } | |
379 } | |
380 | |
381 Future _listIsolates(Debugger debugger) { | |
382 var cmdo = debugger.cmdo; | |
383 if (debugger.vm == null) { | |
384 cmdo.print('No VM is attached'); | |
385 return new Future.value(); | |
386 } | |
387 return debugger.vm.reload().then((vm) { | |
388 // Sort the isolates by their indices. | |
389 var isolates = vm.isolates.toList(); | |
390 isolates.sort((iso1, iso2) { | |
391 return (debugger.getIsolateIndex(iso1) - | |
392 debugger.getIsolateIndex(iso2)); | |
393 }); | |
394 | |
395 StringBuffer sb = new StringBuffer(); | |
396 cmdo.print(' ID NAME STATE'); | |
397 cmdo.print('-----------------------------------------------'); | |
398 for (var isolate in isolates) { | |
399 if (isolate == debugger.isolate) { | |
400 sb.write('* '); | |
401 } else { | |
402 sb.write(' '); | |
403 } | |
404 sb.write(debugger.getIsolateIndex(isolate).toString().padRight(8)); | |
405 sb.write(' '); | |
406 sb.write(isolate.name.padRight(12)); | |
407 sb.write(' '); | |
408 if (isolate.pauseEvent != null) { | |
409 switch (isolate.pauseEvent.eventType) { | |
410 case 'IsolateCreated': | |
411 sb.write('paused at isolate start'); | |
412 break; | |
413 case 'IsolateShutdown': | |
414 sb.write('paused at isolate exit'); | |
415 break; | |
416 case 'IsolateInterrupted': | |
417 sb.write('paused'); | |
418 break; | |
419 case 'BreakpointReached': | |
420 sb.write('paused by breakpoint'); | |
421 break; | |
422 case 'ExceptionThrown': | |
423 sb.write('paused by exception'); | |
424 break; | |
425 default: | |
426 sb.write('paused by unknown cause'); | |
427 break; | |
428 } | |
429 } else if (isolate.running) { | |
430 sb.write('running'); | |
431 } else if (isolate.idle) { | |
432 sb.write('idle'); | |
433 } else if (isolate.loading) { | |
434 // TODO(turnidge): This is weird in a command line debugger. | |
435 sb.write('(not available)'); | |
436 } | |
437 sb.write('\n'); | |
438 } | |
439 cmdo.print(sb); | |
440 }); | |
441 return new Future.value(); | |
442 } | |
443 | |
444 List<String> complete(List<String> commandParts) { | |
445 if (commandParts.isEmpty) { | |
446 return ['$name ${commandParts.join(" ")}']; | |
447 } else { | |
448 var completions = _commands.complete(commandParts); | |
449 return completions.map((completion) { | |
450 return '$name $completion'; | |
451 }); | |
452 } | |
453 } | |
454 } | |
455 | |
456 class QuitCommand extends Command { | |
457 final name = 'quit'; | |
458 final helpShort = 'Quit the debugger.'; | |
459 void printHelp(Debugger debugger, List<String> args) { | |
460 debugger.cmdo.print(''' | |
461 ----- quit ----- | |
462 | |
463 Quit the debugger. | |
464 | |
465 Usage: | |
466 quit | |
467 '''); | |
468 } | |
469 | |
470 Future run(Debugger debugger, List<String> args) { | |
471 var cmdo = debugger.cmdo; | |
472 if (args.length > 1) { | |
473 cmdo.print("Unexpected arguments to $name command."); | |
474 return new Future.value(); | |
475 } | |
476 return debugger.quit(); | |
477 } | |
478 } | |
OLD | NEW |