Chromium Code Reviews| 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 |