Index: tools/ddbg_service/lib/debugger.dart |
diff --git a/tools/ddbg_service/lib/debugger.dart b/tools/ddbg_service/lib/debugger.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a5dbb6d9cb64db557729aaa6e66c220c0ba10f31 |
--- /dev/null |
+++ b/tools/ddbg_service/lib/debugger.dart |
@@ -0,0 +1,437 @@ |
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library debugger; |
+ |
+import "dart:async"; |
+import "dart:io"; |
+ |
+import "package:ddbg/commando.dart"; |
+import "package:observatory/service_io.dart"; |
+ |
+class Debugger { |
+ Commando cmdo; |
+ var _cmdoSubscription; |
+ |
+ VM _vm; |
+ VM get vm => _vm; |
+ set vm(VM vm) { |
+ if (_vm == vm) { |
+ // Do nothing. |
+ return; |
+ } |
+ if (_vm != null) { |
+ _vm.disconnect(); |
+ } |
+ if (vm != null) { |
+ vm.onConnect.then(_vmConnected); |
+ vm.onDisconnect.then(_vmDisconnected); |
+ vm.errors.stream.listen(_onServiceError); |
+ vm.exceptions.stream.listen(_onServiceException); |
+ vm.events.stream.listen(_onServiceEvent); |
+ } |
+ _vm = vm; |
+ } |
+ |
+ _vmConnected(VM vm) { |
+ cmdo.print('Connected to vm'); |
+ } |
+ |
+ _vmDisconnected(VM vm) { |
+ cmdo.print('Disconnected from vm'); |
+ } |
+ |
+ _onServiceError(ServiceError error) { |
+ cmdo.print('error $error'); |
+ } |
+ |
+ _onServiceException(ServiceException exception) { |
+ cmdo.print('${exception.message}'); |
+ } |
+ |
+ _onServiceEvent(ServiceEvent event) { |
+ switch (event.eventType) { |
+ case 'GC': |
+ // Ignore GC events for now. |
+ break; |
+ default: |
+ cmdo.print('event $event'); |
+ break; |
+ } |
+ } |
+ |
+ VM _isolate; |
+ VM get isolate => _isolate; |
+ set isolate(Isolate isolate) { |
+ _isolate = isolate; |
+ cmdo.print('Current isolate is now isolate ${getIsolateIndex(_isolate)}'); |
+ } |
+ |
+ Map _isolateIndexMap = new Map(); |
+ int _nextIsolateIndex = 0; |
+ int getIsolateIndex(Isolate isolate) { |
+ var index = _isolateIndexMap[isolate.id]; |
+ if (index == null) { |
+ index = _nextIsolateIndex++; |
+ _isolateIndexMap[isolate.id] = index; |
+ } |
+ return index; |
+ } |
+ |
+ void onUncaughtError(error, StackTrace trace) { |
+ if (error is ServiceException || |
+ error is ServiceError) { |
+ // These are handled elsewhere. Ignore. |
+ return; |
+ } |
+ cmdo.print('\n--------\nExiting due to unexpected error:\n' |
+ ' $error\n$trace\n'); |
+ quit(); |
+ } |
+ |
+ Debugger() { |
+ cmdo = new Commando(completer: completeCommand); |
+ _cmdoSubscription = cmdo.commands.listen(_processCommand, |
+ onError: _cmdoError, |
+ onDone: _cmdoDone); |
+ } |
+ |
+ Future _closeCmdo() { |
+ var sub = _cmdoSubscription; |
+ _cmdoSubscription = null; |
+ cmdo = null; |
+ |
+ var future = sub.cancel(); |
+ if (future != null) { |
+ return future; |
+ } else { |
+ return new Future.value(); |
+ } |
+ } |
+ |
+ Future quit() { |
+ return Future.wait([_closeCmdo()]).then((_) { |
+ exit(0); |
+ }); |
+ } |
+ |
+ void _cmdoError(self, parent, zone, error, StackTrace trace) { |
+ cmdo.print('\n--------\nExiting due to unexpected error:\n' |
+ ' $error\n$trace\n'); |
+ quit(); |
+ } |
+ |
+ void _cmdoDone() { |
+ quit(); |
+ } |
+ |
+ void _processCommand(String cmdLine) { |
+ void huh() { |
+ cmdo.print("'$cmdLine' not understood, try 'help' for help."); |
+ } |
+ |
+ cmdo.hide(); |
+ cmdLine = cmdLine.trim(); |
+ var args = cmdLine.split(' '); |
+ if (args.length == 0) { |
+ return; |
+ } |
+ var command = args[0]; |
+ var matches = matchCommand(command, true); |
+ if (matches.length == 0) { |
+ huh(); |
+ cmdo.show(); |
+ } else if (matches.length == 1) { |
+ matches[0].run(this, args).then((_) { |
+ cmdo.show(); |
+ }); |
+ } else { |
+ var matchNames = matches.map((handler) => handler.name); |
+ cmdo.print("Ambigous command '$command' : ${matchNames.toList()}"); |
+ cmdo.show(); |
+ } |
+ } |
+ |
+} |
+ |
+// Every debugger command extends this base class. |
+abstract class Command { |
+ String get name; |
+ String get helpShort; |
+ void printHelp(Debugger debugger, List<String> args); |
+ Future run(Debugger debugger, List<String> args); |
+} |
+ |
+class AttachCommand extends Command { |
+ final name = 'attach'; |
+ final helpShort = 'Attach to a running Dart VM'; |
+ void printHelp(Debugger debugger, List<String> args) { |
+ debugger.cmdo.print(''' |
+----- attach ----- |
+ |
+Attach to the Dart VM running at the indicated host:port. If no |
+host:port is provided, attach to the VM running on the default port. |
+ |
+Usage: |
+ attach |
+ attach <host:port> |
+'''); |
+ } |
+ |
+ Future run(Debugger debugger, List<String> args) { |
+ var cmdo = debugger.cmdo; |
+ if (args.length > 2) { |
+ cmdo.print('$name expects 0 or 1 arguments'); |
+ return new Future.value(); |
+ } |
+ String hostPort = 'localhost:8181'; |
+ if (args.length > 1) { |
+ hostPort = args[1]; |
+ } |
+ |
+ debugger.vm = new WebSocketVM(new WebSocketVMTarget('ws://${hostPort}/ws')); |
+ return debugger.vm.load().then((vm) { |
+ if (debugger.isolate == null) { |
+ for (var isolate in vm.isolates) { |
+ if (isolate.name == 'root') { |
+ debugger.isolate = isolate; |
+ } |
+ } |
+ } |
+ }); |
+ } |
+} |
+ |
+class DetachCommand extends Command { |
+ final name = 'detach'; |
+ final helpShort = 'Detach from a running Dart VM'; |
+ void printHelp(Debugger debugger, List<String> args) { |
+ cmdo.print(''' |
+----- detach ----- |
+ |
+Detach from the Dart VM. |
+ |
+Usage: |
+ detach |
+'''); |
+ } |
+ |
+ Future run(Debugger debugger, List<String> args) { |
+ var cmdo = debugger.cmdo; |
+ if (args.length > 1) { |
+ cmdo.print('$name expects no arguments'); |
+ return new Future.value(); |
+ } |
+ if (debugger.vm == null) { |
+ cmdo.print('No VM is attached'); |
+ } else { |
+ debugger.vm = null; |
+ } |
+ return new Future.value(); |
+ } |
+} |
+ |
+class HelpCommand extends Command { |
+ final name = 'help'; |
+ final helpShort = 'Show a list of debugger commands'; |
+ void printHelp(Debugger debugger, List<String> args) { |
+ debugger.cmdo.print(''' |
+----- help ----- |
+ |
+Show a list of debugger commands or get more information about a |
+particular command. |
+ |
+Usage: |
+ help |
+ help <command> |
+'''); |
+ } |
+ |
+ Future run(Debugger debugger, List<String> args) { |
+ var cmdo = debugger.cmdo; |
+ if (args.length <= 1) { |
+ cmdo.print("\nDebugger commands:\n"); |
+ for (var command in commandList) { |
+ cmdo.print(' ${command.name.padRight(11)} ${command.helpShort}'); |
+ } |
+ cmdo.print("For more information about a particular command, type:\n\n" |
+ " help <command>\n"); |
+ |
+ cmdo.print("Commands may be abbreviated: e.g. type 'h' for 'help.\n"); |
+ } else { |
+ var commandName = args[1]; |
+ var matches = matchCommand(commandName, true); |
+ if (matches.length == 0) { |
+ cmdo.print("Command '$commandName' not recognized. " |
+ "Try 'help' for a list of commands."); |
+ } else { |
+ for (var command in matches) { |
+ command.printHelp(debugger, args); |
+ } |
+ } |
+ } |
+ |
+ return new Future.value(); |
+ } |
+} |
+ |
+class IsolateCommand extends Command { |
+ final name = 'isolate'; |
+ final helpShort = 'Isolate control'; |
+ void printHelp(Debugger debugger, List<String> args) { |
+ debugger.cmdo.print(''' |
+----- isolate ----- |
+ |
+List all isolates. |
+ |
+Usage: |
+ isolate |
+ isolate list |
+ |
+Set current isolate. |
+ |
+Usage: |
+ isolate <id> |
+'''); |
+ } |
+ |
+ Future run(Debugger debugger, List<String> args) { |
+ var cmdo = debugger.cmdo; |
+ if (args.length == 1 || |
+ (args.length == 2 && args[1] == 'list')) { |
+ return _listIsolates(debugger); |
+ } else if (args.length == 2) { |
+ cmdo.print('UNIMPLEMENTED'); |
+ return new Future.value(); |
+ } else { |
+ if (args.length > 1) { |
+ cmdo.print('Unrecognized isolate command'); |
+ printHelp(debugger, []); |
+ return new Future.value(); |
+ } |
+ } |
+ } |
+ |
+ Future _listIsolates(Debugger debugger) { |
+ var cmdo = debugger.cmdo; |
+ if (debugger.vm == null) { |
+ cmdo.print('No VM is attached'); |
+ return new Future.value(); |
+ } |
+ return debugger.vm.reload().then((vm) { |
+ // Sort the isolates by their indices. |
+ var isolates = vm.isolates.toList(); |
+ isolates.sort((iso1, iso2) { |
+ return (debugger.getIsolateIndex(iso1) - |
+ debugger.getIsolateIndex(iso2)); |
+ }); |
+ |
+ StringBuffer sb = new StringBuffer(); |
+ cmdo.print(' ID NAME STATE'); |
+ cmdo.print('-----------------------------------------------'); |
+ for (var isolate in isolates) { |
+ if (isolate == debugger.isolate) { |
+ sb.write('* '); |
+ } else { |
+ sb.write(' '); |
+ } |
+ sb.write(debugger.getIsolateIndex(isolate).toString().padRight(8)); |
+ sb.write(' '); |
+ sb.write(isolate.name.padRight(12)); |
+ sb.write(' '); |
+ if (isolate.pauseEvent != null) { |
+ switch (isolate.pauseEvent.eventType) { |
+ case 'IsolateCreated': |
+ sb.write('paused at isolate start'); |
+ break; |
+ case 'IsolateShutdown': |
+ sb.write('paused at isolate exit'); |
+ break; |
+ case 'IsolateInterrupted': |
+ sb.write('paused'); |
+ break; |
+ case 'BreakpointReached': |
+ sb.write('paused by breakpoint'); |
+ break; |
+ case 'ExceptionThrown': |
+ sb.write('paused by exception'); |
+ break; |
+ default: |
+ sb.write('paused by unknown cause'); |
+ break; |
+ } |
+ } else if (isolate.running) { |
+ sb.write('running'); |
+ } else if (isolate.idle) { |
+ sb.write('idle'); |
+ } else if (isolate.loading) { |
+ // TODO(turnidge): This is weird in a command line debugger. |
+ sb.write('(not available)'); |
+ } |
+ sb.write('\n'); |
+ } |
+ cmdo.print(sb); |
+ }); |
+ return new Future.value(); |
+ } |
+} |
+ |
+class QuitCommand extends Command { |
+ final name = 'quit'; |
+ final helpShort = 'Quit the debugger.'; |
+ void printHelp(Debugger debugger, List<String> args) { |
+ debugger.cmdo.print(''' |
+----- quit ----- |
+ |
+Quit the debugger. |
+ |
+Usage: |
+ quit |
+'''); |
+ } |
+ |
+ Future run(Debugger debugger, List<String> args) { |
+ var cmdo = debugger.cmdo; |
+ if (args.length > 1) { |
+ cmdo.print("Unexpected arguments to $name command."); |
+ return new Future.value(); |
+ } |
+ return debugger.quit(); |
+ } |
+} |
+ |
+List<Command> commandList = |
+ [ new AttachCommand(), |
+ new DetachCommand(), |
+ new HelpCommand(), |
+ new IsolateCommand(), |
+ new QuitCommand() ]; |
+ |
+List<Command> matchCommand(String commandName, bool exactMatchWins) { |
+ var matches = []; |
+ for (var command in commandList) { |
+ if (command.name.startsWith(commandName)) { |
+ if (exactMatchWins && command.name == commandName) { |
+ // Exact match |
+ return [command]; |
+ } else { |
+ matches.add(command); |
+ } |
+ } |
+ } |
+ return matches; |
+} |
+ |
+List<String> completeCommand(List<String> commandParts) { |
+ var completions = new List<String>(); |
+ if (commandParts.length == 1) { |
+ String prefix = commandParts[0]; |
+ for (var command in commandList) { |
+ if (command.name.startsWith(prefix)) { |
+ completions.add(command.name); |
+ } |
+ } |
+ } |
+ return completions; |
+} |