| 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 175 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 329 processArgs.addAll(settings['vmargs'].split(' ')); | 346 processArgs.addAll(settings['vmargs'].split(' ')); |
| 330 } | 347 } |
| 331 processArgs.add(settings['script']); | 348 processArgs.add(settings['script']); |
| 332 if (settings['args'] != null) { | 349 if (settings['args'] != null) { |
| 333 processArgs.addAll(settings['args'].split(' ')); | 350 processArgs.addAll(settings['args'].split(' ')); |
| 334 } | 351 } |
| 335 String vm = settings['vm']; | 352 String vm = settings['vm']; |
| 336 | 353 |
| 337 isDebugging = true; | 354 isDebugging = true; |
| 338 cmdo.hide(); | 355 cmdo.hide(); |
| 339 return Process.start(vm, processArgs).then((Process process) { | 356 return Process.start(vm, processArgs).then((process) { |
| 340 print("Started process ${process.pid} '$vm ${processArgs.join(' ')}'"); | 357 print("Started process ${process.pid} '$vm ${processArgs.join(' ')}'"); |
| 341 targetProcess = process; | 358 targetProcess = process; |
| 342 process.stdin.close(); | 359 process.stdin.close(); |
| 343 | 360 |
| 344 // TODO(turnidge): For now we only show full lines of output | 361 // TODO(turnidge): For now we only show full lines of output |
| 345 // from the debugged process. Should show each character. | 362 // from the debugged process. Should show each character. |
| 346 process.stdout | 363 process.stdout |
| 347 .transform(UTF8.decoder) | 364 .transform(UTF8.decoder) |
| 348 .transform(new LineSplitter()) | 365 .transform(new LineSplitter()) |
| 349 .listen((String line) { | 366 .listen((String line) { |
| (...skipping 155 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 // Send the source command |
| 986 var sourceResponse = sendCmd(sourceCmd).then((response) { |
| 987 Map result = response["result"]; |
| 988 script.source = result['text']; |
| 989 // Line numbers are 1-based so add a dummy for line 0. |
| 990 script.lineToSource = ['']; |
| 991 script.lineToSource.addAll(script.source.split('\n')); |
| 992 }); |
| 993 |
| 994 // Send the line numbers command |
| 995 var lineNumberResponse = sendCmd(lineNumberCmd).then((response) { |
| 996 Map result = response["result"]; |
| 997 script.tokenToLine = parseLineNumberTable(result['lines']); |
| 998 }); |
| 999 |
| 1000 script = new TargetScript(); |
| 1001 return Future.wait([sourceResponse, lineNumberResponse]).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((script) { |
| 1012 var lineNumber = script.tokenToLine[location['tokenOffset']]; |
| 1013 var text = script.lineToSource[lineNumber]; |
| 1014 if (label != null) { |
| 1015 var fileName = location['url'].split("/").last; |
| 1016 print("$label \n" |
| 1017 " at $fileName:$lineNumber"); |
| 1018 } |
| 1019 print("${_leftJustify(lineNumber, 8)}$text"); |
| 1020 }); |
| 1021 } |
| 1022 |
| 1023 |
| 1024 Future handlePausedEvent(msg) { |
| 928 assert(msg["params"] != null); | 1025 assert(msg["params"] != null); |
| 929 var reason = msg["params"]["reason"]; | 1026 var reason = msg["params"]["reason"]; |
| 930 int isolateId = msg["params"]["isolateId"]; | 1027 int isolateId = msg["params"]["isolateId"]; |
| 931 assert(isolateId != null); | 1028 assert(isolateId != null); |
| 932 var isolate = targetIsolates[isolateId]; | 1029 var isolate = targetIsolates[isolateId]; |
| 933 assert(isolate != null); | 1030 assert(isolate != null); |
| 934 assert(!isolate.isPaused); | 1031 assert(!isolate.isPaused); |
| 935 var location = msg["params"]["location"];; | 1032 var location = msg["params"]["location"];; |
| 936 assert(location != null); | 1033 assert(location != null); |
| 937 isolate.pausedLocation = location; | 1034 isolate.pausedLocation = location; |
| 938 if (reason == "breakpoint") { | 1035 if (reason == "breakpoint") { |
| 939 print("Isolate $isolateId paused on breakpoint"); | 1036 return printLocation((stepMode ? null : "Breakpoint"), location); |
| 940 print("location: ${formatLocation(location)}"); | |
| 941 } else if (reason == "interrupted") { | 1037 } else if (reason == "interrupted") { |
| 942 print("Isolate $isolateId paused due to an interrupt"); | 1038 stepMode = false; |
| 943 print("location: ${formatLocation(location)}"); | 1039 return printLocation("Interrupted", location); |
| 944 } else { | 1040 } else { |
| 945 assert(reason == "exception"); | 1041 assert(reason == "exception"); |
| 946 var excObj = msg["params"]["exception"]; | 1042 var excObj = msg["params"]["exception"]; |
| 947 print("Isolate $isolateId paused on exception"); | 1043 print("Isolate $isolateId paused on exception"); |
| 948 print(remoteObject(excObj)); | 1044 print(remoteObject(excObj)); |
| 1045 return new Future.value(); |
| 949 } | 1046 } |
| 950 } | 1047 } |
| 951 | 1048 |
| 952 void handleIsolateEvent(msg) { | 1049 void handleIsolateEvent(msg) { |
| 953 Map params = msg["params"]; | 1050 Map params = msg["params"]; |
| 954 assert(params != null); | 1051 assert(params != null); |
| 955 var isolateId = params["id"]; | 1052 var isolateId = params["id"]; |
| 956 var reason = params["reason"]; | 1053 var reason = params["reason"]; |
| 957 if (reason == "created") { | 1054 if (reason == "created") { |
| 958 print("Isolate $isolateId has been created."); | 1055 print("Isolate $isolateId has been created."); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 994 } | 1091 } |
| 995 var event = msg["event"]; | 1092 var event = msg["event"]; |
| 996 if (event == "isolate") { | 1093 if (event == "isolate") { |
| 997 cmdo.hide(); | 1094 cmdo.hide(); |
| 998 handleIsolateEvent(msg); | 1095 handleIsolateEvent(msg); |
| 999 cmdo.show(); | 1096 cmdo.show(); |
| 1000 return; | 1097 return; |
| 1001 } | 1098 } |
| 1002 if (event == "paused") { | 1099 if (event == "paused") { |
| 1003 cmdo.hide(); | 1100 cmdo.hide(); |
| 1004 handlePausedEvent(msg); | 1101 handlePausedEvent(msg).then((_) { |
| 1005 cmdo.show(); | 1102 cmdo.show(); |
| 1103 }); |
| 1006 return; | 1104 return; |
| 1007 } | 1105 } |
| 1008 if (event == "breakpointResolved") { | 1106 if (event == "breakpointResolved") { |
| 1009 Map params = msg["params"]; | 1107 Map params = msg["params"]; |
| 1010 assert(params != null); | 1108 assert(params != null); |
| 1011 var isolateId = params["isolateId"]; | 1109 var isolateId = params["isolateId"]; |
| 1012 var location = formatLocation(params["location"]); | 1110 var location = formatLocation(params["location"]); |
| 1013 cmdo.hide(); | 1111 cmdo.hide(); |
| 1014 print("BP ${params["breakpointId"]} resolved in isolate $isolateId" | 1112 print("Breakpoint ${params["breakpointId"]} resolved in isolate $isolateId" |
| 1015 " at $location."); | 1113 " at $location."); |
| 1016 cmdo.show(); | 1114 cmdo.show(); |
| 1017 return; | 1115 return; |
| 1018 } | 1116 } |
| 1019 if (msg["id"] != null) { | 1117 if (msg["id"] != null) { |
| 1020 var id = msg["id"]; | 1118 var id = msg["id"]; |
| 1021 if (outstandingCommands.containsKey(id)) { | 1119 if (outstandingCommands.containsKey(id)) { |
| 1022 var completer = outstandingCommands.remove(id); | 1120 var completer = outstandingCommands.remove(id); |
| 1023 if (msg["error"] != null) { | 1121 if (msg["error"] != null) { |
| 1024 print("VM says: ${msg["error"]}"); | 1122 print("VM says: ${msg["error"]}"); |
| (...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1260 cleanupFutures.add(future); | 1358 cleanupFutures.add(future); |
| 1261 } | 1359 } |
| 1262 | 1360 |
| 1263 vmSubscription = null; | 1361 vmSubscription = null; |
| 1264 vmSock = null; | 1362 vmSock = null; |
| 1265 outstandingCommands = null; | 1363 outstandingCommands = null; |
| 1266 | 1364 |
| 1267 return Future.wait(cleanupFutures); | 1365 return Future.wait(cleanupFutures); |
| 1268 } | 1366 } |
| 1269 | 1367 |
| 1368 void debuggerError(self, parent, zone, error, StackTrace trace) { |
| 1369 print('\n--------\nExiting due to unexpected error:\n' |
| 1370 ' $error\n$trace\n'); |
| 1371 debuggerQuit(); |
| 1372 } |
| 1373 |
| 1270 Future debuggerQuit() { | 1374 Future debuggerQuit() { |
| 1271 // Kill target process, if any. | 1375 // Kill target process, if any. |
| 1272 if (targetProcess != null) { | 1376 if (targetProcess != null) { |
| 1273 if (!targetProcess.kill()) { | 1377 if (!targetProcess.kill()) { |
| 1274 print('Unable to kill process ${targetProcess.pid}'); | 1378 print('Unable to kill process ${targetProcess.pid}'); |
| 1275 } | 1379 } |
| 1276 } | 1380 } |
| 1277 | 1381 |
| 1278 // Restore terminal settings, close connections. | 1382 // Restore terminal settings, close connections. |
| 1279 return Future.wait([closeCommando(), closeVmSocket()]).then((_) { | 1383 return Future.wait([closeCommando(), closeVmSocket()]).then((_) { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 1292 pos++; | 1396 pos++; |
| 1293 } | 1397 } |
| 1294 if (pos < args.length) { | 1398 if (pos < args.length) { |
| 1295 settings['vmargs'] = args.getRange(0, pos).join(' '); | 1399 settings['vmargs'] = args.getRange(0, pos).join(' '); |
| 1296 settings['script'] = args[pos]; | 1400 settings['script'] = args[pos]; |
| 1297 settings['args'] = args.getRange(pos + 1, args.length).join(' '); | 1401 settings['args'] = args.getRange(pos + 1, args.length).join(' '); |
| 1298 } | 1402 } |
| 1299 } | 1403 } |
| 1300 | 1404 |
| 1301 void main(List<String> args) { | 1405 void main(List<String> args) { |
| 1302 parseArgs(args); | 1406 // Setup a zone which will exit the debugger cleanly on any uncaught |
| 1407 // exception. |
| 1408 var zone = Zone.ROOT.fork(specification:new ZoneSpecification( |
| 1409 handleUncaughtError: debuggerError)); |
| 1303 | 1410 |
| 1304 cmdo = new Commando(completer: debuggerCommandCompleter); | 1411 zone.run(() { |
| 1305 cmdSubscription = cmdo.commands.listen(processCommand, | 1412 parseArgs(args); |
| 1306 onError: processError, | 1413 cmdo = new Commando(completer: debuggerCommandCompleter); |
| 1307 onDone: processDone); | 1414 cmdSubscription = cmdo.commands.listen(processCommand, |
| 1415 onError: processError, |
| 1416 onDone: processDone); |
| 1417 }); |
| 1308 } | 1418 } |
| OLD | NEW |