Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // Simple interactive debugger shell that connects to the Dart VM's debugger | 5 // Simple interactive debugger shell that connects to the Dart VM's debugger |
| 6 // connection port. | 6 // connection port. |
| 7 | 7 |
| 8 import "dart:convert"; | 8 import "dart:convert"; |
| 9 import "dart:io"; | 9 import "dart:io"; |
| 10 import "dart:async"; | 10 import "dart:async"; |
| 11 import "dart:math"; | 11 import "dart:math"; |
| 12 | 12 |
| 13 import "ddbg/lib/commando.dart"; | 13 import "ddbg/lib/commando.dart"; |
| 14 | 14 |
| 15 class TargetScript { | |
| 16 // The text of a script. | |
| 17 String source = null; | |
| 18 | |
| 19 // A mapping from line number to source text. | |
| 20 List<String> lineToSource = null; | |
| 21 | |
| 22 // A mapping from token offset to line number. | |
| 23 Map<int,int> tokenToLine = null; | |
| 24 } | |
| 25 | |
| 26 | |
| 15 class TargetIsolate { | 27 class TargetIsolate { |
| 16 int id; | 28 int id; |
| 17 // The location of the last paused event. | 29 // The location of the last paused event. |
| 18 Map pausedLocation = null; | 30 Map pausedLocation = null; |
| 19 | 31 |
| 20 TargetIsolate(this.id); | 32 TargetIsolate(this.id); |
| 21 bool get isPaused => pausedLocation != null; | 33 bool get isPaused => pausedLocation != null; |
| 34 | |
| 35 Map<String, TargetScript> scripts = {}; | |
| 22 } | 36 } |
| 23 | 37 |
| 24 Map<int, TargetIsolate> targetIsolates= new Map<int, TargetIsolate>(); | 38 Map<int, TargetIsolate> targetIsolates= new Map<int, TargetIsolate>(); |
| 25 | 39 |
| 26 Map<int, Completer> outstandingCommands; | 40 Map<int, Completer> outstandingCommands; |
| 27 | 41 |
| 28 Socket vmSock; | 42 Socket vmSock; |
| 29 String vmData; | 43 String vmData; |
| 30 var cmdSubscription; | 44 var cmdSubscription; |
| 31 Commando cmdo; | 45 Commando cmdo; |
| 32 var vmSubscription; | 46 var vmSubscription; |
| 33 int seqNum = 0; | 47 int seqNum = 0; |
| 34 | 48 |
| 35 bool isDebugging = false; | 49 bool isDebugging = false; |
| 50 bool stepMode = false; | |
| 36 Process targetProcess = null; | 51 Process targetProcess = null; |
| 37 bool suppressNextExitCode = false; | 52 bool suppressNextExitCode = false; |
| 38 | 53 |
| 39 final verbose = false; | 54 final verbose = false; |
| 40 final printMessages = false; | 55 final printMessages = false; |
| 41 | 56 |
| 42 TargetIsolate currentIsolate; | 57 TargetIsolate currentIsolate; |
| 43 TargetIsolate mainIsolate; | 58 TargetIsolate mainIsolate; |
| 44 | 59 |
| 45 int debugPort = 5858; | 60 int debugPort = 5858; |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 78 print("Current isolate must be paused"); | 93 print("Current isolate must be paused"); |
| 79 return false; | 94 return false; |
| 80 } | 95 } |
| 81 | 96 |
| 82 // These settings are allowed in the 'set' and 'show' debugger commands. | 97 // These settings are allowed in the 'set' and 'show' debugger commands. |
| 83 var validSettings = ['vm', 'vmargs', 'script', 'args']; | 98 var validSettings = ['vm', 'vmargs', 'script', 'args']; |
| 84 | 99 |
| 85 // The current values for all settings. | 100 // The current values for all settings. |
| 86 var settings = new Map(); | 101 var settings = new Map(); |
| 87 | 102 |
| 88 // Generates a string of 'count' spaces. | 103 String _leftJustify(text, int width) { |
| 89 String _spaces(int count) { | 104 StringBuffer buffer = new StringBuffer(); |
| 90 return new List.filled(count, ' ').join(''); | 105 buffer.write(text); |
| 106 while (buffer.length < width) { | |
| 107 buffer.write(' '); | |
| 108 } | |
| 109 return buffer.toString(); | |
| 91 } | 110 } |
| 92 | 111 |
| 93 // TODO(turnidge): Move all commands here. | 112 // TODO(turnidge): Move all commands here. |
| 94 List<Command> commandList = | 113 List<Command> commandList = |
| 95 [ new HelpCommand(), | 114 [ new HelpCommand(), |
| 96 new QuitCommand(), | 115 new QuitCommand(), |
| 97 new RunCommand(), | 116 new RunCommand(), |
| 98 new KillCommand(), | 117 new KillCommand(), |
| 99 new ConnectCommand(), | 118 new ConnectCommand(), |
| 100 new DisconnectCommand(), | 119 new DisconnectCommand(), |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 131 | 150 |
| 132 Usage: | 151 Usage: |
| 133 help | 152 help |
| 134 help <command> | 153 help <command> |
| 135 """; | 154 """; |
| 136 | 155 |
| 137 Future run(List<String> args) { | 156 Future run(List<String> args) { |
| 138 if (args.length == 1) { | 157 if (args.length == 1) { |
| 139 print("Debugger commands:\n"); | 158 print("Debugger commands:\n"); |
| 140 for (var command in commandList) { | 159 for (var command in commandList) { |
| 141 const tabStop = 12; | 160 print(' ${_leftJustify(command.name, 11)} ${command.helpShort}'); |
| 142 var spaces = _spaces(max(1, (tabStop - command.name.length))); | |
| 143 print(' ${command.name}${spaces}${command.helpShort}'); | |
| 144 } | 161 } |
| 145 | 162 |
| 146 // TODO(turnidge): Convert all commands to use the Command class. | 163 // TODO(turnidge): Convert all commands to use the Command class. |
| 147 print(""" | 164 print(""" |
| 148 bt Show backtrace | 165 bt Show backtrace |
| 149 r Resume execution | 166 r Resume execution |
| 150 s Single step | 167 s Single step |
| 151 so Step over | 168 so Step over |
| 152 si Step into | 169 si Step into |
| 153 sbp [<file>] <line> Set breakpoint | 170 sbp [<file>] <line> Set breakpoint |
| (...skipping 351 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 505 var args = cmdLine.split(' '); | 522 var args = cmdLine.split(' '); |
| 506 if (args.length == 0) { | 523 if (args.length == 0) { |
| 507 return; | 524 return; |
| 508 } | 525 } |
| 509 var command = args[0]; | 526 var command = args[0]; |
| 510 | 527 |
| 511 var resume_commands = | 528 var resume_commands = |
| 512 { 'r':'resume', 's':'stepOver', 'si':'stepInto', 'so':'stepOut'}; | 529 { 'r':'resume', 's':'stepOver', 'si':'stepInto', 'so':'stepOut'}; |
| 513 if (resume_commands[command] != null) { | 530 if (resume_commands[command] != null) { |
| 514 if (!checkPaused()) return; | 531 if (!checkPaused()) return; |
| 532 // TODO(turnidge): step mode isn't quite right yet. | |
| 533 stepMode = (command != 'r'); | |
| 515 var cmd = { "id": seqNum, | 534 var cmd = { "id": seqNum, |
| 516 "command": resume_commands[command], | 535 "command": resume_commands[command], |
| 517 "params": { "isolateId" : currentIsolate.id } }; | 536 "params": { "isolateId" : currentIsolate.id } }; |
| 518 sendCmd(cmd).then(showPromptAfter(handleResumedResponse)); | 537 sendCmd(cmd).then(showPromptAfter(handleResumedResponse)); |
| 519 } else if (command == "bt") { | 538 } else if (command == "bt") { |
| 520 var cmd = { "id": seqNum, | 539 var cmd = { "id": seqNum, |
| 521 "command": "getStackTrace", | 540 "command": "getStackTrace", |
| 522 "params": { "isolateId" : currentIsolate.id } }; | 541 "params": { "isolateId" : currentIsolate.id } }; |
| 523 sendCmd(cmd).then(showPromptAfter(handleStackTraceResponse)); | 542 sendCmd(cmd).then(showPromptAfter(handleStackTraceResponse)); |
| 524 } else if (command == "ll") { | 543 } else if (command == "ll") { |
| (...skipping 377 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 902 Map result = response["result"]; | 921 Map result = response["result"]; |
| 903 List callFrames = result["callFrames"]; | 922 List callFrames = result["callFrames"]; |
| 904 assert(callFrames != null); | 923 assert(callFrames != null); |
| 905 printStackTrace(callFrames); | 924 printStackTrace(callFrames); |
| 906 } | 925 } |
| 907 | 926 |
| 908 | 927 |
| 909 void printStackFrame(frame_num, Map frame) { | 928 void printStackFrame(frame_num, Map frame) { |
| 910 var fname = frame["functionName"]; | 929 var fname = frame["functionName"]; |
| 911 var loc = formatLocation(frame["location"]); | 930 var loc = formatLocation(frame["location"]); |
| 912 print("$frame_num $fname ($loc)"); | 931 print("#${_leftJustify(frame_num,2)} $fname at $loc"); |
| 913 List locals = frame["locals"]; | 932 List locals = frame["locals"]; |
| 914 for (int i = 0; i < locals.length; i++) { | 933 for (int i = 0; i < locals.length; i++) { |
| 915 printNamedObject(locals[i]); | 934 printNamedObject(locals[i]); |
| 916 } | 935 } |
| 917 } | 936 } |
| 918 | 937 |
| 919 | 938 |
| 920 void printStackTrace(List frames) { | 939 void printStackTrace(List frames) { |
| 921 for (int i = 0; i < frames.length; i++) { | 940 for (int i = 0; i < frames.length; i++) { |
| 922 printStackFrame(i, frames[i]); | 941 printStackFrame(i, frames[i]); |
| 923 } | 942 } |
| 924 } | 943 } |
| 925 | 944 |
| 926 | 945 |
| 927 void handlePausedEvent(msg) { | 946 Map<int, int> parseLineNumberTable(List<List<int>> table) { |
| 947 Map tokenToLine = {}; | |
| 948 for (var line in table) { | |
| 949 // Each entry begins with a line number... | |
| 950 var lineNumber = line[0]; | |
| 951 for (var pos = 1; pos < line.length; pos += 2) { | |
| 952 // ...and is followed by (token offset, col number) pairs. | |
| 953 // We ignore the column numbers. | |
| 954 var tokenOffset = line[pos]; | |
| 955 tokenToLine[tokenOffset] = lineNumber; | |
| 956 } | |
| 957 } | |
| 958 return tokenToLine; | |
| 959 } | |
| 960 | |
| 961 | |
| 962 Future<TargetScript> getTargetScript(Map location) { | |
| 963 var isolate = targetIsolates[currentIsolate.id]; | |
| 964 var url = location['url']; | |
| 965 var script = isolate.scripts[url]; | |
| 966 if (script != null) { | |
| 967 return new Future.value(script); | |
| 968 } | |
| 969 | |
| 970 // Ask the vm for the source and line number table. | |
| 971 var sourceCmd = { | |
| 972 "id": seqNum++, | |
| 973 "command": "getScriptSource", | |
| 974 "params": { "isolateId": currentIsolate.id, | |
| 975 "libraryId": location['libraryId'], | |
| 976 "url": url } }; | |
| 977 | |
| 978 var lineNumberCmd = { | |
| 979 "id": seqNum++, | |
| 980 "command": "getLineNumberTable", | |
| 981 "params": { "isolateId": currentIsolate.id, | |
| 982 "libraryId": location['libraryId'], | |
| 983 "url": url } }; | |
| 984 | |
| 985 script = new TargetScript(); | |
| 986 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.
| |
| 987 // Send the source command | |
| 988 sendCmd(sourceCmd).then( | |
| 989 (Map response) { | |
| 990 Map result = response["result"]; | |
| 991 script.source = result['text']; | |
| 992 // Line numbers are 1-based so add a dummy for line 0. | |
| 993 script.lineToSource = ['']; | |
| 994 script.lineToSource.addAll(script.source.split('\n')); | |
| 995 }), | |
| 996 // Send the line numbers command | |
| 997 sendCmd(lineNumberCmd).then( | |
| 998 (Map response) { | |
| 999 Map result = response["result"]; | |
| 1000 script.tokenToLine = parseLineNumberTable(result['lines']); | |
| 1001 })]).then((_) { | |
| 1002 // When both commands complete, cache the result. | |
| 1003 isolate.scripts[url] = script; | |
| 1004 return script; | |
| 1005 }); | |
| 1006 } | |
| 1007 | |
| 1008 | |
| 1009 Future printLocation(String label, Map location) { | |
| 1010 // Figure out the line number. | |
| 1011 return getTargetScript(location).then( | |
| 1012 (script) { | |
| 1013 var lineNumber = script.tokenToLine[location['tokenOffset']]; | |
| 1014 var text = script.lineToSource[lineNumber]; | |
| 1015 if (label != null) { | |
| 1016 var fileName = location['url'].split("/").last; | |
| 1017 print("$label \n" | |
| 1018 " at $fileName:$lineNumber"); | |
| 1019 } | |
| 1020 print("${_leftJustify(lineNumber, 8)}$text"); | |
| 1021 }); | |
| 1022 } | |
| 1023 | |
| 1024 | |
| 1025 Future handlePausedEvent(msg) { | |
| 928 assert(msg["params"] != null); | 1026 assert(msg["params"] != null); |
| 929 var reason = msg["params"]["reason"]; | 1027 var reason = msg["params"]["reason"]; |
| 930 int isolateId = msg["params"]["isolateId"]; | 1028 int isolateId = msg["params"]["isolateId"]; |
| 931 assert(isolateId != null); | 1029 assert(isolateId != null); |
| 932 var isolate = targetIsolates[isolateId]; | 1030 var isolate = targetIsolates[isolateId]; |
| 933 assert(isolate != null); | 1031 assert(isolate != null); |
| 934 assert(!isolate.isPaused); | 1032 assert(!isolate.isPaused); |
| 935 var location = msg["params"]["location"];; | 1033 var location = msg["params"]["location"];; |
| 936 assert(location != null); | 1034 assert(location != null); |
| 937 isolate.pausedLocation = location; | 1035 isolate.pausedLocation = location; |
| 938 if (reason == "breakpoint") { | 1036 if (reason == "breakpoint") { |
| 939 print("Isolate $isolateId paused on breakpoint"); | 1037 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
| |
| 940 print("location: ${formatLocation(location)}"); | |
| 941 } else if (reason == "interrupted") { | 1038 } else if (reason == "interrupted") { |
| 942 print("Isolate $isolateId paused due to an interrupt"); | 1039 stepMode = false; |
| 943 print("location: ${formatLocation(location)}"); | 1040 return printLocation("Interrupted", location); |
| 944 } else { | 1041 } else { |
| 945 assert(reason == "exception"); | 1042 assert(reason == "exception"); |
| 946 var excObj = msg["params"]["exception"]; | 1043 var excObj = msg["params"]["exception"]; |
| 947 print("Isolate $isolateId paused on exception"); | 1044 print("Isolate $isolateId paused on exception"); |
| 948 print(remoteObject(excObj)); | 1045 print(remoteObject(excObj)); |
| 1046 return new Future.value(); | |
| 949 } | 1047 } |
| 950 } | 1048 } |
| 951 | 1049 |
| 952 void handleIsolateEvent(msg) { | 1050 void handleIsolateEvent(msg) { |
| 953 Map params = msg["params"]; | 1051 Map params = msg["params"]; |
| 954 assert(params != null); | 1052 assert(params != null); |
| 955 var isolateId = params["id"]; | 1053 var isolateId = params["id"]; |
| 956 var reason = params["reason"]; | 1054 var reason = params["reason"]; |
| 957 if (reason == "created") { | 1055 if (reason == "created") { |
| 958 print("Isolate $isolateId has been created."); | 1056 print("Isolate $isolateId has been created."); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 994 } | 1092 } |
| 995 var event = msg["event"]; | 1093 var event = msg["event"]; |
| 996 if (event == "isolate") { | 1094 if (event == "isolate") { |
| 997 cmdo.hide(); | 1095 cmdo.hide(); |
| 998 handleIsolateEvent(msg); | 1096 handleIsolateEvent(msg); |
| 999 cmdo.show(); | 1097 cmdo.show(); |
| 1000 return; | 1098 return; |
| 1001 } | 1099 } |
| 1002 if (event == "paused") { | 1100 if (event == "paused") { |
| 1003 cmdo.hide(); | 1101 cmdo.hide(); |
| 1004 handlePausedEvent(msg); | 1102 handlePausedEvent(msg).then((_) { |
| 1005 cmdo.show(); | 1103 cmdo.show(); |
| 1104 }); | |
| 1006 return; | 1105 return; |
| 1007 } | 1106 } |
| 1008 if (event == "breakpointResolved") { | 1107 if (event == "breakpointResolved") { |
| 1009 Map params = msg["params"]; | 1108 Map params = msg["params"]; |
| 1010 assert(params != null); | 1109 assert(params != null); |
| 1011 var isolateId = params["isolateId"]; | 1110 var isolateId = params["isolateId"]; |
| 1012 var location = formatLocation(params["location"]); | 1111 var location = formatLocation(params["location"]); |
| 1013 cmdo.hide(); | 1112 cmdo.hide(); |
| 1014 print("BP ${params["breakpointId"]} resolved in isolate $isolateId" | 1113 print("Breakpoint ${params["breakpointId"]} resolved in isolate $isolateId" |
| 1015 " at $location."); | 1114 " at $location."); |
| 1016 cmdo.show(); | 1115 cmdo.show(); |
| 1017 return; | 1116 return; |
| 1018 } | 1117 } |
| 1019 if (msg["id"] != null) { | 1118 if (msg["id"] != null) { |
| 1020 var id = msg["id"]; | 1119 var id = msg["id"]; |
| 1021 if (outstandingCommands.containsKey(id)) { | 1120 if (outstandingCommands.containsKey(id)) { |
| 1022 var completer = outstandingCommands.remove(id); | 1121 var completer = outstandingCommands.remove(id); |
| 1023 if (msg["error"] != null) { | 1122 if (msg["error"] != null) { |
| 1024 print("VM says: ${msg["error"]}"); | 1123 print("VM says: ${msg["error"]}"); |
| (...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1260 cleanupFutures.add(future); | 1359 cleanupFutures.add(future); |
| 1261 } | 1360 } |
| 1262 | 1361 |
| 1263 vmSubscription = null; | 1362 vmSubscription = null; |
| 1264 vmSock = null; | 1363 vmSock = null; |
| 1265 outstandingCommands = null; | 1364 outstandingCommands = null; |
| 1266 | 1365 |
| 1267 return Future.wait(cleanupFutures); | 1366 return Future.wait(cleanupFutures); |
| 1268 } | 1367 } |
| 1269 | 1368 |
| 1369 void debuggerError(error, StackTrace trace) { | |
| 1370 print('\n--------\nExiting due to unexpected error:\n' | |
| 1371 ' $error\n$trace\n'); | |
| 1372 debuggerQuit(); | |
| 1373 } | |
| 1374 | |
| 1270 Future debuggerQuit() { | 1375 Future debuggerQuit() { |
| 1271 // Kill target process, if any. | 1376 // Kill target process, if any. |
| 1272 if (targetProcess != null) { | 1377 if (targetProcess != null) { |
| 1273 if (!targetProcess.kill()) { | 1378 if (!targetProcess.kill()) { |
| 1274 print('Unable to kill process ${targetProcess.pid}'); | 1379 print('Unable to kill process ${targetProcess.pid}'); |
| 1275 } | 1380 } |
| 1276 } | 1381 } |
| 1277 | 1382 |
| 1278 // Restore terminal settings, close connections. | 1383 // Restore terminal settings, close connections. |
| 1279 return Future.wait([closeCommando(), closeVmSocket()]).then((_) { | 1384 return Future.wait([closeCommando(), closeVmSocket()]).then((_) { |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 1292 pos++; | 1397 pos++; |
| 1293 } | 1398 } |
| 1294 if (pos < args.length) { | 1399 if (pos < args.length) { |
| 1295 settings['vmargs'] = args.getRange(0, pos).join(' '); | 1400 settings['vmargs'] = args.getRange(0, pos).join(' '); |
| 1296 settings['script'] = args[pos]; | 1401 settings['script'] = args[pos]; |
| 1297 settings['args'] = args.getRange(pos + 1, args.length).join(' '); | 1402 settings['args'] = args.getRange(pos + 1, args.length).join(' '); |
| 1298 } | 1403 } |
| 1299 } | 1404 } |
| 1300 | 1405 |
| 1301 void main(List<String> args) { | 1406 void main(List<String> args) { |
| 1302 parseArgs(args); | 1407 // Setup a zone which will exit the debugger cleanly on any uncaught |
| 1408 // exception. | |
| 1409 var zone = | |
| 1410 Zone.ROOT.fork( | |
| 1411 specification:new ZoneSpecification( | |
| 1412 handleUncaughtError: | |
| 1413 (self, parent, zone, error, trace) { | |
| 1414 debuggerError(error, trace); | |
| 1415 })); | |
| 1303 | 1416 |
| 1304 cmdo = new Commando(completer: debuggerCommandCompleter); | 1417 zone.run(() { |
| 1305 cmdSubscription = cmdo.commands.listen(processCommand, | 1418 parseArgs(args); |
| 1306 onError: processError, | 1419 cmdo = new Commando(completer: debuggerCommandCompleter); |
| 1307 onDone: processDone); | 1420 cmdSubscription = cmdo.commands.listen(processCommand, |
| 1421 onError: processError, | |
| 1422 onDone: processDone); | |
| 1423 }); | |
| 1308 } | 1424 } |
| OLD | NEW |