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