Chromium Code Reviews| Index: tools/ddbg.dart |
| =================================================================== |
| --- tools/ddbg.dart (revision 30871) |
| +++ tools/ddbg.dart (working copy) |
| @@ -12,6 +12,18 @@ |
| import "ddbg/lib/commando.dart"; |
| +class TargetScript { |
| + // The text of a script. |
| + String source = null; |
| + |
| + // A mapping from line number to source text. |
| + List<String> lineToSource = null; |
| + |
| + // A mapping from token offset to line number. |
| + Map<int,int> tokenToLine = null; |
| +} |
| + |
| + |
| class TargetIsolate { |
| int id; |
| // The location of the last paused event. |
| @@ -19,6 +31,8 @@ |
| TargetIsolate(this.id); |
| bool get isPaused => pausedLocation != null; |
| + |
| + Map<String, TargetScript> scripts = {}; |
| } |
| Map<int, TargetIsolate> targetIsolates= new Map<int, TargetIsolate>(); |
| @@ -33,6 +47,7 @@ |
| int seqNum = 0; |
| bool isDebugging = false; |
| +bool stepMode = false; |
| Process targetProcess = null; |
| bool suppressNextExitCode = false; |
| @@ -85,9 +100,13 @@ |
| // 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(''); |
| +String _leftJustify(text, int width) { |
| + StringBuffer buffer = new StringBuffer(); |
| + buffer.write(text); |
| + while (buffer.length < width) { |
| + buffer.write(' '); |
| + } |
| + return buffer.toString(); |
| } |
| // TODO(turnidge): Move all commands here. |
| @@ -138,9 +157,7 @@ |
| 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}'); |
| + print(' ${_leftJustify(command.name, 11)} ${command.helpShort}'); |
| } |
| // TODO(turnidge): Convert all commands to use the Command class. |
| @@ -512,6 +529,8 @@ |
| { 'r':'resume', 's':'stepOver', 'si':'stepInto', 'so':'stepOut'}; |
| if (resume_commands[command] != null) { |
| if (!checkPaused()) return; |
| + // TODO(turnidge): step mode isn't quite right yet. |
| + stepMode = (command != 'r'); |
| var cmd = { "id": seqNum, |
| "command": resume_commands[command], |
| "params": { "isolateId" : currentIsolate.id } }; |
| @@ -909,7 +928,7 @@ |
| void printStackFrame(frame_num, Map frame) { |
| var fname = frame["functionName"]; |
| var loc = formatLocation(frame["location"]); |
| - print("$frame_num $fname ($loc)"); |
| + print("#${_leftJustify(frame_num,2)} $fname at $loc"); |
| List locals = frame["locals"]; |
| for (int i = 0; i < locals.length; i++) { |
| printNamedObject(locals[i]); |
| @@ -924,7 +943,86 @@ |
| } |
| -void handlePausedEvent(msg) { |
| +Map<int, int> parseLineNumberTable(List<List<int>> table) { |
| + Map tokenToLine = {}; |
| + for (var line in table) { |
| + // Each entry begins with a line number... |
| + var lineNumber = line[0]; |
| + for (var pos = 1; pos < line.length; pos += 2) { |
| + // ...and is followed by (token offset, col number) pairs. |
| + // We ignore the column numbers. |
| + var tokenOffset = line[pos]; |
| + tokenToLine[tokenOffset] = lineNumber; |
| + } |
| + } |
| + return tokenToLine; |
| +} |
| + |
| + |
| +Future<TargetScript> getTargetScript(Map location) { |
| + var isolate = targetIsolates[currentIsolate.id]; |
| + var url = location['url']; |
| + var script = isolate.scripts[url]; |
| + if (script != null) { |
| + return new Future.value(script); |
| + } |
| + |
| + // Ask the vm for the source and line number table. |
| + var sourceCmd = { |
| + "id": seqNum++, |
| + "command": "getScriptSource", |
| + "params": { "isolateId": currentIsolate.id, |
| + "libraryId": location['libraryId'], |
| + "url": url } }; |
| + |
| + var lineNumberCmd = { |
| + "id": seqNum++, |
| + "command": "getLineNumberTable", |
| + "params": { "isolateId": currentIsolate.id, |
| + "libraryId": location['libraryId'], |
| + "url": url } }; |
| + |
| + script = new TargetScript(); |
| + return Future.wait([ |
|
hausner
2013/12/05 23:57:53
Your comments helped a lot to understand the contr
Bob Nystrom
2013/12/06 00:08:09
Drive-by!
turnidge
2013/12/06 17:19:43
Done.
turnidge
2013/12/06 17:19:43
Done.
|
| + // Send the source command |
| + sendCmd(sourceCmd).then( |
| + (Map response) { |
| + Map result = response["result"]; |
| + script.source = result['text']; |
| + // Line numbers are 1-based so add a dummy for line 0. |
| + script.lineToSource = ['']; |
| + script.lineToSource.addAll(script.source.split('\n')); |
| + }), |
| + // Send the line numbers command |
| + sendCmd(lineNumberCmd).then( |
| + (Map response) { |
| + Map result = response["result"]; |
| + script.tokenToLine = parseLineNumberTable(result['lines']); |
| + })]).then((_) { |
| + // When both commands complete, cache the result. |
| + isolate.scripts[url] = script; |
| + return script; |
| + }); |
| +} |
| + |
| + |
| +Future printLocation(String label, Map location) { |
| + // Figure out the line number. |
| + return getTargetScript(location).then( |
| + (script) { |
| + var lineNumber = script.tokenToLine[location['tokenOffset']]; |
| + var text = script.lineToSource[lineNumber]; |
| + if (label != null) { |
| + var fileName = location['url'].split("/").last; |
| + print("$label \n" |
| + " at $fileName:$lineNumber"); |
| + } |
| + print("${_leftJustify(lineNumber, 8)}$text"); |
| + }); |
| +} |
| + |
| + |
| +Future handlePausedEvent(msg) { |
| assert(msg["params"] != null); |
| var reason = msg["params"]["reason"]; |
| int isolateId = msg["params"]["isolateId"]; |
| @@ -936,16 +1034,16 @@ |
| assert(location != null); |
| isolate.pausedLocation = location; |
| if (reason == "breakpoint") { |
| - print("Isolate $isolateId paused on breakpoint"); |
| - print("location: ${formatLocation(location)}"); |
| + return printLocation((stepMode ? null : "Breakpoint"), location); |
|
hausner
2013/12/05 23:57:53
A cleaner way would be to add to the wire protocol
turnidge
2013/12/06 17:19:43
That would be nice. Also, it might be helpful to
|
| } else if (reason == "interrupted") { |
| - print("Isolate $isolateId paused due to an interrupt"); |
| - print("location: ${formatLocation(location)}"); |
| + stepMode = false; |
| + return printLocation("Interrupted", location); |
| } else { |
| assert(reason == "exception"); |
| var excObj = msg["params"]["exception"]; |
| print("Isolate $isolateId paused on exception"); |
| print(remoteObject(excObj)); |
| + return new Future.value(); |
| } |
| } |
| @@ -1001,8 +1099,9 @@ |
| } |
| if (event == "paused") { |
| cmdo.hide(); |
| - handlePausedEvent(msg); |
| - cmdo.show(); |
| + handlePausedEvent(msg).then((_) { |
| + cmdo.show(); |
| + }); |
| return; |
| } |
| if (event == "breakpointResolved") { |
| @@ -1011,7 +1110,7 @@ |
| var isolateId = params["isolateId"]; |
| var location = formatLocation(params["location"]); |
| cmdo.hide(); |
| - print("BP ${params["breakpointId"]} resolved in isolate $isolateId" |
| + print("Breakpoint ${params["breakpointId"]} resolved in isolate $isolateId" |
| " at $location."); |
| cmdo.show(); |
| return; |
| @@ -1267,6 +1366,12 @@ |
| return Future.wait(cleanupFutures); |
| } |
| +void debuggerError(error, StackTrace trace) { |
| + print('\n--------\nExiting due to unexpected error:\n' |
| + ' $error\n$trace\n'); |
| + debuggerQuit(); |
| +} |
| + |
| Future debuggerQuit() { |
| // Kill target process, if any. |
| if (targetProcess != null) { |
| @@ -1299,10 +1404,21 @@ |
| } |
| void main(List<String> args) { |
| - parseArgs(args); |
| + // Setup a zone which will exit the debugger cleanly on any uncaught |
| + // exception. |
| + var zone = |
| + Zone.ROOT.fork( |
| + specification:new ZoneSpecification( |
| + handleUncaughtError: |
| + (self, parent, zone, error, trace) { |
| + debuggerError(error, trace); |
| + })); |
| - cmdo = new Commando(completer: debuggerCommandCompleter); |
| - cmdSubscription = cmdo.commands.listen(processCommand, |
| - onError: processError, |
| - onDone: processDone); |
| + zone.run(() { |
| + parseArgs(args); |
| + cmdo = new Commando(completer: debuggerCommandCompleter); |
| + cmdSubscription = cmdo.commands.listen(processCommand, |
| + onError: processError, |
| + onDone: processDone); |
| + }); |
| } |