Chromium Code Reviews| Index: tools/ddbg.dart |
| =================================================================== |
| --- tools/ddbg.dart (revision 30608) |
| +++ tools/ddbg.dart (working copy) |
| @@ -8,6 +8,7 @@ |
| import "dart:convert"; |
| import "dart:io"; |
| import "dart:async"; |
| +import "dart:math"; |
| import "ddbg/lib/commando.dart"; |
| @@ -26,11 +27,14 @@ |
| Socket vmSock; |
| String vmData; |
| +var cmdSubscription; |
| Commando cmdo; |
| var vmSubscription; |
| int seqNum = 0; |
| +bool isRunning = false; |
| Process targetProcess; |
| +bool suppressNextExitCode = false; |
| final verbose = false; |
| final printMessages = false; |
| @@ -38,39 +42,8 @@ |
| TargetIsolate currentIsolate; |
| TargetIsolate mainIsolate; |
| +int debugPort = 5858; |
| -void printHelp() { |
| - print(""" |
| - q Quit debugger shell |
| - bt Show backtrace |
| - r Resume execution |
| - s Single step |
| - so Step over |
| - si Step into |
| - sbp [<file>] <line> Set breakpoint |
| - rbp <id> Remove breakpoint with given id |
| - po <id> Print object info for given id |
| - eval obj <id> <expr> Evaluate expr on object id |
| - eval cls <id> <expr> Evaluate expr on class id |
| - eval lib <id> <expr> Evaluate expr in toplevel of library id |
| - pl <id> <idx> [<len>] Print list element/slice |
| - pc <id> Print class info for given id |
| - ll List loaded libraries |
| - plib <id> Print library info for given library id |
| - slib <id> <true|false> Set library id debuggable |
| - pg <id> Print all global variables visible within given library id |
| - ls <lib_id> List loaded scripts in library |
| - gs <lib_id> <script_url> Get source text of script in library |
| - tok <lib_id> <script_url> Get line and token table of script in library |
| - epi <none|all|unhandled> Set exception pause info |
| - li List ids of all isolates in the VM |
| - sci <id> Set current target isolate |
| - i <id> Interrupt execution of given isolate id |
| - h Print help |
| -"""); |
| -} |
| - |
| - |
| String formatLocation(Map location) { |
| if (location == null) return ""; |
| var fileName = location["url"].split("/").last; |
| @@ -78,13 +51,6 @@ |
| } |
| -void quitShell() { |
| - vmSubscription.cancel(); |
| - vmSock.close(); |
| - cmdo.done(); |
| -} |
| - |
| - |
| Future sendCmd(Map<String, dynamic> cmd) { |
| var completer = new Completer(); |
| int id = cmd["id"]; |
| @@ -113,6 +79,330 @@ |
| return false; |
| } |
| +// These settings are allowed in the 'set' and 'show' debugger commands. |
| +var validSettings = ['vm', 'vmargs', 'script', 'args']; |
| + |
| +// The current values for all settings. |
| +var settings = new Map(); |
| + |
| +// Generates a string of 'count' spaces. |
| +String _spaces(int count) { |
| + return new List.filled(count, ' ').join(''); |
| +} |
| + |
| +// TODO(turnidge): Move all commands here. |
| +List<Command> commandList = |
| + [ new HelpCommand(), |
| + new QuitCommand(), |
| + new RunCommand(), |
| + new KillCommand(), |
| + new SetCommand(), |
| + new ShowCommand() ]; |
| + |
| + |
| +Command matchCommand(String commandName, bool exactMatchWins) { |
| + List 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; |
| +} |
| + |
| +abstract class Command { |
| + String get name; |
| + Future run(List<String> args); |
| +} |
| + |
| +class HelpCommand extends Command { |
| + final name = 'help'; |
| + final helpShort = 'Show a list of debugger commands'; |
| + final helpLong =""" |
| +Show a list of debugger commands or get more information about a |
| +particular command. |
| + |
| +Usage: |
| + help |
| + help <command> |
| +"""; |
| + |
| + Future run(List<String> args) { |
| + if (args.length == 1) { |
| + print("Debugger commands:\n"); |
| + for (var command in commandList) { |
| + const tabStop = 12; |
| + var spaces = _spaces(max(1, (tabStop - command.name.length))); |
| + print(' ${command.name}${spaces}${command.helpShort}'); |
| + } |
| + |
| + // TODO(turnidge): Convert all commands to use the Command class. |
| + print(""" |
| + bt Show backtrace |
| + r Resume execution |
| + s Single step |
| + so Step over |
| + si Step into |
| + sbp [<file>] <line> Set breakpoint |
| + rbp <id> Remove breakpoint with given id |
| + po <id> Print object info for given id |
| + eval obj <id> <expr> Evaluate expr on object id |
| + eval cls <id> <expr> Evaluate expr on class id |
| + eval lib <id> <expr> Evaluate expr in toplevel of library id |
| + pl <id> <idx> [<len>] Print list element/slice |
| + pc <id> Print class info for given id |
| + ll List loaded libraries |
| + plib <id> Print library info for given library id |
| + slib <id> <true|false> Set library id debuggable |
| + pg <id> Print all global variables visible within given library id |
| + ls <lib_id> List loaded scripts in library |
| + gs <lib_id> <script_url> Get source text of script in library |
| + tok <lib_id> <script_url> Get line and token table of script in library |
| + epi <none|all|unhandled> Set exception pause info |
| + li List ids of all isolates in the VM |
| + sci <id> Set current target isolate |
| + i <id> Interrupt execution of given isolate id |
| +"""); |
| + |
| + print("For more information about a particular command, type:\n\n" |
| + " help <command>\n"); |
| + |
| + print("Commands may be abbreviated: e.g. type 'h' for 'help.\n"); |
| + } else if (args.length == 2) { |
| + var commandName = args[1]; |
| + var matches = matchCommand(commandName, true); |
| + if (matches.length == 0) { |
| + print("Command '$commandName' not recognized. " |
| + "Try 'help' for a list of commands."); |
| + } else if (matches.length == 1) { |
| + print(matches[0].helpLong); |
| + } else { |
| + var matchNames = matches.map((handler) => handler.name); |
|
hausner
2013/12/04 00:26:35
You could just print help for all the commands tha
turnidge
2013/12/04 19:35:21
Nice idea. Done.
|
| + print("Ambigous command '$commandName' : ${matchNames.toList()}"); |
| + } |
| + } else { |
| + print("Command not recognized."); |
|
hausner
2013/12/04 00:26:35
How about printing the usage here?
turnidge
2013/12/04 19:35:21
I have improved this message to this:
|
| + } |
| + |
| + return new Future.value(); |
| + } |
| +} |
| + |
| + |
| +class QuitCommand extends Command { |
| + final name = 'quit'; |
| + final helpShort = 'Quit the debugger.'; |
| + final helpLong =""" |
| +Quit the debugger. |
| + |
| +Usage: |
| + quit |
| +"""; |
| + |
| + Future run(List<String> args) { |
| + if (args.length > 1) { |
| + print("Unexpected arguments to $name command."); |
| + return new Future.value(); |
| + } |
| + return debuggerQuit(); |
| + } |
| +} |
| + |
| +class SetCommand extends Command { |
| + final name = 'set'; |
| + final helpShort = 'Change the value of a debugger setting.'; |
| + final helpLong =""" |
| +Change the value of a debugger setting. |
| + |
| +Usage: |
| + set <setting> <value> |
| + |
| +Valid settings are: |
| + ${validSettings.join('\n ')}. |
| + |
| +See also 'help show'. |
| +"""; |
| + |
| + Future run(List<String> args) { |
| + if (args.length < 3 || !validSettings.contains(args[1])) { |
| + print("Undefined $name command. Try 'help $name'."); |
| + return new Future.value(); |
| + } |
| + var option = args[1]; |
| + var value = args.getRange(2, args.length).join(' '); |
| + settings[option] = value; |
| + return new Future.value(); |
| + } |
| +} |
| + |
| +class ShowCommand extends Command { |
| + final name = 'show'; |
| + final helpShort = 'Show the current value of a debugger setting.'; |
| + final helpLong =""" |
| +Show the current value of a debugger setting. |
| + |
| +Usage: |
| + show |
| + show <setting> |
| + |
| +If no <setting> is specified, all current settings are shown. |
| + |
| +Valid settings are: |
| + ${validSettings.join('\n ')}. |
| + |
| +See also 'help set'. |
| +"""; |
| + |
| + Future run(List<String> args) { |
| + if (args.length == 1) { |
| + for (var option in validSettings) { |
| + var value = settings[option]; |
| + print("$option = '$value'"); |
| + } |
| + } else if (args.length == 2 && validSettings.contains(args[1])) { |
| + var option = args[1]; |
| + var value = settings[option]; |
| + if (value == null) { |
| + print('$option has not been set.'); |
| + } else { |
| + print("$option = '$value'"); |
| + } |
| + return new Future.value(); |
| + } else { |
| + print("Undefined $name command. Try 'help $name'."); |
| + } |
| + return new Future.value(); |
| + } |
| +} |
| + |
| +class RunCommand extends Command { |
| + final name = 'run'; |
| + final helpShort = "Run the currrent script."; |
| + final helpLong =""" |
| +Runs the current script. |
| + |
| +Usage: |
| + run |
| + run <args> |
| + |
| +The current script will be run on the current vm. The 'vm' and |
| +'vmargs' settings are used to specify the current vm and vm arguments. |
| +The 'script' and 'args' settings are used to specify the current |
| +script and script arguments. |
| + |
| +For more information on settings type 'help show' or 'help set'. |
| + |
| +If <args> are provided to the run command, it is the same as typing |
| +'set args <args>' followed by 'run'. |
| +"""; |
| + |
| + Future run(List<String> cmdArgs) { |
| + if (isRunning) { |
| + // TODO(turnidge): Implement modal y/n dialog to stop running script. |
| + print("There is already a running dart process. " |
| + "Try 'kill'."); |
| + return new Future.value(); |
| + } |
| + assert(targetProcess == null); |
| + if (settings['script'] == null) { |
| + print("There is no script specified. " |
| + "Use 'set script' to set the current script."); |
| + return new Future.value(); |
| + } |
| + if (cmdArgs.length > 1) { |
| + settings['args'] = cmdArgs.getRange(1, cmdArgs.length); |
| + } |
| + |
| + // Build the process arguments. |
| + var processArgs = ['--debug:$debugPort']; |
| + if (verbose) { |
| + processArgs.add('--verbose_debug'); |
| + } |
| + if (settings['vmargs'] != null) { |
| + processArgs.addAll(settings['vmargs'].split(' ')); |
| + } |
| + processArgs.add(settings['script']); |
| + if (settings['args'] != null) { |
| + processArgs.addAll(settings['args'].split(' ')); |
| + } |
| + String vm = settings['vm']; |
| + |
| + isRunning = true; |
| + cmdo.hide(); |
| + return Process.start(vm, processArgs).then((Process process) { |
| + print("Started process ${process.pid} '$vm ${processArgs.join(' ')}'"); |
| + targetProcess = process; |
| + process.stdin.close(); |
| + |
| + // TODO(turnidge): For now we only show full lines of output |
| + // from the debugged process. Should show each character. |
| + process.stdout |
| + .transform(UTF8.decoder) |
| + .transform(new LineSplitter()) |
| + .listen((String line) { |
| + cmdo.hide(); |
| + // TODO(turnidge): Escape output in any way? |
| + print(line); |
| + cmdo.show(); |
| + }); |
| + |
| + process.stderr |
| + .transform(UTF8.decoder) |
| + .transform(new LineSplitter()) |
| + .listen((String line) { |
| + cmdo.hide(); |
| + print(line); |
| + cmdo.show(); |
| + }); |
| + |
| + process.exitCode.then((int exitCode) { |
| + cmdo.hide(); |
| + if (suppressNextExitCode) { |
| + suppressNextExitCode = false; |
| + } else { |
| + if (exitCode == 0) { |
| + print('Process exited normally.'); |
| + } else { |
| + print('Process exited with code $exitCode.'); |
| + } |
| + } |
| + isRunning = false; |
| + targetProcess = null; |
| + cmdo.show(); |
| + }); |
| + |
| + // Wait for the vm to open the debugging port. |
| + return openVmSocket(0); |
| + }); |
| + } |
| +} |
| + |
| +class KillCommand extends Command { |
| + final name = 'kill'; |
| + final helpShort = 'Kill the currently executing script.'; |
| + |
| + Future run(List<String> cmdArgs) { |
| + if (!isRunning) { |
| + print('There is no running script.'); |
| + return new Future.value(); |
| + } |
| + assert(targetProcess != null); |
| + bool result = targetProcess.kill(); |
| + if (result) { |
| + print('Process killed.'); |
| + suppressNextExitCode = true; |
| + } else { |
| + print('Unable to kill process ${targetProcess.pid}'); |
| + } |
| + return new Future.value(); |
| + } |
| +} |
| + |
| typedef void HandlerType(Map response); |
| HandlerType showPromptAfter(void handler(Map response)) { |
| @@ -123,13 +413,13 @@ |
| }; |
| } |
| - |
| void processCommand(String cmdLine) { |
| void huh() { |
| - print("'$cmdLine' not understood, try h for help"); |
| + print("'$cmdLine' not understood, try 'help' for help."); |
| } |
| + cmdo.hide(); |
| seqNum++; |
| cmdLine = cmdLine.trim(); |
| var args = cmdLine.split(' '); |
| @@ -137,6 +427,7 @@ |
| return; |
| } |
| var command = args[0]; |
| + |
| var resume_commands = |
| { 'r':'resume', 's':'stepOver', 'si':'stepInto', 'so':'stepOut'}; |
| if (resume_commands[command] != null) { |
| @@ -144,19 +435,16 @@ |
| var cmd = { "id": seqNum, |
| "command": resume_commands[command], |
| "params": { "isolateId" : currentIsolate.id } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleResumedResponse)); |
| } else if (command == "bt") { |
| var cmd = { "id": seqNum, |
| "command": "getStackTrace", |
| "params": { "isolateId" : currentIsolate.id } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleStackTraceResponse)); |
| } else if (command == "ll") { |
| var cmd = { "id": seqNum, |
| "command": "getLibraries", |
| "params": { "isolateId" : currentIsolate.id } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetLibraryResponse)); |
| } else if (command == "sbp" && args.length >= 2) { |
| var url, line; |
| @@ -173,21 +461,18 @@ |
| "params": { "isolateId" : currentIsolate.id, |
| "url": url, |
| "line": line }}; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleSetBpResponse)); |
| } else if (command == "rbp" && args.length == 2) { |
| var cmd = { "id": seqNum, |
| "command": "removeBreakpoint", |
| "params": { "isolateId" : currentIsolate.id, |
| "breakpointId": int.parse(args[1]) } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); |
| } else if (command == "ls" && args.length == 2) { |
| var cmd = { "id": seqNum, |
| "command": "getScriptURLs", |
| "params": { "isolateId" : currentIsolate.id, |
| "libraryId": int.parse(args[1]) } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetScriptsResponse)); |
| } else if (command == "eval" && args.length > 3) { |
| var expr = args.getRange(3, args.length).join(" "); |
| @@ -207,14 +492,12 @@ |
| "params": { "isolateId": currentIsolate.id, |
| target: int.parse(args[2]), |
| "expression": expr } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleEvalResponse)); |
| } else if (command == "po" && args.length == 2) { |
| var cmd = { "id": seqNum, |
| "command": "getObjectProperties", |
| "params": { "isolateId" : currentIsolate.id, |
| "objectId": int.parse(args[1]) } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetObjPropsResponse)); |
| } else if (command == "pl" && args.length >= 3) { |
| var cmd; |
| @@ -232,21 +515,18 @@ |
| "index": int.parse(args[2]), |
| "length": int.parse(args[3]) } }; |
| } |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetListResponse)); |
| } else if (command == "pc" && args.length == 2) { |
| var cmd = { "id": seqNum, |
| "command": "getClassProperties", |
| "params": { "isolateId" : currentIsolate.id, |
| "classId": int.parse(args[1]) } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetClassPropsResponse)); |
| } else if (command == "plib" && args.length == 2) { |
| var cmd = { "id": seqNum, |
| "command": "getLibraryProperties", |
| "params": {"isolateId" : currentIsolate.id, |
| "libraryId": int.parse(args[1]) } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetLibraryPropsResponse)); |
| } else if (command == "slib" && args.length == 3) { |
| var cmd = { "id": seqNum, |
| @@ -254,14 +534,12 @@ |
| "params": {"isolateId" : currentIsolate.id, |
| "libraryId": int.parse(args[1]), |
| "debuggingEnabled": args[2] } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleSetLibraryPropsResponse)); |
| } else if (command == "pg" && args.length == 2) { |
| var cmd = { "id": seqNum, |
| "command": "getGlobalVariables", |
| "params": { "isolateId" : currentIsolate.id, |
| "libraryId": int.parse(args[1]) } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetGlobalVarsResponse)); |
| } else if (command == "gs" && args.length == 3) { |
| var cmd = { "id": seqNum, |
| @@ -269,7 +547,6 @@ |
| "params": { "isolateId" : currentIsolate.id, |
| "libraryId": int.parse(args[1]), |
| "url": args[2] } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetSourceResponse)); |
| } else if (command == "tok" && args.length == 3) { |
| var cmd = { "id": seqNum, |
| @@ -277,18 +554,15 @@ |
| "params": { "isolateId" : currentIsolate.id, |
| "libraryId": int.parse(args[1]), |
| "url": args[2] } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetLineTableResponse)); |
| } else if (command == "epi" && args.length == 2) { |
| var cmd = { "id": seqNum, |
| "command": "setPauseOnException", |
| "params": { "isolateId" : currentIsolate.id, |
| "exceptions": args[1] } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); |
| } else if (command == "li") { |
| var cmd = { "id": seqNum, "command": "getIsolateIds" }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGetIsolatesResponse)); |
| } else if (command == "sci" && args.length == 2) { |
| var id = int.parse(args[1]); |
| @@ -298,22 +572,46 @@ |
| } else { |
| print("$id is not a valid isolate id"); |
| } |
| + cmdo.show(); |
| } else if (command == "i" && args.length == 2) { |
| var cmd = { "id": seqNum, |
| "command": "interrupt", |
| "params": { "isolateId": int.parse(args[1]) } }; |
| - cmdo.hide(); |
| sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); |
| - } else if (command == "q") { |
| - quitShell(); |
| - } else if (command == "h") { |
| - printHelp(); |
| + } else if (command.length == 0) { |
| + huh(); |
| + cmdo.show(); |
| } else { |
| - huh(); |
| + // TODO(turnidge): Migrate all commands into this . |
| + var matches = matchCommand(command, true); |
| + if (matches.length == 0) { |
| + huh(); |
| + cmdo.show(); |
| + } else if (matches.length == 1) { |
| + matches[0].run(args).then((_) { |
| + cmdo.show(); |
| + }); |
| + } else { |
| + var matchNames = matches.map((handler) => handler.name); |
| + print("Ambigous command '$command' : ${matchNames.toList()}"); |
| + cmdo.show(); |
| + } |
| } |
| } |
| +void processError(error, trace) { |
| + cmdo.hide(); |
| + print("\nInternal error:\n$error\n$trace"); |
| + cmdo.show(); |
| +} |
| + |
| + |
| +void processDone() { |
| + debuggerQuit(); |
| +} |
| + |
| + |
| String remoteObject(value) { |
| var kind = value["kind"]; |
| var text = value["text"]; |
| @@ -764,83 +1062,154 @@ |
| // TODO(turnidge): Implement completion for arguments as well. |
| List<String> allCommands = ['q', 'bt', 'r', 's', 'so', 'si', 'sbp', 'rbp', |
| 'po', 'eval', 'pl', 'pc', 'll', 'plib', 'slib', |
| - 'pg', 'ls', 'gs', 'tok', 'epi', 'li', 'i', 'h']; |
| + 'pg', 'ls', 'gs', 'tok', 'epi', 'li', 'i', |
| + 'help']; |
|
hausner
2013/12/04 00:26:35
Don't you need to add the new set and show command
turnidge
2013/12/04 19:35:21
allCommands will be shrinking over time. The seco
|
| // Completion of first word in the command. |
| if (commandParts.length == 1) { |
| String prefix = commandParts.last; |
| - for (String command in allCommands) { |
| + for (var command in allCommands) { |
| if (command.startsWith(prefix)) { |
| completions.add(command); |
| } |
| - } |
| + } |
| + for (var command in commandList) { |
| + if (command.name.startsWith(prefix)) { |
| + completions.add(command.name); |
| + } |
| + } |
| } |
| return completions; |
| } |
| -void debuggerMain() { |
| - outstandingCommands = new Map<int, Completer>(); |
| - Socket.connect("127.0.0.1", 5858).then((s) { |
| - vmSock = s; |
| - vmSock.setOption(SocketOption.TCP_NODELAY, true); |
| - var stringStream = vmSock.transform(UTF8.decoder); |
| - vmSubscription = stringStream.listen( |
| - (String data) { |
| - processVmData(data); |
| - }, |
| - onDone: () { |
| - print("VM debugger connection closed"); |
| - quitShell(); |
| - }, |
| - onError: (err) { |
| - print("Error in debug connection: $err"); |
| - // TODO(floitsch): do we want to print the stack trace? |
| - quitShell(); |
| +Future closeCommando() { |
| + var subscription = cmdSubscription; |
| + cmdSubscription = null; |
| + cmdo = null; |
| + |
| + var future = subscription.cancel(); |
| + if (future != null) { |
| + return future; |
| + } else { |
| + return new Future.value(); |
| + } |
| +} |
| + |
| + |
| +Future openVmSocket(int attempt) { |
| + return Socket.connect("127.0.0.1", debugPort).then((s) { |
| + vmSock = s; |
| + vmSock.setOption(SocketOption.TCP_NODELAY, true); |
| + var stringStream = vmSock.transform(UTF8.decoder); |
| + outstandingCommands = new Map<int, Completer>(); |
| + vmSubscription = stringStream.listen( |
| + (String data) { |
| + processVmData(data); |
| + }, |
| + onDone: () { |
| + cmdo.hide(); |
| + if (verbose) { |
| + print("VM debugger connection closed"); |
| + } |
| + closeVmSocket().then((_) { |
| + cmdo.show(); |
| + }); |
| + }, |
| + onError: (err) { |
| + cmdo.hide(); |
| + // TODO(floitsch): do we want to print the stack trace? |
| + print("Error in debug connection: $err"); |
| + closeVmSocket().then((_) { |
| + cmdo.show(); |
| + }); |
| + }); |
| + }, |
| + onError: (e) { |
|
hausner
2013/12/04 00:26:35
This is where Dart code becomes pretty much unread
turnidge
2013/12/04 19:35:21
Yes, hard to read here for sure.
The onError is a
|
| + // We were unable to connect to the debugger's port. |
| + var delay; |
| + if (attempt < 10) { |
| + delay = new Duration(milliseconds:10); |
| + } else if (attempt < 20) { |
| + delay = new Duration(seconds:1); |
| + } else { |
| + // Too many retries. Give up. |
| + print('Timed out waiting for debugger to start.\nError: $e'); |
| + return closeVmSocket(); |
| + } |
| + |
| + // Wait and retry. |
| + return new Future.delayed(delay, () { |
| + int tmp = attempt + 1; |
| + openVmSocket(tmp); |
| }); |
| - cmdo = new Commando(stdin, stdout, processCommand, |
| - completer : debuggerCommandCompleter); |
| - }); |
| + }); |
| } |
| -void main(List<String> args) { |
| - if (args.length > 0) { |
| - if (verbose) { |
| - args = <String>['--debug', '--verbose_debug']..addAll(args); |
| - } else { |
| - args = <String>['--debug']..addAll(args); |
| +Future closeVmSocket() { |
| + if (vmSubscription == null) { |
| + // Already closed, nothing to do. |
| + assert(vmSock == null); |
| + return new Future.value(); |
| + } |
| + |
| + var subscription = vmSubscription; |
| + var sock = vmSock; |
| + |
| + // Wait for the socket to close and the subscription to be |
| + // cancelled. Perhaps overkill, but it means we know these will be |
| + // done. |
| + // |
| + // This is uglier than it needs to be since cancel can return null. |
| + var cleanupFutures = [sock.close()]; |
| + var future = subscription.cancel(); |
| + if (future != null) { |
| + cleanupFutures.add(future); |
| + } |
| + |
| + vmSubscription = null; |
| + vmSock = null; |
| + outstandingCommands = null; |
| + |
| + return Future.wait(cleanupFutures); |
| +} |
| + |
| +Future debuggerQuit() { |
| + // Kill target process, if any. |
| + if (targetProcess != null) { |
| + if (!targetProcess.kill()) { |
| + print('Unable to kill process ${targetProcess.pid}'); |
| } |
| - Process.start(Platform.executable, args).then((Process process) { |
| - targetProcess = process; |
| - process.stdin.close(); |
| + } |
| - // TODO(turnidge): For now we only show full lines of output |
| - // from the debugged process. Should show each character. |
| - process.stdout |
| - .transform(UTF8.decoder) |
| - .transform(new LineSplitter()) |
| - .listen((String line) { |
| - // Hide/show command prompt across asynchronous output. |
| - if (cmdo != null) { |
| - cmdo.hide(); |
| - } |
| - print("$line"); |
| - if (cmdo != null) { |
| - cmdo.show(); |
| - } |
| - }); |
| + // Restore terminal settings, close connections. |
| + return Future.wait([closeCommando(), closeVmSocket()]).then((_) { |
| + exit(0); |
| - process.exitCode.then((int exitCode) { |
| - if (exitCode == 0) { |
| - print('Program exited normally.'); |
| - } else { |
| - print('Program exited with code $exitCode.'); |
| - } |
| - }); |
| + // Unreachable. |
| + return new Future.value(); |
| + }); |
| +} |
| - debuggerMain(); |
| - }); |
| - } else { |
| - debuggerMain(); |
| + |
| +void parseArgs(List<String> args) { |
| + int pos = 0; |
| + settings['vm'] = Platform.executable; |
| + while (pos < args.length && args[pos].startsWith('-')) { |
| + pos++; |
| } |
| + if (pos < args.length) { |
| + settings['vmargs'] = args.getRange(0, pos).join(' '); |
| + settings['script'] = args[pos]; |
| + settings['args'] = args.getRange(pos + 1, args.length).join(' '); |
| + } |
| } |
| + |
| +void main(List<String> args) { |
|
hausner
2013/12/04 00:26:35
Is it still possible to invoke ddbg without a targ
turnidge
2013/12/04 19:35:21
No. I missed that.
I have now added two new comm
|
| + parseArgs(args); |
| + |
| + cmdo = new Commando(completer: debuggerCommandCompleter); |
| + cmdSubscription = cmdo.commands.listen(processCommand, |
| + onError: processError, |
| + onDone: processDone); |
| +} |