| 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 |