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 | 12 |
| 12 import "ddbg/lib/commando.dart"; | 13 import "ddbg/lib/commando.dart"; |
| 13 | 14 |
| 14 class TargetIsolate { | 15 class TargetIsolate { |
| 15 int id; | 16 int id; |
| 16 // The location of the last paused event. | 17 // The location of the last paused event. |
| 17 Map pausedLocation = null; | 18 Map pausedLocation = null; |
| 18 | 19 |
| 19 TargetIsolate(this.id); | 20 TargetIsolate(this.id); |
| 20 bool get isPaused => pausedLocation != null; | 21 bool get isPaused => pausedLocation != null; |
| 21 } | 22 } |
| 22 | 23 |
| 23 Map<int, TargetIsolate> targetIsolates= new Map<int, TargetIsolate>(); | 24 Map<int, TargetIsolate> targetIsolates= new Map<int, TargetIsolate>(); |
| 24 | 25 |
| 25 Map<int, Completer> outstandingCommands; | 26 Map<int, Completer> outstandingCommands; |
| 26 | 27 |
| 27 Socket vmSock; | 28 Socket vmSock; |
| 28 String vmData; | 29 String vmData; |
| 30 var cmdSubscription; | |
| 29 Commando cmdo; | 31 Commando cmdo; |
| 30 var vmSubscription; | 32 var vmSubscription; |
| 31 int seqNum = 0; | 33 int seqNum = 0; |
| 32 | 34 |
| 35 bool isRunning = false; | |
| 33 Process targetProcess; | 36 Process targetProcess; |
| 37 bool suppressNextExitCode = false; | |
| 34 | 38 |
| 35 final verbose = false; | 39 final verbose = false; |
| 36 final printMessages = false; | 40 final printMessages = false; |
| 37 | 41 |
| 38 TargetIsolate currentIsolate; | 42 TargetIsolate currentIsolate; |
| 39 TargetIsolate mainIsolate; | 43 TargetIsolate mainIsolate; |
| 40 | 44 |
| 41 | 45 int debugPort = 5858; |
| 42 void printHelp() { | |
| 43 print(""" | |
| 44 q Quit debugger shell | |
| 45 bt Show backtrace | |
| 46 r Resume execution | |
| 47 s Single step | |
| 48 so Step over | |
| 49 si Step into | |
| 50 sbp [<file>] <line> Set breakpoint | |
| 51 rbp <id> Remove breakpoint with given id | |
| 52 po <id> Print object info for given id | |
| 53 eval obj <id> <expr> Evaluate expr on object id | |
| 54 eval cls <id> <expr> Evaluate expr on class id | |
| 55 eval lib <id> <expr> Evaluate expr in toplevel of library id | |
| 56 pl <id> <idx> [<len>] Print list element/slice | |
| 57 pc <id> Print class info for given id | |
| 58 ll List loaded libraries | |
| 59 plib <id> Print library info for given library id | |
| 60 slib <id> <true|false> Set library id debuggable | |
| 61 pg <id> Print all global variables visible within given library id | |
| 62 ls <lib_id> List loaded scripts in library | |
| 63 gs <lib_id> <script_url> Get source text of script in library | |
| 64 tok <lib_id> <script_url> Get line and token table of script in library | |
| 65 epi <none|all|unhandled> Set exception pause info | |
| 66 li List ids of all isolates in the VM | |
| 67 sci <id> Set current target isolate | |
| 68 i <id> Interrupt execution of given isolate id | |
| 69 h Print help | |
| 70 """); | |
| 71 } | |
| 72 | |
| 73 | 46 |
| 74 String formatLocation(Map location) { | 47 String formatLocation(Map location) { |
| 75 if (location == null) return ""; | 48 if (location == null) return ""; |
| 76 var fileName = location["url"].split("/").last; | 49 var fileName = location["url"].split("/").last; |
| 77 return "file: $fileName lib: ${location['libraryId']} token: ${location['token Offset']}"; | 50 return "file: $fileName lib: ${location['libraryId']} token: ${location['token Offset']}"; |
| 78 } | 51 } |
| 79 | 52 |
| 80 | 53 |
| 81 void quitShell() { | |
| 82 vmSubscription.cancel(); | |
| 83 vmSock.close(); | |
| 84 cmdo.done(); | |
| 85 } | |
| 86 | |
| 87 | |
| 88 Future sendCmd(Map<String, dynamic> cmd) { | 54 Future sendCmd(Map<String, dynamic> cmd) { |
| 89 var completer = new Completer(); | 55 var completer = new Completer(); |
| 90 int id = cmd["id"]; | 56 int id = cmd["id"]; |
| 91 outstandingCommands[id] = completer; | 57 outstandingCommands[id] = completer; |
| 92 if (verbose) { | 58 if (verbose) { |
| 93 print("sending: '${JSON.encode(cmd)}'"); | 59 print("sending: '${JSON.encode(cmd)}'"); |
| 94 } | 60 } |
| 95 vmSock.write(JSON.encode(cmd)); | 61 vmSock.write(JSON.encode(cmd)); |
| 96 return completer.future; | 62 return completer.future; |
| 97 } | 63 } |
| 98 | 64 |
| 99 | 65 |
| 100 bool checkCurrentIsolate() { | 66 bool checkCurrentIsolate() { |
| 101 if (currentIsolate != null) { | 67 if (currentIsolate != null) { |
| 102 return true; | 68 return true; |
| 103 } | 69 } |
| 104 print("Need valid current isolate"); | 70 print("Need valid current isolate"); |
| 105 return false; | 71 return false; |
| 106 } | 72 } |
| 107 | 73 |
| 108 | 74 |
| 109 bool checkPaused() { | 75 bool checkPaused() { |
| 110 if (!checkCurrentIsolate()) return false; | 76 if (!checkCurrentIsolate()) return false; |
| 111 if (currentIsolate.isPaused) return true; | 77 if (currentIsolate.isPaused) return true; |
| 112 print("Current isolate must be paused"); | 78 print("Current isolate must be paused"); |
| 113 return false; | 79 return false; |
| 114 } | 80 } |
| 115 | 81 |
| 82 // These settings are allowed in the 'set' and 'show' debugger commands. | |
| 83 var validSettings = ['vm', 'vmargs', 'script', 'args']; | |
| 84 | |
| 85 // The current values for all settings. | |
| 86 var settings = new Map(); | |
| 87 | |
| 88 // Generates a string of 'count' spaces. | |
| 89 String _spaces(int count) { | |
| 90 return new List.filled(count, ' ').join(''); | |
| 91 } | |
| 92 | |
| 93 // TODO(turnidge): Move all commands here. | |
| 94 List<Command> commandList = | |
| 95 [ new HelpCommand(), | |
| 96 new QuitCommand(), | |
| 97 new RunCommand(), | |
| 98 new KillCommand(), | |
| 99 new SetCommand(), | |
| 100 new ShowCommand() ]; | |
| 101 | |
| 102 | |
| 103 Command matchCommand(String commandName, bool exactMatchWins) { | |
| 104 List matches = []; | |
| 105 for (var command in commandList) { | |
| 106 if (command.name.startsWith(commandName)) { | |
| 107 if (exactMatchWins && command.name == commandName) { | |
| 108 // Exact match | |
| 109 return [command]; | |
| 110 } else { | |
| 111 matches.add(command); | |
| 112 } | |
| 113 } | |
| 114 } | |
| 115 return matches; | |
| 116 } | |
| 117 | |
| 118 abstract class Command { | |
| 119 String get name; | |
| 120 Future run(List<String> args); | |
| 121 } | |
| 122 | |
| 123 class HelpCommand extends Command { | |
| 124 final name = 'help'; | |
| 125 final helpShort = 'Show a list of debugger commands'; | |
| 126 final helpLong =""" | |
| 127 Show a list of debugger commands or get more information about a | |
| 128 particular command. | |
| 129 | |
| 130 Usage: | |
| 131 help | |
| 132 help <command> | |
| 133 """; | |
| 134 | |
| 135 Future run(List<String> args) { | |
| 136 if (args.length == 1) { | |
| 137 print("Debugger commands:\n"); | |
| 138 for (var command in commandList) { | |
| 139 const tabStop = 12; | |
| 140 var spaces = _spaces(max(1, (tabStop - command.name.length))); | |
| 141 print(' ${command.name}${spaces}${command.helpShort}'); | |
| 142 } | |
| 143 | |
| 144 // TODO(turnidge): Convert all commands to use the Command class. | |
| 145 print(""" | |
| 146 bt Show backtrace | |
| 147 r Resume execution | |
| 148 s Single step | |
| 149 so Step over | |
| 150 si Step into | |
| 151 sbp [<file>] <line> Set breakpoint | |
| 152 rbp <id> Remove breakpoint with given id | |
| 153 po <id> Print object info for given id | |
| 154 eval obj <id> <expr> Evaluate expr on object id | |
| 155 eval cls <id> <expr> Evaluate expr on class id | |
| 156 eval lib <id> <expr> Evaluate expr in toplevel of library id | |
| 157 pl <id> <idx> [<len>] Print list element/slice | |
| 158 pc <id> Print class info for given id | |
| 159 ll List loaded libraries | |
| 160 plib <id> Print library info for given library id | |
| 161 slib <id> <true|false> Set library id debuggable | |
| 162 pg <id> Print all global variables visible within given library id | |
| 163 ls <lib_id> List loaded scripts in library | |
| 164 gs <lib_id> <script_url> Get source text of script in library | |
| 165 tok <lib_id> <script_url> Get line and token table of script in library | |
| 166 epi <none|all|unhandled> Set exception pause info | |
| 167 li List ids of all isolates in the VM | |
| 168 sci <id> Set current target isolate | |
| 169 i <id> Interrupt execution of given isolate id | |
| 170 """); | |
| 171 | |
| 172 print("For more information about a particular command, type:\n\n" | |
| 173 " help <command>\n"); | |
| 174 | |
| 175 print("Commands may be abbreviated: e.g. type 'h' for 'help.\n"); | |
| 176 } else if (args.length == 2) { | |
| 177 var commandName = args[1]; | |
| 178 var matches = matchCommand(commandName, true); | |
| 179 if (matches.length == 0) { | |
| 180 print("Command '$commandName' not recognized. " | |
| 181 "Try 'help' for a list of commands."); | |
| 182 } else if (matches.length == 1) { | |
| 183 print(matches[0].helpLong); | |
| 184 } else { | |
| 185 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.
| |
| 186 print("Ambigous command '$commandName' : ${matchNames.toList()}"); | |
| 187 } | |
| 188 } else { | |
| 189 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:
| |
| 190 } | |
| 191 | |
| 192 return new Future.value(); | |
| 193 } | |
| 194 } | |
| 195 | |
| 196 | |
| 197 class QuitCommand extends Command { | |
| 198 final name = 'quit'; | |
| 199 final helpShort = 'Quit the debugger.'; | |
| 200 final helpLong =""" | |
| 201 Quit the debugger. | |
| 202 | |
| 203 Usage: | |
| 204 quit | |
| 205 """; | |
| 206 | |
| 207 Future run(List<String> args) { | |
| 208 if (args.length > 1) { | |
| 209 print("Unexpected arguments to $name command."); | |
| 210 return new Future.value(); | |
| 211 } | |
| 212 return debuggerQuit(); | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 class SetCommand extends Command { | |
| 217 final name = 'set'; | |
| 218 final helpShort = 'Change the value of a debugger setting.'; | |
| 219 final helpLong =""" | |
| 220 Change the value of a debugger setting. | |
| 221 | |
| 222 Usage: | |
| 223 set <setting> <value> | |
| 224 | |
| 225 Valid settings are: | |
| 226 ${validSettings.join('\n ')}. | |
| 227 | |
| 228 See also 'help show'. | |
| 229 """; | |
| 230 | |
| 231 Future run(List<String> args) { | |
| 232 if (args.length < 3 || !validSettings.contains(args[1])) { | |
| 233 print("Undefined $name command. Try 'help $name'."); | |
| 234 return new Future.value(); | |
| 235 } | |
| 236 var option = args[1]; | |
| 237 var value = args.getRange(2, args.length).join(' '); | |
| 238 settings[option] = value; | |
| 239 return new Future.value(); | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 class ShowCommand extends Command { | |
| 244 final name = 'show'; | |
| 245 final helpShort = 'Show the current value of a debugger setting.'; | |
| 246 final helpLong =""" | |
| 247 Show the current value of a debugger setting. | |
| 248 | |
| 249 Usage: | |
| 250 show | |
| 251 show <setting> | |
| 252 | |
| 253 If no <setting> is specified, all current settings are shown. | |
| 254 | |
| 255 Valid settings are: | |
| 256 ${validSettings.join('\n ')}. | |
| 257 | |
| 258 See also 'help set'. | |
| 259 """; | |
| 260 | |
| 261 Future run(List<String> args) { | |
| 262 if (args.length == 1) { | |
| 263 for (var option in validSettings) { | |
| 264 var value = settings[option]; | |
| 265 print("$option = '$value'"); | |
| 266 } | |
| 267 } else if (args.length == 2 && validSettings.contains(args[1])) { | |
| 268 var option = args[1]; | |
| 269 var value = settings[option]; | |
| 270 if (value == null) { | |
| 271 print('$option has not been set.'); | |
| 272 } else { | |
| 273 print("$option = '$value'"); | |
| 274 } | |
| 275 return new Future.value(); | |
| 276 } else { | |
| 277 print("Undefined $name command. Try 'help $name'."); | |
| 278 } | |
| 279 return new Future.value(); | |
| 280 } | |
| 281 } | |
| 282 | |
| 283 class RunCommand extends Command { | |
| 284 final name = 'run'; | |
| 285 final helpShort = "Run the currrent script."; | |
| 286 final helpLong =""" | |
| 287 Runs the current script. | |
| 288 | |
| 289 Usage: | |
| 290 run | |
| 291 run <args> | |
| 292 | |
| 293 The current script will be run on the current vm. The 'vm' and | |
| 294 'vmargs' settings are used to specify the current vm and vm arguments. | |
| 295 The 'script' and 'args' settings are used to specify the current | |
| 296 script and script arguments. | |
| 297 | |
| 298 For more information on settings type 'help show' or 'help set'. | |
| 299 | |
| 300 If <args> are provided to the run command, it is the same as typing | |
| 301 'set args <args>' followed by 'run'. | |
| 302 """; | |
| 303 | |
| 304 Future run(List<String> cmdArgs) { | |
| 305 if (isRunning) { | |
| 306 // TODO(turnidge): Implement modal y/n dialog to stop running script. | |
| 307 print("There is already a running dart process. " | |
| 308 "Try 'kill'."); | |
| 309 return new Future.value(); | |
| 310 } | |
| 311 assert(targetProcess == null); | |
| 312 if (settings['script'] == null) { | |
| 313 print("There is no script specified. " | |
| 314 "Use 'set script' to set the current script."); | |
| 315 return new Future.value(); | |
| 316 } | |
| 317 if (cmdArgs.length > 1) { | |
| 318 settings['args'] = cmdArgs.getRange(1, cmdArgs.length); | |
| 319 } | |
| 320 | |
| 321 // Build the process arguments. | |
| 322 var processArgs = ['--debug:$debugPort']; | |
| 323 if (verbose) { | |
| 324 processArgs.add('--verbose_debug'); | |
| 325 } | |
| 326 if (settings['vmargs'] != null) { | |
| 327 processArgs.addAll(settings['vmargs'].split(' ')); | |
| 328 } | |
| 329 processArgs.add(settings['script']); | |
| 330 if (settings['args'] != null) { | |
| 331 processArgs.addAll(settings['args'].split(' ')); | |
| 332 } | |
| 333 String vm = settings['vm']; | |
| 334 | |
| 335 isRunning = true; | |
| 336 cmdo.hide(); | |
| 337 return Process.start(vm, processArgs).then((Process process) { | |
| 338 print("Started process ${process.pid} '$vm ${processArgs.join(' ')}'"); | |
| 339 targetProcess = process; | |
| 340 process.stdin.close(); | |
| 341 | |
| 342 // TODO(turnidge): For now we only show full lines of output | |
| 343 // from the debugged process. Should show each character. | |
| 344 process.stdout | |
| 345 .transform(UTF8.decoder) | |
| 346 .transform(new LineSplitter()) | |
| 347 .listen((String line) { | |
| 348 cmdo.hide(); | |
| 349 // TODO(turnidge): Escape output in any way? | |
| 350 print(line); | |
| 351 cmdo.show(); | |
| 352 }); | |
| 353 | |
| 354 process.stderr | |
| 355 .transform(UTF8.decoder) | |
| 356 .transform(new LineSplitter()) | |
| 357 .listen((String line) { | |
| 358 cmdo.hide(); | |
| 359 print(line); | |
| 360 cmdo.show(); | |
| 361 }); | |
| 362 | |
| 363 process.exitCode.then((int exitCode) { | |
| 364 cmdo.hide(); | |
| 365 if (suppressNextExitCode) { | |
| 366 suppressNextExitCode = false; | |
| 367 } else { | |
| 368 if (exitCode == 0) { | |
| 369 print('Process exited normally.'); | |
| 370 } else { | |
| 371 print('Process exited with code $exitCode.'); | |
| 372 } | |
| 373 } | |
| 374 isRunning = false; | |
| 375 targetProcess = null; | |
| 376 cmdo.show(); | |
| 377 }); | |
| 378 | |
| 379 // Wait for the vm to open the debugging port. | |
| 380 return openVmSocket(0); | |
| 381 }); | |
| 382 } | |
| 383 } | |
| 384 | |
| 385 class KillCommand extends Command { | |
| 386 final name = 'kill'; | |
| 387 final helpShort = 'Kill the currently executing script.'; | |
| 388 | |
| 389 Future run(List<String> cmdArgs) { | |
| 390 if (!isRunning) { | |
| 391 print('There is no running script.'); | |
| 392 return new Future.value(); | |
| 393 } | |
| 394 assert(targetProcess != null); | |
| 395 bool result = targetProcess.kill(); | |
| 396 if (result) { | |
| 397 print('Process killed.'); | |
| 398 suppressNextExitCode = true; | |
| 399 } else { | |
| 400 print('Unable to kill process ${targetProcess.pid}'); | |
| 401 } | |
| 402 return new Future.value(); | |
| 403 } | |
| 404 } | |
| 405 | |
| 116 typedef void HandlerType(Map response); | 406 typedef void HandlerType(Map response); |
| 117 | 407 |
| 118 HandlerType showPromptAfter(void handler(Map response)) { | 408 HandlerType showPromptAfter(void handler(Map response)) { |
| 119 // Hide the command prompt immediately. | 409 // Hide the command prompt immediately. |
| 120 return (response) { | 410 return (response) { |
| 121 handler(response); | 411 handler(response); |
| 122 cmdo.show(); | 412 cmdo.show(); |
| 123 }; | 413 }; |
| 124 } | 414 } |
| 125 | 415 |
| 126 | |
| 127 void processCommand(String cmdLine) { | 416 void processCommand(String cmdLine) { |
| 128 | 417 |
| 129 void huh() { | 418 void huh() { |
| 130 print("'$cmdLine' not understood, try h for help"); | 419 print("'$cmdLine' not understood, try 'help' for help."); |
| 131 } | 420 } |
| 132 | 421 |
| 422 cmdo.hide(); | |
| 133 seqNum++; | 423 seqNum++; |
| 134 cmdLine = cmdLine.trim(); | 424 cmdLine = cmdLine.trim(); |
| 135 var args = cmdLine.split(' '); | 425 var args = cmdLine.split(' '); |
| 136 if (args.length == 0) { | 426 if (args.length == 0) { |
| 137 return; | 427 return; |
| 138 } | 428 } |
| 139 var command = args[0]; | 429 var command = args[0]; |
| 430 | |
| 140 var resume_commands = | 431 var resume_commands = |
| 141 { 'r':'resume', 's':'stepOver', 'si':'stepInto', 'so':'stepOut'}; | 432 { 'r':'resume', 's':'stepOver', 'si':'stepInto', 'so':'stepOut'}; |
| 142 if (resume_commands[command] != null) { | 433 if (resume_commands[command] != null) { |
| 143 if (!checkPaused()) return; | 434 if (!checkPaused()) return; |
| 144 var cmd = { "id": seqNum, | 435 var cmd = { "id": seqNum, |
| 145 "command": resume_commands[command], | 436 "command": resume_commands[command], |
| 146 "params": { "isolateId" : currentIsolate.id } }; | 437 "params": { "isolateId" : currentIsolate.id } }; |
| 147 cmdo.hide(); | |
| 148 sendCmd(cmd).then(showPromptAfter(handleResumedResponse)); | 438 sendCmd(cmd).then(showPromptAfter(handleResumedResponse)); |
| 149 } else if (command == "bt") { | 439 } else if (command == "bt") { |
| 150 var cmd = { "id": seqNum, | 440 var cmd = { "id": seqNum, |
| 151 "command": "getStackTrace", | 441 "command": "getStackTrace", |
| 152 "params": { "isolateId" : currentIsolate.id } }; | 442 "params": { "isolateId" : currentIsolate.id } }; |
| 153 cmdo.hide(); | |
| 154 sendCmd(cmd).then(showPromptAfter(handleStackTraceResponse)); | 443 sendCmd(cmd).then(showPromptAfter(handleStackTraceResponse)); |
| 155 } else if (command == "ll") { | 444 } else if (command == "ll") { |
| 156 var cmd = { "id": seqNum, | 445 var cmd = { "id": seqNum, |
| 157 "command": "getLibraries", | 446 "command": "getLibraries", |
| 158 "params": { "isolateId" : currentIsolate.id } }; | 447 "params": { "isolateId" : currentIsolate.id } }; |
| 159 cmdo.hide(); | |
| 160 sendCmd(cmd).then(showPromptAfter(handleGetLibraryResponse)); | 448 sendCmd(cmd).then(showPromptAfter(handleGetLibraryResponse)); |
| 161 } else if (command == "sbp" && args.length >= 2) { | 449 } else if (command == "sbp" && args.length >= 2) { |
| 162 var url, line; | 450 var url, line; |
| 163 if (args.length == 2 && currentIsolate.pausedLocation != null) { | 451 if (args.length == 2 && currentIsolate.pausedLocation != null) { |
| 164 url = currentIsolate.pausedLocation["url"]; | 452 url = currentIsolate.pausedLocation["url"]; |
| 165 assert(url != null); | 453 assert(url != null); |
| 166 line = int.parse(args[1]); | 454 line = int.parse(args[1]); |
| 167 } else { | 455 } else { |
| 168 url = args[1]; | 456 url = args[1]; |
| 169 line = int.parse(args[2]); | 457 line = int.parse(args[2]); |
| 170 } | 458 } |
| 171 var cmd = { "id": seqNum, | 459 var cmd = { "id": seqNum, |
| 172 "command": "setBreakpoint", | 460 "command": "setBreakpoint", |
| 173 "params": { "isolateId" : currentIsolate.id, | 461 "params": { "isolateId" : currentIsolate.id, |
| 174 "url": url, | 462 "url": url, |
| 175 "line": line }}; | 463 "line": line }}; |
| 176 cmdo.hide(); | |
| 177 sendCmd(cmd).then(showPromptAfter(handleSetBpResponse)); | 464 sendCmd(cmd).then(showPromptAfter(handleSetBpResponse)); |
| 178 } else if (command == "rbp" && args.length == 2) { | 465 } else if (command == "rbp" && args.length == 2) { |
| 179 var cmd = { "id": seqNum, | 466 var cmd = { "id": seqNum, |
| 180 "command": "removeBreakpoint", | 467 "command": "removeBreakpoint", |
| 181 "params": { "isolateId" : currentIsolate.id, | 468 "params": { "isolateId" : currentIsolate.id, |
| 182 "breakpointId": int.parse(args[1]) } }; | 469 "breakpointId": int.parse(args[1]) } }; |
| 183 cmdo.hide(); | |
| 184 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); | 470 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); |
| 185 } else if (command == "ls" && args.length == 2) { | 471 } else if (command == "ls" && args.length == 2) { |
| 186 var cmd = { "id": seqNum, | 472 var cmd = { "id": seqNum, |
| 187 "command": "getScriptURLs", | 473 "command": "getScriptURLs", |
| 188 "params": { "isolateId" : currentIsolate.id, | 474 "params": { "isolateId" : currentIsolate.id, |
| 189 "libraryId": int.parse(args[1]) } }; | 475 "libraryId": int.parse(args[1]) } }; |
| 190 cmdo.hide(); | |
| 191 sendCmd(cmd).then(showPromptAfter(handleGetScriptsResponse)); | 476 sendCmd(cmd).then(showPromptAfter(handleGetScriptsResponse)); |
| 192 } else if (command == "eval" && args.length > 3) { | 477 } else if (command == "eval" && args.length > 3) { |
| 193 var expr = args.getRange(3, args.length).join(" "); | 478 var expr = args.getRange(3, args.length).join(" "); |
| 194 var target = args[1]; | 479 var target = args[1]; |
| 195 if (target == "obj") { | 480 if (target == "obj") { |
| 196 target = "objectId"; | 481 target = "objectId"; |
| 197 } else if (target == "cls") { | 482 } else if (target == "cls") { |
| 198 target = "classId"; | 483 target = "classId"; |
| 199 } else if (target == "lib") { | 484 } else if (target == "lib") { |
| 200 target = "libraryId"; | 485 target = "libraryId"; |
| 201 } else { | 486 } else { |
| 202 huh(); | 487 huh(); |
| 203 return; | 488 return; |
| 204 } | 489 } |
| 205 var cmd = { "id": seqNum, | 490 var cmd = { "id": seqNum, |
| 206 "command": "evaluateExpr", | 491 "command": "evaluateExpr", |
| 207 "params": { "isolateId": currentIsolate.id, | 492 "params": { "isolateId": currentIsolate.id, |
| 208 target: int.parse(args[2]), | 493 target: int.parse(args[2]), |
| 209 "expression": expr } }; | 494 "expression": expr } }; |
| 210 cmdo.hide(); | |
| 211 sendCmd(cmd).then(showPromptAfter(handleEvalResponse)); | 495 sendCmd(cmd).then(showPromptAfter(handleEvalResponse)); |
| 212 } else if (command == "po" && args.length == 2) { | 496 } else if (command == "po" && args.length == 2) { |
| 213 var cmd = { "id": seqNum, | 497 var cmd = { "id": seqNum, |
| 214 "command": "getObjectProperties", | 498 "command": "getObjectProperties", |
| 215 "params": { "isolateId" : currentIsolate.id, | 499 "params": { "isolateId" : currentIsolate.id, |
| 216 "objectId": int.parse(args[1]) } }; | 500 "objectId": int.parse(args[1]) } }; |
| 217 cmdo.hide(); | |
| 218 sendCmd(cmd).then(showPromptAfter(handleGetObjPropsResponse)); | 501 sendCmd(cmd).then(showPromptAfter(handleGetObjPropsResponse)); |
| 219 } else if (command == "pl" && args.length >= 3) { | 502 } else if (command == "pl" && args.length >= 3) { |
| 220 var cmd; | 503 var cmd; |
| 221 if (args.length == 3) { | 504 if (args.length == 3) { |
| 222 cmd = { "id": seqNum, | 505 cmd = { "id": seqNum, |
| 223 "command": "getListElements", | 506 "command": "getListElements", |
| 224 "params": { "isolateId" : currentIsolate.id, | 507 "params": { "isolateId" : currentIsolate.id, |
| 225 "objectId": int.parse(args[1]), | 508 "objectId": int.parse(args[1]), |
| 226 "index": int.parse(args[2]) } }; | 509 "index": int.parse(args[2]) } }; |
| 227 } else { | 510 } else { |
| 228 cmd = { "id": seqNum, | 511 cmd = { "id": seqNum, |
| 229 "command": "getListElements", | 512 "command": "getListElements", |
| 230 "params": { "isolateId" : currentIsolate.id, | 513 "params": { "isolateId" : currentIsolate.id, |
| 231 "objectId": int.parse(args[1]), | 514 "objectId": int.parse(args[1]), |
| 232 "index": int.parse(args[2]), | 515 "index": int.parse(args[2]), |
| 233 "length": int.parse(args[3]) } }; | 516 "length": int.parse(args[3]) } }; |
| 234 } | 517 } |
| 235 cmdo.hide(); | |
| 236 sendCmd(cmd).then(showPromptAfter(handleGetListResponse)); | 518 sendCmd(cmd).then(showPromptAfter(handleGetListResponse)); |
| 237 } else if (command == "pc" && args.length == 2) { | 519 } else if (command == "pc" && args.length == 2) { |
| 238 var cmd = { "id": seqNum, | 520 var cmd = { "id": seqNum, |
| 239 "command": "getClassProperties", | 521 "command": "getClassProperties", |
| 240 "params": { "isolateId" : currentIsolate.id, | 522 "params": { "isolateId" : currentIsolate.id, |
| 241 "classId": int.parse(args[1]) } }; | 523 "classId": int.parse(args[1]) } }; |
| 242 cmdo.hide(); | |
| 243 sendCmd(cmd).then(showPromptAfter(handleGetClassPropsResponse)); | 524 sendCmd(cmd).then(showPromptAfter(handleGetClassPropsResponse)); |
| 244 } else if (command == "plib" && args.length == 2) { | 525 } else if (command == "plib" && args.length == 2) { |
| 245 var cmd = { "id": seqNum, | 526 var cmd = { "id": seqNum, |
| 246 "command": "getLibraryProperties", | 527 "command": "getLibraryProperties", |
| 247 "params": {"isolateId" : currentIsolate.id, | 528 "params": {"isolateId" : currentIsolate.id, |
| 248 "libraryId": int.parse(args[1]) } }; | 529 "libraryId": int.parse(args[1]) } }; |
| 249 cmdo.hide(); | |
| 250 sendCmd(cmd).then(showPromptAfter(handleGetLibraryPropsResponse)); | 530 sendCmd(cmd).then(showPromptAfter(handleGetLibraryPropsResponse)); |
| 251 } else if (command == "slib" && args.length == 3) { | 531 } else if (command == "slib" && args.length == 3) { |
| 252 var cmd = { "id": seqNum, | 532 var cmd = { "id": seqNum, |
| 253 "command": "setLibraryProperties", | 533 "command": "setLibraryProperties", |
| 254 "params": {"isolateId" : currentIsolate.id, | 534 "params": {"isolateId" : currentIsolate.id, |
| 255 "libraryId": int.parse(args[1]), | 535 "libraryId": int.parse(args[1]), |
| 256 "debuggingEnabled": args[2] } }; | 536 "debuggingEnabled": args[2] } }; |
| 257 cmdo.hide(); | |
| 258 sendCmd(cmd).then(showPromptAfter(handleSetLibraryPropsResponse)); | 537 sendCmd(cmd).then(showPromptAfter(handleSetLibraryPropsResponse)); |
| 259 } else if (command == "pg" && args.length == 2) { | 538 } else if (command == "pg" && args.length == 2) { |
| 260 var cmd = { "id": seqNum, | 539 var cmd = { "id": seqNum, |
| 261 "command": "getGlobalVariables", | 540 "command": "getGlobalVariables", |
| 262 "params": { "isolateId" : currentIsolate.id, | 541 "params": { "isolateId" : currentIsolate.id, |
| 263 "libraryId": int.parse(args[1]) } }; | 542 "libraryId": int.parse(args[1]) } }; |
| 264 cmdo.hide(); | |
| 265 sendCmd(cmd).then(showPromptAfter(handleGetGlobalVarsResponse)); | 543 sendCmd(cmd).then(showPromptAfter(handleGetGlobalVarsResponse)); |
| 266 } else if (command == "gs" && args.length == 3) { | 544 } else if (command == "gs" && args.length == 3) { |
| 267 var cmd = { "id": seqNum, | 545 var cmd = { "id": seqNum, |
| 268 "command": "getScriptSource", | 546 "command": "getScriptSource", |
| 269 "params": { "isolateId" : currentIsolate.id, | 547 "params": { "isolateId" : currentIsolate.id, |
| 270 "libraryId": int.parse(args[1]), | 548 "libraryId": int.parse(args[1]), |
| 271 "url": args[2] } }; | 549 "url": args[2] } }; |
| 272 cmdo.hide(); | |
| 273 sendCmd(cmd).then(showPromptAfter(handleGetSourceResponse)); | 550 sendCmd(cmd).then(showPromptAfter(handleGetSourceResponse)); |
| 274 } else if (command == "tok" && args.length == 3) { | 551 } else if (command == "tok" && args.length == 3) { |
| 275 var cmd = { "id": seqNum, | 552 var cmd = { "id": seqNum, |
| 276 "command": "getLineNumberTable", | 553 "command": "getLineNumberTable", |
| 277 "params": { "isolateId" : currentIsolate.id, | 554 "params": { "isolateId" : currentIsolate.id, |
| 278 "libraryId": int.parse(args[1]), | 555 "libraryId": int.parse(args[1]), |
| 279 "url": args[2] } }; | 556 "url": args[2] } }; |
| 280 cmdo.hide(); | |
| 281 sendCmd(cmd).then(showPromptAfter(handleGetLineTableResponse)); | 557 sendCmd(cmd).then(showPromptAfter(handleGetLineTableResponse)); |
| 282 } else if (command == "epi" && args.length == 2) { | 558 } else if (command == "epi" && args.length == 2) { |
| 283 var cmd = { "id": seqNum, | 559 var cmd = { "id": seqNum, |
| 284 "command": "setPauseOnException", | 560 "command": "setPauseOnException", |
| 285 "params": { "isolateId" : currentIsolate.id, | 561 "params": { "isolateId" : currentIsolate.id, |
| 286 "exceptions": args[1] } }; | 562 "exceptions": args[1] } }; |
| 287 cmdo.hide(); | |
| 288 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); | 563 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); |
| 289 } else if (command == "li") { | 564 } else if (command == "li") { |
| 290 var cmd = { "id": seqNum, "command": "getIsolateIds" }; | 565 var cmd = { "id": seqNum, "command": "getIsolateIds" }; |
| 291 cmdo.hide(); | |
| 292 sendCmd(cmd).then(showPromptAfter(handleGetIsolatesResponse)); | 566 sendCmd(cmd).then(showPromptAfter(handleGetIsolatesResponse)); |
| 293 } else if (command == "sci" && args.length == 2) { | 567 } else if (command == "sci" && args.length == 2) { |
| 294 var id = int.parse(args[1]); | 568 var id = int.parse(args[1]); |
| 295 if (targetIsolates[id] != null) { | 569 if (targetIsolates[id] != null) { |
| 296 currentIsolate = targetIsolates[id]; | 570 currentIsolate = targetIsolates[id]; |
| 297 print("Setting current target isolate to $id"); | 571 print("Setting current target isolate to $id"); |
| 298 } else { | 572 } else { |
| 299 print("$id is not a valid isolate id"); | 573 print("$id is not a valid isolate id"); |
| 300 } | 574 } |
| 575 cmdo.show(); | |
| 301 } else if (command == "i" && args.length == 2) { | 576 } else if (command == "i" && args.length == 2) { |
| 302 var cmd = { "id": seqNum, | 577 var cmd = { "id": seqNum, |
| 303 "command": "interrupt", | 578 "command": "interrupt", |
| 304 "params": { "isolateId": int.parse(args[1]) } }; | 579 "params": { "isolateId": int.parse(args[1]) } }; |
| 305 cmdo.hide(); | |
| 306 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); | 580 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); |
| 307 } else if (command == "q") { | 581 } else if (command.length == 0) { |
| 308 quitShell(); | 582 huh(); |
| 309 } else if (command == "h") { | 583 cmdo.show(); |
| 310 printHelp(); | |
| 311 } else { | 584 } else { |
| 312 huh(); | 585 // TODO(turnidge): Migrate all commands into this . |
| 586 var matches = matchCommand(command, true); | |
| 587 if (matches.length == 0) { | |
| 588 huh(); | |
| 589 cmdo.show(); | |
| 590 } else if (matches.length == 1) { | |
| 591 matches[0].run(args).then((_) { | |
| 592 cmdo.show(); | |
| 593 }); | |
| 594 } else { | |
| 595 var matchNames = matches.map((handler) => handler.name); | |
| 596 print("Ambigous command '$command' : ${matchNames.toList()}"); | |
| 597 cmdo.show(); | |
| 598 } | |
| 313 } | 599 } |
| 314 } | 600 } |
| 315 | 601 |
| 316 | 602 |
| 603 void processError(error, trace) { | |
| 604 cmdo.hide(); | |
| 605 print("\nInternal error:\n$error\n$trace"); | |
| 606 cmdo.show(); | |
| 607 } | |
| 608 | |
| 609 | |
| 610 void processDone() { | |
| 611 debuggerQuit(); | |
| 612 } | |
| 613 | |
| 614 | |
| 317 String remoteObject(value) { | 615 String remoteObject(value) { |
| 318 var kind = value["kind"]; | 616 var kind = value["kind"]; |
| 319 var text = value["text"]; | 617 var text = value["text"]; |
| 320 var id = value["objectId"]; | 618 var id = value["objectId"]; |
| 321 if (kind == "string") { | 619 if (kind == "string") { |
| 322 return "(string, id $id) '$text'"; | 620 return "(string, id $id) '$text'"; |
| 323 } else if (kind == "list") { | 621 } else if (kind == "list") { |
| 324 var len = value["length"]; | 622 var len = value["length"]; |
| 325 return "(list, id $id, len $len) $text"; | 623 return "(list, id $id, len $len) $text"; |
| 326 } else if (kind == "object") { | 624 } else if (kind == "object") { |
| (...skipping 430 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 757 List<String> debuggerCommandCompleter(List<String> commandParts) { | 1055 List<String> debuggerCommandCompleter(List<String> commandParts) { |
| 758 List<String> completions = new List<String>(); | 1056 List<String> completions = new List<String>(); |
| 759 | 1057 |
| 760 // TODO(turnidge): Have a global command table and use it to for | 1058 // TODO(turnidge): Have a global command table and use it to for |
| 761 // help messages, command completion, and command dispatching. For now | 1059 // help messages, command completion, and command dispatching. For now |
| 762 // we hardcode the list here. | 1060 // we hardcode the list here. |
| 763 // | 1061 // |
| 764 // TODO(turnidge): Implement completion for arguments as well. | 1062 // TODO(turnidge): Implement completion for arguments as well. |
| 765 List<String> allCommands = ['q', 'bt', 'r', 's', 'so', 'si', 'sbp', 'rbp', | 1063 List<String> allCommands = ['q', 'bt', 'r', 's', 'so', 'si', 'sbp', 'rbp', |
| 766 'po', 'eval', 'pl', 'pc', 'll', 'plib', 'slib', | 1064 'po', 'eval', 'pl', 'pc', 'll', 'plib', 'slib', |
| 767 'pg', 'ls', 'gs', 'tok', 'epi', 'li', 'i', 'h']; | 1065 'pg', 'ls', 'gs', 'tok', 'epi', 'li', 'i', |
| 1066 '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
| |
| 768 | 1067 |
| 769 // Completion of first word in the command. | 1068 // Completion of first word in the command. |
| 770 if (commandParts.length == 1) { | 1069 if (commandParts.length == 1) { |
| 771 String prefix = commandParts.last; | 1070 String prefix = commandParts.last; |
| 772 for (String command in allCommands) { | 1071 for (var command in allCommands) { |
| 773 if (command.startsWith(prefix)) { | 1072 if (command.startsWith(prefix)) { |
| 774 completions.add(command); | 1073 completions.add(command); |
| 775 } | 1074 } |
| 776 } | 1075 } |
| 1076 for (var command in commandList) { | |
| 1077 if (command.name.startsWith(prefix)) { | |
| 1078 completions.add(command.name); | |
| 1079 } | |
| 1080 } | |
| 777 } | 1081 } |
| 778 | 1082 |
| 779 return completions; | 1083 return completions; |
| 780 } | 1084 } |
| 781 | 1085 |
| 782 void debuggerMain() { | 1086 Future closeCommando() { |
| 783 outstandingCommands = new Map<int, Completer>(); | 1087 var subscription = cmdSubscription; |
| 784 Socket.connect("127.0.0.1", 5858).then((s) { | 1088 cmdSubscription = null; |
| 785 vmSock = s; | 1089 cmdo = null; |
| 786 vmSock.setOption(SocketOption.TCP_NODELAY, true); | 1090 |
| 787 var stringStream = vmSock.transform(UTF8.decoder); | 1091 var future = subscription.cancel(); |
| 788 vmSubscription = stringStream.listen( | 1092 if (future != null) { |
| 789 (String data) { | 1093 return future; |
| 790 processVmData(data); | 1094 } else { |
| 791 }, | 1095 return new Future.value(); |
| 792 onDone: () { | 1096 } |
| 793 print("VM debugger connection closed"); | 1097 } |
| 794 quitShell(); | 1098 |
| 795 }, | 1099 |
| 796 onError: (err) { | 1100 Future openVmSocket(int attempt) { |
| 797 print("Error in debug connection: $err"); | 1101 return Socket.connect("127.0.0.1", debugPort).then((s) { |
| 798 // TODO(floitsch): do we want to print the stack trace? | 1102 vmSock = s; |
| 799 quitShell(); | 1103 vmSock.setOption(SocketOption.TCP_NODELAY, true); |
| 1104 var stringStream = vmSock.transform(UTF8.decoder); | |
| 1105 outstandingCommands = new Map<int, Completer>(); | |
| 1106 vmSubscription = stringStream.listen( | |
| 1107 (String data) { | |
| 1108 processVmData(data); | |
| 1109 }, | |
| 1110 onDone: () { | |
| 1111 cmdo.hide(); | |
| 1112 if (verbose) { | |
| 1113 print("VM debugger connection closed"); | |
| 1114 } | |
| 1115 closeVmSocket().then((_) { | |
| 1116 cmdo.show(); | |
| 1117 }); | |
| 1118 }, | |
| 1119 onError: (err) { | |
| 1120 cmdo.hide(); | |
| 1121 // TODO(floitsch): do we want to print the stack trace? | |
| 1122 print("Error in debug connection: $err"); | |
| 1123 closeVmSocket().then((_) { | |
| 1124 cmdo.show(); | |
| 1125 }); | |
| 1126 }); | |
| 1127 }, | |
| 1128 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
| |
| 1129 // We were unable to connect to the debugger's port. | |
| 1130 var delay; | |
| 1131 if (attempt < 10) { | |
| 1132 delay = new Duration(milliseconds:10); | |
| 1133 } else if (attempt < 20) { | |
| 1134 delay = new Duration(seconds:1); | |
| 1135 } else { | |
| 1136 // Too many retries. Give up. | |
| 1137 print('Timed out waiting for debugger to start.\nError: $e'); | |
| 1138 return closeVmSocket(); | |
| 1139 } | |
| 1140 | |
| 1141 // Wait and retry. | |
| 1142 return new Future.delayed(delay, () { | |
| 1143 int tmp = attempt + 1; | |
| 1144 openVmSocket(tmp); | |
| 800 }); | 1145 }); |
| 801 cmdo = new Commando(stdin, stdout, processCommand, | 1146 }); |
| 802 completer : debuggerCommandCompleter); | 1147 } |
| 803 }); | 1148 |
| 1149 Future closeVmSocket() { | |
| 1150 if (vmSubscription == null) { | |
| 1151 // Already closed, nothing to do. | |
| 1152 assert(vmSock == null); | |
| 1153 return new Future.value(); | |
| 1154 } | |
| 1155 | |
| 1156 var subscription = vmSubscription; | |
| 1157 var sock = vmSock; | |
| 1158 | |
| 1159 // Wait for the socket to close and the subscription to be | |
| 1160 // cancelled. Perhaps overkill, but it means we know these will be | |
| 1161 // done. | |
| 1162 // | |
| 1163 // This is uglier than it needs to be since cancel can return null. | |
| 1164 var cleanupFutures = [sock.close()]; | |
| 1165 var future = subscription.cancel(); | |
| 1166 if (future != null) { | |
| 1167 cleanupFutures.add(future); | |
| 1168 } | |
| 1169 | |
| 1170 vmSubscription = null; | |
| 1171 vmSock = null; | |
| 1172 outstandingCommands = null; | |
| 1173 | |
| 1174 return Future.wait(cleanupFutures); | |
| 1175 } | |
| 1176 | |
| 1177 Future debuggerQuit() { | |
| 1178 // Kill target process, if any. | |
| 1179 if (targetProcess != null) { | |
| 1180 if (!targetProcess.kill()) { | |
| 1181 print('Unable to kill process ${targetProcess.pid}'); | |
| 1182 } | |
| 1183 } | |
| 1184 | |
| 1185 // Restore terminal settings, close connections. | |
| 1186 return Future.wait([closeCommando(), closeVmSocket()]).then((_) { | |
| 1187 exit(0); | |
| 1188 | |
| 1189 // Unreachable. | |
| 1190 return new Future.value(); | |
| 1191 }); | |
| 1192 } | |
| 1193 | |
| 1194 | |
| 1195 void parseArgs(List<String> args) { | |
| 1196 int pos = 0; | |
| 1197 settings['vm'] = Platform.executable; | |
| 1198 while (pos < args.length && args[pos].startsWith('-')) { | |
| 1199 pos++; | |
| 1200 } | |
| 1201 if (pos < args.length) { | |
| 1202 settings['vmargs'] = args.getRange(0, pos).join(' '); | |
| 1203 settings['script'] = args[pos]; | |
| 1204 settings['args'] = args.getRange(pos + 1, args.length).join(' '); | |
| 1205 } | |
| 804 } | 1206 } |
| 805 | 1207 |
| 806 void main(List<String> args) { | 1208 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
| |
| 807 if (args.length > 0) { | 1209 parseArgs(args); |
| 808 if (verbose) { | |
| 809 args = <String>['--debug', '--verbose_debug']..addAll(args); | |
| 810 } else { | |
| 811 args = <String>['--debug']..addAll(args); | |
| 812 } | |
| 813 Process.start(Platform.executable, args).then((Process process) { | |
| 814 targetProcess = process; | |
| 815 process.stdin.close(); | |
| 816 | 1210 |
| 817 // TODO(turnidge): For now we only show full lines of output | 1211 cmdo = new Commando(completer: debuggerCommandCompleter); |
| 818 // from the debugged process. Should show each character. | 1212 cmdSubscription = cmdo.commands.listen(processCommand, |
| 819 process.stdout | 1213 onError: processError, |
| 820 .transform(UTF8.decoder) | 1214 onDone: processDone); |
| 821 .transform(new LineSplitter()) | |
| 822 .listen((String line) { | |
| 823 // Hide/show command prompt across asynchronous output. | |
| 824 if (cmdo != null) { | |
| 825 cmdo.hide(); | |
| 826 } | |
| 827 print("$line"); | |
| 828 if (cmdo != null) { | |
| 829 cmdo.show(); | |
| 830 } | |
| 831 }); | |
| 832 | |
| 833 process.exitCode.then((int exitCode) { | |
| 834 if (exitCode == 0) { | |
| 835 print('Program exited normally.'); | |
| 836 } else { | |
| 837 print('Program exited with code $exitCode.'); | |
| 838 } | |
| 839 }); | |
| 840 | |
| 841 debuggerMain(); | |
| 842 }); | |
| 843 } else { | |
| 844 debuggerMain(); | |
| 845 } | |
| 846 } | 1215 } |
| OLD | NEW |