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 |
33 Process targetProcess; | 35 bool isDebugging = false; |
| 36 Process targetProcess = null; |
| 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 ConnectCommand(), |
| 100 new DisconnectCommand(), |
| 101 new SetCommand(), |
| 102 new ShowCommand() ]; |
| 103 |
| 104 |
| 105 Command matchCommand(String commandName, bool exactMatchWins) { |
| 106 List matches = []; |
| 107 for (var command in commandList) { |
| 108 if (command.name.startsWith(commandName)) { |
| 109 if (exactMatchWins && command.name == commandName) { |
| 110 // Exact match |
| 111 return [command]; |
| 112 } else { |
| 113 matches.add(command); |
| 114 } |
| 115 } |
| 116 } |
| 117 return matches; |
| 118 } |
| 119 |
| 120 abstract class Command { |
| 121 String get name; |
| 122 Future run(List<String> args); |
| 123 } |
| 124 |
| 125 class HelpCommand extends Command { |
| 126 final name = 'help'; |
| 127 final helpShort = 'Show a list of debugger commands'; |
| 128 final helpLong =""" |
| 129 Show a list of debugger commands or get more information about a |
| 130 particular command. |
| 131 |
| 132 Usage: |
| 133 help |
| 134 help <command> |
| 135 """; |
| 136 |
| 137 Future run(List<String> args) { |
| 138 if (args.length == 1) { |
| 139 print("Debugger commands:\n"); |
| 140 for (var command in commandList) { |
| 141 const tabStop = 12; |
| 142 var spaces = _spaces(max(1, (tabStop - command.name.length))); |
| 143 print(' ${command.name}${spaces}${command.helpShort}'); |
| 144 } |
| 145 |
| 146 // TODO(turnidge): Convert all commands to use the Command class. |
| 147 print(""" |
| 148 bt Show backtrace |
| 149 r Resume execution |
| 150 s Single step |
| 151 so Step over |
| 152 si Step into |
| 153 sbp [<file>] <line> Set breakpoint |
| 154 rbp <id> Remove breakpoint with given id |
| 155 po <id> Print object info for given id |
| 156 eval obj <id> <expr> Evaluate expr on object id |
| 157 eval cls <id> <expr> Evaluate expr on class id |
| 158 eval lib <id> <expr> Evaluate expr in toplevel of library id |
| 159 pl <id> <idx> [<len>] Print list element/slice |
| 160 pc <id> Print class info for given id |
| 161 ll List loaded libraries |
| 162 plib <id> Print library info for given library id |
| 163 slib <id> <true|false> Set library id debuggable |
| 164 pg <id> Print all global variables visible within given library id |
| 165 ls <lib_id> List loaded scripts in library |
| 166 gs <lib_id> <script_url> Get source text of script in library |
| 167 tok <lib_id> <script_url> Get line and token table of script in library |
| 168 epi <none|all|unhandled> Set exception pause info |
| 169 li List ids of all isolates in the VM |
| 170 sci <id> Set current target isolate |
| 171 i <id> Interrupt execution of given isolate id |
| 172 """); |
| 173 |
| 174 print("For more information about a particular command, type:\n\n" |
| 175 " help <command>\n"); |
| 176 |
| 177 print("Commands may be abbreviated: e.g. type 'h' for 'help.\n"); |
| 178 } else if (args.length == 2) { |
| 179 var commandName = args[1]; |
| 180 var matches = matchCommand(commandName, true); |
| 181 if (matches.length == 0) { |
| 182 print("Command '$commandName' not recognized. " |
| 183 "Try 'help' for a list of commands."); |
| 184 } else { |
| 185 for (var command in matches) { |
| 186 print("---- ${command.name} ----\n${command.helpLong}"); |
| 187 } |
| 188 } |
| 189 } else { |
| 190 print("Command '$command' not recognized. " |
| 191 "Try 'help' for a list of commands."); |
| 192 } |
| 193 |
| 194 return new Future.value(); |
| 195 } |
| 196 } |
| 197 |
| 198 |
| 199 class QuitCommand extends Command { |
| 200 final name = 'quit'; |
| 201 final helpShort = 'Quit the debugger.'; |
| 202 final helpLong =""" |
| 203 Quit the debugger. |
| 204 |
| 205 Usage: |
| 206 quit |
| 207 """; |
| 208 |
| 209 Future run(List<String> args) { |
| 210 if (args.length > 1) { |
| 211 print("Unexpected arguments to $name command."); |
| 212 return new Future.value(); |
| 213 } |
| 214 return debuggerQuit(); |
| 215 } |
| 216 } |
| 217 |
| 218 class SetCommand extends Command { |
| 219 final name = 'set'; |
| 220 final helpShort = 'Change the value of a debugger setting.'; |
| 221 final helpLong =""" |
| 222 Change the value of a debugger setting. |
| 223 |
| 224 Usage: |
| 225 set <setting> <value> |
| 226 |
| 227 Valid settings are: |
| 228 ${validSettings.join('\n ')}. |
| 229 |
| 230 See also 'help show'. |
| 231 """; |
| 232 |
| 233 Future run(List<String> args) { |
| 234 if (args.length < 3 || !validSettings.contains(args[1])) { |
| 235 print("Undefined $name command. Try 'help $name'."); |
| 236 return new Future.value(); |
| 237 } |
| 238 var option = args[1]; |
| 239 var value = args.getRange(2, args.length).join(' '); |
| 240 settings[option] = value; |
| 241 return new Future.value(); |
| 242 } |
| 243 } |
| 244 |
| 245 class ShowCommand extends Command { |
| 246 final name = 'show'; |
| 247 final helpShort = 'Show the current value of a debugger setting.'; |
| 248 final helpLong =""" |
| 249 Show the current value of a debugger setting. |
| 250 |
| 251 Usage: |
| 252 show |
| 253 show <setting> |
| 254 |
| 255 If no <setting> is specified, all current settings are shown. |
| 256 |
| 257 Valid settings are: |
| 258 ${validSettings.join('\n ')}. |
| 259 |
| 260 See also 'help set'. |
| 261 """; |
| 262 |
| 263 Future run(List<String> args) { |
| 264 if (args.length == 1) { |
| 265 for (var option in validSettings) { |
| 266 var value = settings[option]; |
| 267 print("$option = '$value'"); |
| 268 } |
| 269 } else if (args.length == 2 && validSettings.contains(args[1])) { |
| 270 var option = args[1]; |
| 271 var value = settings[option]; |
| 272 if (value == null) { |
| 273 print('$option has not been set.'); |
| 274 } else { |
| 275 print("$option = '$value'"); |
| 276 } |
| 277 return new Future.value(); |
| 278 } else { |
| 279 print("Undefined $name command. Try 'help $name'."); |
| 280 } |
| 281 return new Future.value(); |
| 282 } |
| 283 } |
| 284 |
| 285 class RunCommand extends Command { |
| 286 final name = 'run'; |
| 287 final helpShort = "Run the currrent script."; |
| 288 final helpLong =""" |
| 289 Runs the current script. |
| 290 |
| 291 Usage: |
| 292 run |
| 293 run <args> |
| 294 |
| 295 The current script will be run on the current vm. The 'vm' and |
| 296 'vmargs' settings are used to specify the current vm and vm arguments. |
| 297 The 'script' and 'args' settings are used to specify the current |
| 298 script and script arguments. |
| 299 |
| 300 For more information on settings type 'help show' or 'help set'. |
| 301 |
| 302 If <args> are provided to the run command, it is the same as typing |
| 303 'set args <args>' followed by 'run'. |
| 304 """; |
| 305 |
| 306 Future run(List<String> cmdArgs) { |
| 307 if (isDebugging) { |
| 308 // TODO(turnidge): Implement modal y/n dialog to stop running script. |
| 309 print("There is already a running dart process. " |
| 310 "Try 'kill'."); |
| 311 return new Future.value(); |
| 312 } |
| 313 assert(targetProcess == null); |
| 314 if (settings['script'] == null) { |
| 315 print("There is no script specified. " |
| 316 "Use 'set script' to set the current script."); |
| 317 return new Future.value(); |
| 318 } |
| 319 if (cmdArgs.length > 1) { |
| 320 settings['args'] = cmdArgs.getRange(1, cmdArgs.length); |
| 321 } |
| 322 |
| 323 // Build the process arguments. |
| 324 var processArgs = ['--debug:$debugPort']; |
| 325 if (verbose) { |
| 326 processArgs.add('--verbose_debug'); |
| 327 } |
| 328 if (settings['vmargs'] != null) { |
| 329 processArgs.addAll(settings['vmargs'].split(' ')); |
| 330 } |
| 331 processArgs.add(settings['script']); |
| 332 if (settings['args'] != null) { |
| 333 processArgs.addAll(settings['args'].split(' ')); |
| 334 } |
| 335 String vm = settings['vm']; |
| 336 |
| 337 isDebugging = true; |
| 338 cmdo.hide(); |
| 339 return Process.start(vm, processArgs).then((Process process) { |
| 340 print("Started process ${process.pid} '$vm ${processArgs.join(' ')}'"); |
| 341 targetProcess = process; |
| 342 process.stdin.close(); |
| 343 |
| 344 // TODO(turnidge): For now we only show full lines of output |
| 345 // from the debugged process. Should show each character. |
| 346 process.stdout |
| 347 .transform(UTF8.decoder) |
| 348 .transform(new LineSplitter()) |
| 349 .listen((String line) { |
| 350 cmdo.hide(); |
| 351 // TODO(turnidge): Escape output in any way? |
| 352 print(line); |
| 353 cmdo.show(); |
| 354 }); |
| 355 |
| 356 process.stderr |
| 357 .transform(UTF8.decoder) |
| 358 .transform(new LineSplitter()) |
| 359 .listen((String line) { |
| 360 cmdo.hide(); |
| 361 print(line); |
| 362 cmdo.show(); |
| 363 }); |
| 364 |
| 365 process.exitCode.then((int exitCode) { |
| 366 cmdo.hide(); |
| 367 if (suppressNextExitCode) { |
| 368 suppressNextExitCode = false; |
| 369 } else { |
| 370 if (exitCode == 0) { |
| 371 print('Process exited normally.'); |
| 372 } else { |
| 373 print('Process exited with code $exitCode.'); |
| 374 } |
| 375 } |
| 376 targetProcess = null; |
| 377 cmdo.show(); |
| 378 }); |
| 379 |
| 380 // Wait for the vm to open the debugging port. |
| 381 return openVmSocket(0); |
| 382 }); |
| 383 } |
| 384 } |
| 385 |
| 386 class KillCommand extends Command { |
| 387 final name = 'kill'; |
| 388 final helpShort = 'Kill the currently executing script.'; |
| 389 final helpLong =""" |
| 390 Kill the currently executing script. |
| 391 |
| 392 Usage: |
| 393 kill |
| 394 """; |
| 395 |
| 396 Future run(List<String> cmdArgs) { |
| 397 if (!isDebugging) { |
| 398 print('There is no running script.'); |
| 399 return new Future.value(); |
| 400 } |
| 401 if (targetProcess == null) { |
| 402 print("The active dart process was not started with 'run'. " |
| 403 "Try 'disconnect' instead."); |
| 404 return new Future.value(); |
| 405 } |
| 406 assert(targetProcess != null); |
| 407 bool result = targetProcess.kill(); |
| 408 if (result) { |
| 409 print('Process killed.'); |
| 410 suppressNextExitCode = true; |
| 411 } else { |
| 412 print('Unable to kill process ${targetProcess.pid}'); |
| 413 } |
| 414 return new Future.value(); |
| 415 } |
| 416 } |
| 417 |
| 418 class ConnectCommand extends Command { |
| 419 final name = 'connect'; |
| 420 final helpShort = "Connect to a running dart script."; |
| 421 final helpLong =""" |
| 422 Connect to a running dart script. |
| 423 |
| 424 Usage: |
| 425 connect |
| 426 connect <port> |
| 427 |
| 428 The debugger will connect to a dart script which has already been |
| 429 started with the --debug option. If no port is provided, the debugger |
| 430 will attempt to connect on the default debugger port. |
| 431 """; |
| 432 |
| 433 Future run(List<String> cmdArgs) { |
| 434 if (cmdArgs.length > 2) { |
| 435 print("Too many arguments to 'connect'."); |
| 436 } |
| 437 if (isDebugging) { |
| 438 // TODO(turnidge): Implement modal y/n dialog to stop running script. |
| 439 print("There is already a running dart process. " |
| 440 "Try 'kill'."); |
| 441 return new Future.value(); |
| 442 } |
| 443 assert(targetProcess == null); |
| 444 if (cmdArgs.length == 2) { |
| 445 debugPort = int.parse(cmdArgs[1]); |
| 446 } |
| 447 |
| 448 isDebugging = true; |
| 449 cmdo.hide(); |
| 450 return openVmSocket(0); |
| 451 } |
| 452 } |
| 453 |
| 454 class DisconnectCommand extends Command { |
| 455 final name = 'disconnect'; |
| 456 final helpShort = "Disconnect from a running dart script."; |
| 457 final helpLong =""" |
| 458 Disconnect from a running dart script. |
| 459 |
| 460 Usage: |
| 461 disconnect |
| 462 |
| 463 The debugger will disconnect from a dart script's debugging port. The |
| 464 script must have been connected to earlier with the 'connect' command. |
| 465 """; |
| 466 |
| 467 Future run(List<String> cmdArgs) { |
| 468 if (cmdArgs.length > 1) { |
| 469 print("Too many arguments to 'disconnect'."); |
| 470 } |
| 471 if (!isDebugging) { |
| 472 // TODO(turnidge): Implement modal y/n dialog to stop running script. |
| 473 print("There is no active dart process. " |
| 474 "Try 'connect'."); |
| 475 return new Future.value(); |
| 476 } |
| 477 if (targetProcess != null) { |
| 478 print("The active dart process was started with 'run'. " |
| 479 "Try 'kill'."); |
| 480 } |
| 481 |
| 482 cmdo.hide(); |
| 483 return closeVmSocket(); |
| 484 } |
| 485 } |
| 486 |
116 typedef void HandlerType(Map response); | 487 typedef void HandlerType(Map response); |
117 | 488 |
118 HandlerType showPromptAfter(void handler(Map response)) { | 489 HandlerType showPromptAfter(void handler(Map response)) { |
119 // Hide the command prompt immediately. | 490 // Hide the command prompt immediately. |
120 return (response) { | 491 return (response) { |
121 handler(response); | 492 handler(response); |
122 cmdo.show(); | 493 cmdo.show(); |
123 }; | 494 }; |
124 } | 495 } |
125 | 496 |
126 | |
127 void processCommand(String cmdLine) { | 497 void processCommand(String cmdLine) { |
128 | 498 |
129 void huh() { | 499 void huh() { |
130 print("'$cmdLine' not understood, try h for help"); | 500 print("'$cmdLine' not understood, try 'help' for help."); |
131 } | 501 } |
132 | 502 |
| 503 cmdo.hide(); |
133 seqNum++; | 504 seqNum++; |
134 cmdLine = cmdLine.trim(); | 505 cmdLine = cmdLine.trim(); |
135 var args = cmdLine.split(' '); | 506 var args = cmdLine.split(' '); |
136 if (args.length == 0) { | 507 if (args.length == 0) { |
137 return; | 508 return; |
138 } | 509 } |
139 var command = args[0]; | 510 var command = args[0]; |
| 511 |
140 var resume_commands = | 512 var resume_commands = |
141 { 'r':'resume', 's':'stepOver', 'si':'stepInto', 'so':'stepOut'}; | 513 { 'r':'resume', 's':'stepOver', 'si':'stepInto', 'so':'stepOut'}; |
142 if (resume_commands[command] != null) { | 514 if (resume_commands[command] != null) { |
143 if (!checkPaused()) return; | 515 if (!checkPaused()) return; |
144 var cmd = { "id": seqNum, | 516 var cmd = { "id": seqNum, |
145 "command": resume_commands[command], | 517 "command": resume_commands[command], |
146 "params": { "isolateId" : currentIsolate.id } }; | 518 "params": { "isolateId" : currentIsolate.id } }; |
147 cmdo.hide(); | |
148 sendCmd(cmd).then(showPromptAfter(handleResumedResponse)); | 519 sendCmd(cmd).then(showPromptAfter(handleResumedResponse)); |
149 } else if (command == "bt") { | 520 } else if (command == "bt") { |
150 var cmd = { "id": seqNum, | 521 var cmd = { "id": seqNum, |
151 "command": "getStackTrace", | 522 "command": "getStackTrace", |
152 "params": { "isolateId" : currentIsolate.id } }; | 523 "params": { "isolateId" : currentIsolate.id } }; |
153 cmdo.hide(); | |
154 sendCmd(cmd).then(showPromptAfter(handleStackTraceResponse)); | 524 sendCmd(cmd).then(showPromptAfter(handleStackTraceResponse)); |
155 } else if (command == "ll") { | 525 } else if (command == "ll") { |
156 var cmd = { "id": seqNum, | 526 var cmd = { "id": seqNum, |
157 "command": "getLibraries", | 527 "command": "getLibraries", |
158 "params": { "isolateId" : currentIsolate.id } }; | 528 "params": { "isolateId" : currentIsolate.id } }; |
159 cmdo.hide(); | |
160 sendCmd(cmd).then(showPromptAfter(handleGetLibraryResponse)); | 529 sendCmd(cmd).then(showPromptAfter(handleGetLibraryResponse)); |
161 } else if (command == "sbp" && args.length >= 2) { | 530 } else if (command == "sbp" && args.length >= 2) { |
162 var url, line; | 531 var url, line; |
163 if (args.length == 2 && currentIsolate.pausedLocation != null) { | 532 if (args.length == 2 && currentIsolate.pausedLocation != null) { |
164 url = currentIsolate.pausedLocation["url"]; | 533 url = currentIsolate.pausedLocation["url"]; |
165 assert(url != null); | 534 assert(url != null); |
166 line = int.parse(args[1]); | 535 line = int.parse(args[1]); |
167 } else { | 536 } else { |
168 url = args[1]; | 537 url = args[1]; |
169 line = int.parse(args[2]); | 538 line = int.parse(args[2]); |
170 } | 539 } |
171 var cmd = { "id": seqNum, | 540 var cmd = { "id": seqNum, |
172 "command": "setBreakpoint", | 541 "command": "setBreakpoint", |
173 "params": { "isolateId" : currentIsolate.id, | 542 "params": { "isolateId" : currentIsolate.id, |
174 "url": url, | 543 "url": url, |
175 "line": line }}; | 544 "line": line }}; |
176 cmdo.hide(); | |
177 sendCmd(cmd).then(showPromptAfter(handleSetBpResponse)); | 545 sendCmd(cmd).then(showPromptAfter(handleSetBpResponse)); |
178 } else if (command == "rbp" && args.length == 2) { | 546 } else if (command == "rbp" && args.length == 2) { |
179 var cmd = { "id": seqNum, | 547 var cmd = { "id": seqNum, |
180 "command": "removeBreakpoint", | 548 "command": "removeBreakpoint", |
181 "params": { "isolateId" : currentIsolate.id, | 549 "params": { "isolateId" : currentIsolate.id, |
182 "breakpointId": int.parse(args[1]) } }; | 550 "breakpointId": int.parse(args[1]) } }; |
183 cmdo.hide(); | |
184 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); | 551 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); |
185 } else if (command == "ls" && args.length == 2) { | 552 } else if (command == "ls" && args.length == 2) { |
186 var cmd = { "id": seqNum, | 553 var cmd = { "id": seqNum, |
187 "command": "getScriptURLs", | 554 "command": "getScriptURLs", |
188 "params": { "isolateId" : currentIsolate.id, | 555 "params": { "isolateId" : currentIsolate.id, |
189 "libraryId": int.parse(args[1]) } }; | 556 "libraryId": int.parse(args[1]) } }; |
190 cmdo.hide(); | |
191 sendCmd(cmd).then(showPromptAfter(handleGetScriptsResponse)); | 557 sendCmd(cmd).then(showPromptAfter(handleGetScriptsResponse)); |
192 } else if (command == "eval" && args.length > 3) { | 558 } else if (command == "eval" && args.length > 3) { |
193 var expr = args.getRange(3, args.length).join(" "); | 559 var expr = args.getRange(3, args.length).join(" "); |
194 var target = args[1]; | 560 var target = args[1]; |
195 if (target == "obj") { | 561 if (target == "obj") { |
196 target = "objectId"; | 562 target = "objectId"; |
197 } else if (target == "cls") { | 563 } else if (target == "cls") { |
198 target = "classId"; | 564 target = "classId"; |
199 } else if (target == "lib") { | 565 } else if (target == "lib") { |
200 target = "libraryId"; | 566 target = "libraryId"; |
201 } else { | 567 } else { |
202 huh(); | 568 huh(); |
203 return; | 569 return; |
204 } | 570 } |
205 var cmd = { "id": seqNum, | 571 var cmd = { "id": seqNum, |
206 "command": "evaluateExpr", | 572 "command": "evaluateExpr", |
207 "params": { "isolateId": currentIsolate.id, | 573 "params": { "isolateId": currentIsolate.id, |
208 target: int.parse(args[2]), | 574 target: int.parse(args[2]), |
209 "expression": expr } }; | 575 "expression": expr } }; |
210 cmdo.hide(); | |
211 sendCmd(cmd).then(showPromptAfter(handleEvalResponse)); | 576 sendCmd(cmd).then(showPromptAfter(handleEvalResponse)); |
212 } else if (command == "po" && args.length == 2) { | 577 } else if (command == "po" && args.length == 2) { |
213 var cmd = { "id": seqNum, | 578 var cmd = { "id": seqNum, |
214 "command": "getObjectProperties", | 579 "command": "getObjectProperties", |
215 "params": { "isolateId" : currentIsolate.id, | 580 "params": { "isolateId" : currentIsolate.id, |
216 "objectId": int.parse(args[1]) } }; | 581 "objectId": int.parse(args[1]) } }; |
217 cmdo.hide(); | |
218 sendCmd(cmd).then(showPromptAfter(handleGetObjPropsResponse)); | 582 sendCmd(cmd).then(showPromptAfter(handleGetObjPropsResponse)); |
219 } else if (command == "pl" && args.length >= 3) { | 583 } else if (command == "pl" && args.length >= 3) { |
220 var cmd; | 584 var cmd; |
221 if (args.length == 3) { | 585 if (args.length == 3) { |
222 cmd = { "id": seqNum, | 586 cmd = { "id": seqNum, |
223 "command": "getListElements", | 587 "command": "getListElements", |
224 "params": { "isolateId" : currentIsolate.id, | 588 "params": { "isolateId" : currentIsolate.id, |
225 "objectId": int.parse(args[1]), | 589 "objectId": int.parse(args[1]), |
226 "index": int.parse(args[2]) } }; | 590 "index": int.parse(args[2]) } }; |
227 } else { | 591 } else { |
228 cmd = { "id": seqNum, | 592 cmd = { "id": seqNum, |
229 "command": "getListElements", | 593 "command": "getListElements", |
230 "params": { "isolateId" : currentIsolate.id, | 594 "params": { "isolateId" : currentIsolate.id, |
231 "objectId": int.parse(args[1]), | 595 "objectId": int.parse(args[1]), |
232 "index": int.parse(args[2]), | 596 "index": int.parse(args[2]), |
233 "length": int.parse(args[3]) } }; | 597 "length": int.parse(args[3]) } }; |
234 } | 598 } |
235 cmdo.hide(); | |
236 sendCmd(cmd).then(showPromptAfter(handleGetListResponse)); | 599 sendCmd(cmd).then(showPromptAfter(handleGetListResponse)); |
237 } else if (command == "pc" && args.length == 2) { | 600 } else if (command == "pc" && args.length == 2) { |
238 var cmd = { "id": seqNum, | 601 var cmd = { "id": seqNum, |
239 "command": "getClassProperties", | 602 "command": "getClassProperties", |
240 "params": { "isolateId" : currentIsolate.id, | 603 "params": { "isolateId" : currentIsolate.id, |
241 "classId": int.parse(args[1]) } }; | 604 "classId": int.parse(args[1]) } }; |
242 cmdo.hide(); | |
243 sendCmd(cmd).then(showPromptAfter(handleGetClassPropsResponse)); | 605 sendCmd(cmd).then(showPromptAfter(handleGetClassPropsResponse)); |
244 } else if (command == "plib" && args.length == 2) { | 606 } else if (command == "plib" && args.length == 2) { |
245 var cmd = { "id": seqNum, | 607 var cmd = { "id": seqNum, |
246 "command": "getLibraryProperties", | 608 "command": "getLibraryProperties", |
247 "params": {"isolateId" : currentIsolate.id, | 609 "params": {"isolateId" : currentIsolate.id, |
248 "libraryId": int.parse(args[1]) } }; | 610 "libraryId": int.parse(args[1]) } }; |
249 cmdo.hide(); | |
250 sendCmd(cmd).then(showPromptAfter(handleGetLibraryPropsResponse)); | 611 sendCmd(cmd).then(showPromptAfter(handleGetLibraryPropsResponse)); |
251 } else if (command == "slib" && args.length == 3) { | 612 } else if (command == "slib" && args.length == 3) { |
252 var cmd = { "id": seqNum, | 613 var cmd = { "id": seqNum, |
253 "command": "setLibraryProperties", | 614 "command": "setLibraryProperties", |
254 "params": {"isolateId" : currentIsolate.id, | 615 "params": {"isolateId" : currentIsolate.id, |
255 "libraryId": int.parse(args[1]), | 616 "libraryId": int.parse(args[1]), |
256 "debuggingEnabled": args[2] } }; | 617 "debuggingEnabled": args[2] } }; |
257 cmdo.hide(); | |
258 sendCmd(cmd).then(showPromptAfter(handleSetLibraryPropsResponse)); | 618 sendCmd(cmd).then(showPromptAfter(handleSetLibraryPropsResponse)); |
259 } else if (command == "pg" && args.length == 2) { | 619 } else if (command == "pg" && args.length == 2) { |
260 var cmd = { "id": seqNum, | 620 var cmd = { "id": seqNum, |
261 "command": "getGlobalVariables", | 621 "command": "getGlobalVariables", |
262 "params": { "isolateId" : currentIsolate.id, | 622 "params": { "isolateId" : currentIsolate.id, |
263 "libraryId": int.parse(args[1]) } }; | 623 "libraryId": int.parse(args[1]) } }; |
264 cmdo.hide(); | |
265 sendCmd(cmd).then(showPromptAfter(handleGetGlobalVarsResponse)); | 624 sendCmd(cmd).then(showPromptAfter(handleGetGlobalVarsResponse)); |
266 } else if (command == "gs" && args.length == 3) { | 625 } else if (command == "gs" && args.length == 3) { |
267 var cmd = { "id": seqNum, | 626 var cmd = { "id": seqNum, |
268 "command": "getScriptSource", | 627 "command": "getScriptSource", |
269 "params": { "isolateId" : currentIsolate.id, | 628 "params": { "isolateId" : currentIsolate.id, |
270 "libraryId": int.parse(args[1]), | 629 "libraryId": int.parse(args[1]), |
271 "url": args[2] } }; | 630 "url": args[2] } }; |
272 cmdo.hide(); | |
273 sendCmd(cmd).then(showPromptAfter(handleGetSourceResponse)); | 631 sendCmd(cmd).then(showPromptAfter(handleGetSourceResponse)); |
274 } else if (command == "tok" && args.length == 3) { | 632 } else if (command == "tok" && args.length == 3) { |
275 var cmd = { "id": seqNum, | 633 var cmd = { "id": seqNum, |
276 "command": "getLineNumberTable", | 634 "command": "getLineNumberTable", |
277 "params": { "isolateId" : currentIsolate.id, | 635 "params": { "isolateId" : currentIsolate.id, |
278 "libraryId": int.parse(args[1]), | 636 "libraryId": int.parse(args[1]), |
279 "url": args[2] } }; | 637 "url": args[2] } }; |
280 cmdo.hide(); | |
281 sendCmd(cmd).then(showPromptAfter(handleGetLineTableResponse)); | 638 sendCmd(cmd).then(showPromptAfter(handleGetLineTableResponse)); |
282 } else if (command == "epi" && args.length == 2) { | 639 } else if (command == "epi" && args.length == 2) { |
283 var cmd = { "id": seqNum, | 640 var cmd = { "id": seqNum, |
284 "command": "setPauseOnException", | 641 "command": "setPauseOnException", |
285 "params": { "isolateId" : currentIsolate.id, | 642 "params": { "isolateId" : currentIsolate.id, |
286 "exceptions": args[1] } }; | 643 "exceptions": args[1] } }; |
287 cmdo.hide(); | |
288 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); | 644 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); |
289 } else if (command == "li") { | 645 } else if (command == "li") { |
290 var cmd = { "id": seqNum, "command": "getIsolateIds" }; | 646 var cmd = { "id": seqNum, "command": "getIsolateIds" }; |
291 cmdo.hide(); | |
292 sendCmd(cmd).then(showPromptAfter(handleGetIsolatesResponse)); | 647 sendCmd(cmd).then(showPromptAfter(handleGetIsolatesResponse)); |
293 } else if (command == "sci" && args.length == 2) { | 648 } else if (command == "sci" && args.length == 2) { |
294 var id = int.parse(args[1]); | 649 var id = int.parse(args[1]); |
295 if (targetIsolates[id] != null) { | 650 if (targetIsolates[id] != null) { |
296 currentIsolate = targetIsolates[id]; | 651 currentIsolate = targetIsolates[id]; |
297 print("Setting current target isolate to $id"); | 652 print("Setting current target isolate to $id"); |
298 } else { | 653 } else { |
299 print("$id is not a valid isolate id"); | 654 print("$id is not a valid isolate id"); |
300 } | 655 } |
| 656 cmdo.show(); |
301 } else if (command == "i" && args.length == 2) { | 657 } else if (command == "i" && args.length == 2) { |
302 var cmd = { "id": seqNum, | 658 var cmd = { "id": seqNum, |
303 "command": "interrupt", | 659 "command": "interrupt", |
304 "params": { "isolateId": int.parse(args[1]) } }; | 660 "params": { "isolateId": int.parse(args[1]) } }; |
305 cmdo.hide(); | |
306 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); | 661 sendCmd(cmd).then(showPromptAfter(handleGenericResponse)); |
307 } else if (command == "q") { | 662 } else if (command.length == 0) { |
308 quitShell(); | 663 huh(); |
309 } else if (command == "h") { | 664 cmdo.show(); |
310 printHelp(); | |
311 } else { | 665 } else { |
312 huh(); | 666 // TODO(turnidge): Use this for all commands. |
| 667 var matches = matchCommand(command, true); |
| 668 if (matches.length == 0) { |
| 669 huh(); |
| 670 cmdo.show(); |
| 671 } else if (matches.length == 1) { |
| 672 matches[0].run(args).then((_) { |
| 673 cmdo.show(); |
| 674 }); |
| 675 } else { |
| 676 var matchNames = matches.map((handler) => handler.name); |
| 677 print("Ambigous command '$command' : ${matchNames.toList()}"); |
| 678 cmdo.show(); |
| 679 } |
313 } | 680 } |
314 } | 681 } |
315 | 682 |
316 | 683 |
| 684 void processError(error, trace) { |
| 685 cmdo.hide(); |
| 686 print("\nInternal error:\n$error\n$trace"); |
| 687 cmdo.show(); |
| 688 } |
| 689 |
| 690 |
| 691 void processDone() { |
| 692 debuggerQuit(); |
| 693 } |
| 694 |
| 695 |
317 String remoteObject(value) { | 696 String remoteObject(value) { |
318 var kind = value["kind"]; | 697 var kind = value["kind"]; |
319 var text = value["text"]; | 698 var text = value["text"]; |
320 var id = value["objectId"]; | 699 var id = value["objectId"]; |
321 if (kind == "string") { | 700 if (kind == "string") { |
322 return "(string, id $id) '$text'"; | 701 return "(string, id $id) '$text'"; |
323 } else if (kind == "list") { | 702 } else if (kind == "list") { |
324 var len = value["length"]; | 703 var len = value["length"]; |
325 return "(list, id $id, len $len) $text"; | 704 return "(list, id $id, len $len) $text"; |
326 } else if (kind == "object") { | 705 } else if (kind == "object") { |
(...skipping 428 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
755 } | 1134 } |
756 | 1135 |
757 List<String> debuggerCommandCompleter(List<String> commandParts) { | 1136 List<String> debuggerCommandCompleter(List<String> commandParts) { |
758 List<String> completions = new List<String>(); | 1137 List<String> completions = new List<String>(); |
759 | 1138 |
760 // TODO(turnidge): Have a global command table and use it to for | 1139 // TODO(turnidge): Have a global command table and use it to for |
761 // help messages, command completion, and command dispatching. For now | 1140 // help messages, command completion, and command dispatching. For now |
762 // we hardcode the list here. | 1141 // we hardcode the list here. |
763 // | 1142 // |
764 // TODO(turnidge): Implement completion for arguments as well. | 1143 // TODO(turnidge): Implement completion for arguments as well. |
765 List<String> allCommands = ['q', 'bt', 'r', 's', 'so', 'si', 'sbp', 'rbp', | 1144 List<String> oldCommands = ['bt', 'r', 's', 'so', 'si', 'sbp', 'rbp', |
766 'po', 'eval', 'pl', 'pc', 'll', 'plib', 'slib', | 1145 'po', 'eval', 'pl', 'pc', 'll', 'plib', 'slib', |
767 'pg', 'ls', 'gs', 'tok', 'epi', 'li', 'i', 'h']; | 1146 'pg', 'ls', 'gs', 'tok', 'epi', 'li', 'i' ]; |
768 | 1147 |
769 // Completion of first word in the command. | 1148 // Completion of first word in the command. |
770 if (commandParts.length == 1) { | 1149 if (commandParts.length == 1) { |
771 String prefix = commandParts.last; | 1150 String prefix = commandParts.last; |
772 for (String command in allCommands) { | 1151 for (var command in oldCommands) { |
773 if (command.startsWith(prefix)) { | 1152 if (command.startsWith(prefix)) { |
774 completions.add(command); | 1153 completions.add(command); |
775 } | 1154 } |
776 } | 1155 } |
| 1156 for (var command in commandList) { |
| 1157 if (command.name.startsWith(prefix)) { |
| 1158 completions.add(command.name); |
| 1159 } |
| 1160 } |
777 } | 1161 } |
778 | 1162 |
779 return completions; | 1163 return completions; |
780 } | 1164 } |
781 | 1165 |
782 void debuggerMain() { | 1166 Future closeCommando() { |
| 1167 var subscription = cmdSubscription; |
| 1168 cmdSubscription = null; |
| 1169 cmdo = null; |
| 1170 |
| 1171 var future = subscription.cancel(); |
| 1172 if (future != null) { |
| 1173 return future; |
| 1174 } else { |
| 1175 return new Future.value(); |
| 1176 } |
| 1177 } |
| 1178 |
| 1179 |
| 1180 Future openVmSocket(int attempt) { |
| 1181 return Socket.connect("127.0.0.1", debugPort).then( |
| 1182 setupVmSocket, |
| 1183 onError: (e) { |
| 1184 // We were unable to connect to the debugger's port. Try again. |
| 1185 retryOpenVmSocket(e, attempt); |
| 1186 }); |
| 1187 } |
| 1188 |
| 1189 |
| 1190 void setupVmSocket(Socket s) { |
| 1191 vmSock = s; |
| 1192 vmSock.setOption(SocketOption.TCP_NODELAY, true); |
| 1193 var stringStream = vmSock.transform(UTF8.decoder); |
783 outstandingCommands = new Map<int, Completer>(); | 1194 outstandingCommands = new Map<int, Completer>(); |
784 Socket.connect("127.0.0.1", 5858).then((s) { | 1195 vmSubscription = stringStream.listen( |
785 vmSock = s; | 1196 (String data) { |
786 vmSock.setOption(SocketOption.TCP_NODELAY, true); | 1197 processVmData(data); |
787 var stringStream = vmSock.transform(UTF8.decoder); | 1198 }, |
788 vmSubscription = stringStream.listen( | 1199 onDone: () { |
789 (String data) { | 1200 cmdo.hide(); |
790 processVmData(data); | 1201 if (verbose) { |
791 }, | |
792 onDone: () { | |
793 print("VM debugger connection closed"); | 1202 print("VM debugger connection closed"); |
794 quitShell(); | 1203 } |
795 }, | 1204 closeVmSocket().then((_) { |
796 onError: (err) { | 1205 cmdo.show(); |
797 print("Error in debug connection: $err"); | 1206 }); |
798 // TODO(floitsch): do we want to print the stack trace? | 1207 }, |
799 quitShell(); | 1208 onError: (err) { |
800 }); | 1209 cmdo.hide(); |
801 cmdo = new Commando(stdin, stdout, processCommand, | 1210 // TODO(floitsch): do we want to print the stack trace? |
802 completer : debuggerCommandCompleter); | 1211 print("Error in debug connection: $err"); |
803 }); | 1212 |
| 1213 // TODO(turnidge): Kill the debugged process here? |
| 1214 closeVmSocket().then((_) { |
| 1215 cmdo.show(); |
| 1216 }); |
| 1217 }); |
| 1218 } |
| 1219 |
| 1220 |
| 1221 Future retryOpenVmSocket(error, int attempt) { |
| 1222 var delay; |
| 1223 if (attempt < 10) { |
| 1224 delay = new Duration(milliseconds:10); |
| 1225 } else if (attempt < 20) { |
| 1226 delay = new Duration(seconds:1); |
| 1227 } else { |
| 1228 // Too many retries. Give up. |
| 1229 // |
| 1230 // TODO(turnidge): Kill the debugged process here? |
| 1231 print('Timed out waiting for debugger to start.\nError: $e'); |
| 1232 return closeVmSocket(); |
| 1233 } |
| 1234 |
| 1235 // Wait and retry. |
| 1236 return new Future.delayed(delay, () { |
| 1237 openVmSocket(attempt + 1); |
| 1238 }); |
| 1239 } |
| 1240 |
| 1241 |
| 1242 Future closeVmSocket() { |
| 1243 if (vmSubscription == null) { |
| 1244 // Already closed, nothing to do. |
| 1245 assert(vmSock == null); |
| 1246 return new Future.value(); |
| 1247 } |
| 1248 |
| 1249 isDebugging = false; |
| 1250 var subscription = vmSubscription; |
| 1251 var sock = vmSock; |
| 1252 |
| 1253 // Wait for the socket to close and the subscription to be |
| 1254 // cancelled. Perhaps overkill, but it means we know these will be |
| 1255 // done. |
| 1256 // |
| 1257 // This is uglier than it needs to be since cancel can return null. |
| 1258 var cleanupFutures = [sock.close()]; |
| 1259 var future = subscription.cancel(); |
| 1260 if (future != null) { |
| 1261 cleanupFutures.add(future); |
| 1262 } |
| 1263 |
| 1264 vmSubscription = null; |
| 1265 vmSock = null; |
| 1266 outstandingCommands = null; |
| 1267 |
| 1268 return Future.wait(cleanupFutures); |
| 1269 } |
| 1270 |
| 1271 Future debuggerQuit() { |
| 1272 // Kill target process, if any. |
| 1273 if (targetProcess != null) { |
| 1274 if (!targetProcess.kill()) { |
| 1275 print('Unable to kill process ${targetProcess.pid}'); |
| 1276 } |
| 1277 } |
| 1278 |
| 1279 // Restore terminal settings, close connections. |
| 1280 return Future.wait([closeCommando(), closeVmSocket()]).then((_) { |
| 1281 exit(0); |
| 1282 |
| 1283 // Unreachable. |
| 1284 return new Future.value(); |
| 1285 }); |
| 1286 } |
| 1287 |
| 1288 |
| 1289 void parseArgs(List<String> args) { |
| 1290 int pos = 0; |
| 1291 settings['vm'] = Platform.executable; |
| 1292 while (pos < args.length && args[pos].startsWith('-')) { |
| 1293 pos++; |
| 1294 } |
| 1295 if (pos < args.length) { |
| 1296 settings['vmargs'] = args.getRange(0, pos).join(' '); |
| 1297 settings['script'] = args[pos]; |
| 1298 settings['args'] = args.getRange(pos + 1, args.length).join(' '); |
| 1299 } |
804 } | 1300 } |
805 | 1301 |
806 void main(List<String> args) { | 1302 void main(List<String> args) { |
807 if (args.length > 0) { | 1303 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 | 1304 |
817 // TODO(turnidge): For now we only show full lines of output | 1305 cmdo = new Commando(completer: debuggerCommandCompleter); |
818 // from the debugged process. Should show each character. | 1306 cmdSubscription = cmdo.commands.listen(processCommand, |
819 process.stdout | 1307 onError: processError, |
820 .transform(UTF8.decoder) | 1308 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 } | 1309 } |
OLD | NEW |