Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(479)

Side by Side Diff: tools/ddbg.dart

Issue 1497033003: - Remove the legacy debug protocol. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Address review comments. Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/coverage.dart ('k') | tools/ddbg/lib/commando.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 // Simple interactive debugger shell that connects to the Dart VM's debugger
6 // connection port.
7
8 import "dart:convert";
9 import "dart:io";
10 import "dart:async";
11 import "dart:math";
12
13 import "ddbg/lib/commando.dart";
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 const UnknownLocation = const {};
27
28 class TargetIsolate {
29 int id;
30 // The location of the last paused event.
31 Map pausedLocation = null;
32
33 TargetIsolate(this.id);
34 bool get isPaused => pausedLocation != null;
35 String get pausedUrl => pausedLocation != null ? pausedLocation["url"] : null;
36
37 Map<String, TargetScript> scripts = {};
38 }
39
40 Map<int, TargetIsolate> targetIsolates= new Map<int, TargetIsolate>();
41
42 Map<int, Completer> outstandingCommands;
43
44 Socket vmSock;
45 String vmData;
46 var cmdSubscription;
47 Commando cmdo;
48 var vmSubscription;
49 int seqNum = 0;
50
51 bool isDebugging = false;
52 Process targetProcess = null;
53 bool suppressNextExitCode = false;
54
55 final verbose = false;
56 final printMessages = false;
57
58 TargetIsolate currentIsolate;
59 TargetIsolate mainIsolate;
60
61 int debugPort = 5858;
62
63 String formatLocation(Map location) {
64 if (location == null) return "";
65 var fileName = location["url"].split("/").last;
66 return "file: $fileName lib: ${location['libraryId']} token: ${location['token Offset']}";
67 }
68
69
70 Future sendCmd(Map<String, dynamic> cmd) {
71 var completer = new Completer.sync();
72 int id = cmd["id"];
73 outstandingCommands[id] = completer;
74 if (verbose) {
75 print("sending: '${JSON.encode(cmd)}'");
76 }
77 vmSock.write(JSON.encode(cmd));
78 return completer.future;
79 }
80
81
82 bool checkCurrentIsolate() {
83 if (vmSock == null) {
84 print("There is no active script. Try 'help run'.");
85 return false;
86 }
87 if (currentIsolate == null) {
88 print('There is no current isolate.');
89 return false;
90 }
91 return true;
92 }
93
94
95 void setCurrentIsolate(TargetIsolate isolate) {
96 if (isolate != currentIsolate) {
97 currentIsolate = isolate;
98 if (mainIsolate == null) {
99 print("Main isolate is ${isolate.id}");
100 mainIsolate = isolate;
101 }
102 print("Current isolate is now ${isolate.id}");
103 }
104 }
105
106
107 bool checkPaused() {
108 if (!checkCurrentIsolate()) return false;
109 if (currentIsolate.isPaused) return true;
110 print("Current isolate must be paused");
111 return false;
112 }
113
114 // These settings are allowed in the 'set' and 'show' debugger commands.
115 var validSettings = ['vm', 'vmargs', 'script', 'args'];
116
117 // The current values for all settings.
118 var settings = new Map();
119
120 String _leftJustify(text, int width) {
121 StringBuffer buffer = new StringBuffer();
122 buffer.write(text);
123 while (buffer.length < width) {
124 buffer.write(' ');
125 }
126 return buffer.toString();
127 }
128
129 // TODO(turnidge): Move all commands here.
130 List<Command> commandList =
131 [ new HelpCommand(),
132 new QuitCommand(),
133 new RunCommand(),
134 new KillCommand(),
135 new ConnectCommand(),
136 new DisconnectCommand(),
137 new SetCommand(),
138 new ShowCommand() ];
139
140
141 List<Command> matchCommand(String commandName, bool exactMatchWins) {
142 List matches = [];
143 for (var command in commandList) {
144 if (command.name.startsWith(commandName)) {
145 if (exactMatchWins && command.name == commandName) {
146 // Exact match
147 return [command];
148 } else {
149 matches.add(command);
150 }
151 }
152 }
153 return matches;
154 }
155
156 abstract class Command {
157 String get name;
158 Future run(List<String> args);
159 }
160
161 class HelpCommand extends Command {
162 final name = 'help';
163 final helpShort = 'Show a list of debugger commands';
164 final helpLong ="""
165 Show a list of debugger commands or get more information about a
166 particular command.
167
168 Usage:
169 help
170 help <command>
171 """;
172
173 Future run(List<String> args) {
174 if (args.length == 1) {
175 print("Debugger commands:\n");
176 for (var command in commandList) {
177 print(' ${_leftJustify(command.name, 11)} ${command.helpShort}');
178 }
179
180 // TODO(turnidge): Convert all commands to use the Command class.
181 print("""
182 bt Show backtrace
183 r Resume execution
184 s Single step
185 so Step over
186 si Step into
187 sbp [<file>] <line> Set breakpoint
188 rbp <id> Remove breakpoint with given id
189 po <id> Print object info for given id
190 eval fr <n> <expr> Evaluate expr on stack frame index n
191 eval obj <id> <expr> Evaluate expr on object id
192 eval cls <id> <expr> Evaluate expr on class id
193 eval lib <id> <expr> Evaluate expr in toplevel of library id
194 pl <id> <idx> [<len>] Print list element/slice
195 pc <id> Print class info for given id
196 ll List loaded libraries
197 plib <id> Print library info for given library id
198 slib <id> <true|false> Set library id debuggable
199 pg <id> Print all global variables visible within given library id
200 ls <lib_id> List loaded scripts in library
201 gs <lib_id> <script_url> Get source text of script in library
202 tok <lib_id> <script_url> Get line and token table of script in library
203 epi <none|all|unhandled> Set exception pause info
204 li List ids of all isolates in the VM
205 sci <id> Set current target isolate
206 i <id> Interrupt execution of given isolate id
207 """);
208
209 print("For more information about a particular command, type:\n\n"
210 " help <command>\n");
211
212 print("Commands may be abbreviated: e.g. type 'h' for 'help.\n");
213 } else if (args.length == 2) {
214 var commandName = args[1];
215 var matches = matchCommand(commandName, true);
216 if (matches.length == 0) {
217 print("Command '$commandName' not recognized. "
218 "Try 'help' for a list of commands.");
219 } else {
220 for (var command in matches) {
221 print("---- ${command.name} ----\n${command.helpLong}");
222 }
223 }
224 } else {
225 print("Command '$command' not recognized. "
226 "Try 'help' for a list of commands.");
227 }
228
229 return new Future.value();
230 }
231 }
232
233
234 class QuitCommand extends Command {
235 final name = 'quit';
236 final helpShort = 'Quit the debugger.';
237 final helpLong ="""
238 Quit the debugger.
239
240 Usage:
241 quit
242 """;
243
244 Future run(List<String> args) {
245 if (args.length > 1) {
246 print("Unexpected arguments to $name command.");
247 return new Future.value();
248 }
249 return debuggerQuit();
250 }
251 }
252
253 class SetCommand extends Command {
254 final name = 'set';
255 final helpShort = 'Change the value of a debugger setting.';
256 final helpLong ="""
257 Change the value of a debugger setting.
258
259 Usage:
260 set <setting> <value>
261
262 Valid settings are:
263 ${validSettings.join('\n ')}.
264
265 See also 'help show'.
266 """;
267
268 Future run(List<String> args) {
269 if (args.length < 3 || !validSettings.contains(args[1])) {
270 print("Undefined $name command. Try 'help $name'.");
271 return new Future.value();
272 }
273 var option = args[1];
274 var value = args.getRange(2, args.length).join(' ');
275 settings[option] = value;
276 return new Future.value();
277 }
278 }
279
280 class ShowCommand extends Command {
281 final name = 'show';
282 final helpShort = 'Show the current value of a debugger setting.';
283 final helpLong ="""
284 Show the current value of a debugger setting.
285
286 Usage:
287 show
288 show <setting>
289
290 If no <setting> is specified, all current settings are shown.
291
292 Valid settings are:
293 ${validSettings.join('\n ')}.
294
295 See also 'help set'.
296 """;
297
298 Future run(List<String> args) {
299 if (args.length == 1) {
300 for (var option in validSettings) {
301 var value = settings[option];
302 print("$option = '$value'");
303 }
304 } else if (args.length == 2 && validSettings.contains(args[1])) {
305 var option = args[1];
306 var value = settings[option];
307 if (value == null) {
308 print('$option has not been set.');
309 } else {
310 print("$option = '$value'");
311 }
312 return new Future.value();
313 } else {
314 print("Undefined $name command. Try 'help $name'.");
315 }
316 return new Future.value();
317 }
318 }
319
320 class RunCommand extends Command {
321 final name = 'run';
322 final helpShort = "Run the currrent script.";
323 final helpLong ="""
324 Runs the current script.
325
326 Usage:
327 run
328 run <args>
329
330 The current script will be run on the current vm. The 'vm' and
331 'vmargs' settings are used to specify the current vm and vm arguments.
332 The 'script' and 'args' settings are used to specify the current
333 script and script arguments.
334
335 For more information on settings type 'help show' or 'help set'.
336
337 If <args> are provided to the run command, it is the same as typing
338 'set args <args>' followed by 'run'.
339 """;
340
341 Future run(List<String> cmdArgs) {
342 if (isDebugging) {
343 // TODO(turnidge): Implement modal y/n dialog to stop running script.
344 print("There is already a running dart process. "
345 "Try 'kill'.");
346 return new Future.value();
347 }
348 assert(targetProcess == null);
349 if (settings['script'] == null) {
350 print("There is no script specified. "
351 "Use 'set script' to set the current script.");
352 return new Future.value();
353 }
354 if (cmdArgs.length > 1) {
355 settings['args'] = cmdArgs.getRange(1, cmdArgs.length);
356 }
357
358 // Build the process arguments.
359 var processArgs = ['--debug:$debugPort'];
360 if (verbose) {
361 processArgs.add('--verbose_debug');
362 }
363 if (settings['vmargs'] != null) {
364 processArgs.addAll(settings['vmargs'].split(' '));
365 }
366 processArgs.add(settings['script']);
367 if (settings['args'] != null) {
368 processArgs.addAll(settings['args'].split(' '));
369 }
370 String vm = settings['vm'];
371
372 isDebugging = true;
373 cmdo.hide();
374 return Process.start(vm, processArgs).then((process) {
375 print("Started process ${process.pid} '$vm ${processArgs.join(' ')}'");
376 targetProcess = process;
377 process.stdin.close();
378
379 // TODO(turnidge): For now we only show full lines of output
380 // from the debugged process. Should show each character.
381 process.stdout
382 .transform(UTF8.decoder)
383 .transform(new LineSplitter())
384 .listen((String line) {
385 cmdo.hide();
386 // TODO(turnidge): Escape output in any way?
387 print(line);
388 cmdo.show();
389 });
390
391 process.stderr
392 .transform(UTF8.decoder)
393 .transform(new LineSplitter())
394 .listen((String line) {
395 cmdo.hide();
396 print(line);
397 cmdo.show();
398 });
399
400 process.exitCode.then((int exitCode) {
401 cmdo.hide();
402 if (suppressNextExitCode) {
403 suppressNextExitCode = false;
404 } else {
405 if (exitCode == 0) {
406 print('Process exited normally.');
407 } else {
408 print('Process exited with code $exitCode.');
409 }
410 }
411 targetProcess = null;
412 cmdo.show();
413 });
414
415 // Wait for the vm to open the debugging port.
416 return openVmSocket(0);
417 });
418 }
419 }
420
421 class KillCommand extends Command {
422 final name = 'kill';
423 final helpShort = 'Kill the currently executing script.';
424 final helpLong ="""
425 Kill the currently executing script.
426
427 Usage:
428 kill
429 """;
430
431 Future run(List<String> cmdArgs) {
432 if (!isDebugging) {
433 print('There is no running script.');
434 return new Future.value();
435 }
436 if (targetProcess == null) {
437 print("The active dart process was not started with 'run'. "
438 "Try 'disconnect' instead.");
439 return new Future.value();
440 }
441 assert(targetProcess != null);
442 bool result = targetProcess.kill();
443 if (result) {
444 print('Process killed.');
445 suppressNextExitCode = true;
446 } else {
447 print('Unable to kill process ${targetProcess.pid}');
448 }
449 return new Future.value();
450 }
451 }
452
453 class ConnectCommand extends Command {
454 final name = 'connect';
455 final helpShort = "Connect to a running dart script.";
456 final helpLong ="""
457 Connect to a running dart script.
458
459 Usage:
460 connect
461 connect <port>
462
463 The debugger will connect to a dart script which has already been
464 started with the --debug option. If no port is provided, the debugger
465 will attempt to connect on the default debugger port.
466 """;
467
468 Future run(List<String> cmdArgs) {
469 if (cmdArgs.length > 2) {
470 print("Too many arguments to 'connect'.");
471 }
472 if (isDebugging) {
473 // TODO(turnidge): Implement modal y/n dialog to stop running script.
474 print("There is already a running dart process. "
475 "Try 'kill'.");
476 return new Future.value();
477 }
478 assert(targetProcess == null);
479 if (cmdArgs.length == 2) {
480 debugPort = int.parse(cmdArgs[1]);
481 }
482
483 isDebugging = true;
484 cmdo.hide();
485 return openVmSocket(0);
486 }
487 }
488
489 class DisconnectCommand extends Command {
490 final name = 'disconnect';
491 final helpShort = "Disconnect from a running dart script.";
492 final helpLong ="""
493 Disconnect from a running dart script.
494
495 Usage:
496 disconnect
497
498 The debugger will disconnect from a dart script's debugging port. The
499 script must have been connected to earlier with the 'connect' command.
500 """;
501
502 Future run(List<String> cmdArgs) {
503 if (cmdArgs.length > 1) {
504 print("Too many arguments to 'disconnect'.");
505 }
506 if (!isDebugging) {
507 // TODO(turnidge): Implement modal y/n dialog to stop running script.
508 print("There is no active dart process. "
509 "Try 'connect'.");
510 return new Future.value();
511 }
512 if (targetProcess != null) {
513 print("The active dart process was started with 'run'. "
514 "Try 'kill'.");
515 }
516
517 cmdo.hide();
518 return closeVmSocket();
519 }
520 }
521
522 typedef void HandlerType(Map response);
523
524 HandlerType showPromptAfter(void handler(Map response)) {
525 return (response) {
526 handler(response);
527 cmdo.show();
528 };
529 }
530
531 void processCommand(String cmdLine) {
532 void huh() {
533 print("'$cmdLine' not understood, try 'help' for help.");
534 }
535
536 cmdo.hide();
537 seqNum++;
538 cmdLine = cmdLine.trim();
539 var args = cmdLine.split(' ');
540 if (args.length == 0) {
541 return;
542 }
543 var command = args[0];
544
545 var resume_commands =
546 { 'r':'resume', 's':'stepOver', 'si':'stepInto', 'so':'stepOut'};
547 if (resume_commands[command] != null) {
548 if (!checkPaused()) {
549 cmdo.show();
550 return;
551 }
552 var cmd = { "id": seqNum,
553 "command": resume_commands[command],
554 "params": { "isolateId" : currentIsolate.id } };
555 sendCmd(cmd).then(showPromptAfter(handleResumedResponse));
556 } else if (command == "bt") {
557 if (!checkCurrentIsolate()) {
558 cmdo.show();
559 return;
560 }
561 var cmd = { "id": seqNum,
562 "command": "getStackTrace",
563 "params": { "isolateId" : currentIsolate.id } };
564 sendCmd(cmd).then(showPromptAfter(handleStackTraceResponse));
565 } else if (command == "ll") {
566 if (!checkCurrentIsolate()) {
567 cmdo.show();
568 return;
569 }
570 var cmd = { "id": seqNum,
571 "command": "getLibraries",
572 "params": { "isolateId" : currentIsolate.id } };
573 sendCmd(cmd).then(showPromptAfter(handleGetLibraryResponse));
574 } else if (command == "sbp" && args.length >= 2) {
575 if (!checkCurrentIsolate()) {
576 cmdo.show();
577 return;
578 }
579 var url, line;
580 if (args.length == 2 && currentIsolate.pausedUrl != null) {
581 url = currentIsolate.pausedUrl;
582 line = int.parse(args[1]);
583 } else {
584 url = args[1];
585 line = int.parse(args[2]);
586 }
587 var cmd = { "id": seqNum,
588 "command": "setBreakpoint",
589 "params": { "isolateId" : currentIsolate.id,
590 "url": url,
591 "line": line }};
592 sendCmd(cmd).then(showPromptAfter(handleSetBpResponse));
593 } else if (command == "rbp" && args.length == 2) {
594 if (!checkCurrentIsolate()) {
595 cmdo.show();
596 return;
597 }
598 var cmd = { "id": seqNum,
599 "command": "removeBreakpoint",
600 "params": { "isolateId" : currentIsolate.id,
601 "breakpointId": int.parse(args[1]) } };
602 sendCmd(cmd).then(showPromptAfter(handleGenericResponse));
603 } else if (command == "ls" && args.length == 2) {
604 if (!checkCurrentIsolate()) {
605 cmdo.show();
606 return;
607 }
608 var cmd = { "id": seqNum,
609 "command": "getScriptURLs",
610 "params": { "isolateId" : currentIsolate.id,
611 "libraryId": int.parse(args[1]) } };
612 sendCmd(cmd).then(showPromptAfter(handleGetScriptsResponse));
613 } else if (command == "eval" && args.length > 3) {
614 if (!checkCurrentIsolate()) {
615 cmdo.show();
616 return;
617 }
618 var expr = args.getRange(3, args.length).join(" ");
619 var target = args[1];
620 if (target == "obj") {
621 target = "objectId";
622 } else if (target == "cls") {
623 target = "classId";
624 } else if (target == "lib") {
625 target = "libraryId";
626 } else if (target == "fr") {
627 target = "frameId";
628 } else {
629 huh();
630 return;
631 }
632 var cmd = { "id": seqNum,
633 "command": "evaluateExpr",
634 "params": { "isolateId": currentIsolate.id,
635 target: int.parse(args[2]),
636 "expression": expr } };
637 sendCmd(cmd).then(showPromptAfter(handleEvalResponse));
638 } else if (command == "po" && args.length == 2) {
639 if (!checkCurrentIsolate()) {
640 cmdo.show();
641 return;
642 }
643 var cmd = { "id": seqNum,
644 "command": "getObjectProperties",
645 "params": { "isolateId" : currentIsolate.id,
646 "objectId": int.parse(args[1]) } };
647 sendCmd(cmd).then(showPromptAfter(handleGetObjPropsResponse));
648 } else if (command == "pl" && args.length >= 3) {
649 if (!checkCurrentIsolate()) {
650 cmdo.show();
651 return;
652 }
653 var cmd;
654 if (args.length == 3) {
655 cmd = { "id": seqNum,
656 "command": "getListElements",
657 "params": { "isolateId" : currentIsolate.id,
658 "objectId": int.parse(args[1]),
659 "index": int.parse(args[2]) } };
660 } else {
661 cmd = { "id": seqNum,
662 "command": "getListElements",
663 "params": { "isolateId" : currentIsolate.id,
664 "objectId": int.parse(args[1]),
665 "index": int.parse(args[2]),
666 "length": int.parse(args[3]) } };
667 }
668 sendCmd(cmd).then(showPromptAfter(handleGetListResponse));
669 } else if (command == "pc" && args.length == 2) {
670 if (!checkCurrentIsolate()) {
671 cmdo.show();
672 return;
673 }
674 var cmd = { "id": seqNum,
675 "command": "getClassProperties",
676 "params": { "isolateId" : currentIsolate.id,
677 "classId": int.parse(args[1]) } };
678 sendCmd(cmd).then(showPromptAfter(handleGetClassPropsResponse));
679 } else if (command == "plib" && args.length == 2) {
680 if (!checkCurrentIsolate()) {
681 cmdo.show();
682 return;
683 }
684 var cmd = { "id": seqNum,
685 "command": "getLibraryProperties",
686 "params": {"isolateId" : currentIsolate.id,
687 "libraryId": int.parse(args[1]) } };
688 sendCmd(cmd).then(showPromptAfter(handleGetLibraryPropsResponse));
689 } else if (command == "slib" && args.length == 3) {
690 if (!checkCurrentIsolate()) {
691 cmdo.show();
692 return;
693 }
694 var cmd = { "id": seqNum,
695 "command": "setLibraryProperties",
696 "params": {"isolateId" : currentIsolate.id,
697 "libraryId": int.parse(args[1]),
698 "debuggingEnabled": args[2] } };
699 sendCmd(cmd).then(showPromptAfter(handleSetLibraryPropsResponse));
700 } else if (command == "pg" && args.length == 2) {
701 if (!checkCurrentIsolate()) {
702 cmdo.show();
703 return;
704 }
705 var cmd = { "id": seqNum,
706 "command": "getGlobalVariables",
707 "params": { "isolateId" : currentIsolate.id,
708 "libraryId": int.parse(args[1]) } };
709 sendCmd(cmd).then(showPromptAfter(handleGetGlobalVarsResponse));
710 } else if (command == "gs" && args.length == 3) {
711 if (!checkCurrentIsolate()) {
712 cmdo.show();
713 return;
714 }
715 var cmd = { "id": seqNum,
716 "command": "getScriptSource",
717 "params": { "isolateId" : currentIsolate.id,
718 "libraryId": int.parse(args[1]),
719 "url": args[2] } };
720 sendCmd(cmd).then(showPromptAfter(handleGetSourceResponse));
721 } else if (command == "tok" && args.length == 3) {
722 if (!checkCurrentIsolate()) {
723 cmdo.show();
724 return;
725 }
726 var cmd = { "id": seqNum,
727 "command": "getLineNumberTable",
728 "params": { "isolateId" : currentIsolate.id,
729 "libraryId": int.parse(args[1]),
730 "url": args[2] } };
731 sendCmd(cmd).then(showPromptAfter(handleGetLineTableResponse));
732 } else if (command == "epi" && args.length == 2) {
733 if (!checkCurrentIsolate()) {
734 cmdo.show();
735 return;
736 }
737 var cmd = { "id": seqNum,
738 "command": "setPauseOnException",
739 "params": { "isolateId" : currentIsolate.id,
740 "exceptions": args[1] } };
741 sendCmd(cmd).then(showPromptAfter(handleGenericResponse));
742 } else if (command == "li") {
743 if (!checkCurrentIsolate()) {
744 cmdo.show();
745 return;
746 }
747 var cmd = { "id": seqNum, "command": "getIsolateIds" };
748 sendCmd(cmd).then(showPromptAfter(handleGetIsolatesResponse));
749 } else if (command == "sci" && args.length == 2) {
750 var id = int.parse(args[1]);
751 if (targetIsolates[id] != null) {
752 setCurrentIsolate(targetIsolates[id]);
753 } else {
754 print("$id is not a valid isolate id");
755 }
756 cmdo.show();
757 } else if (command == "i" && args.length == 2) {
758 var cmd = { "id": seqNum,
759 "command": "interrupt",
760 "params": { "isolateId": int.parse(args[1]) } };
761 sendCmd(cmd).then(showPromptAfter(handleGenericResponse));
762 } else if (command.length == 0) {
763 huh();
764 cmdo.show();
765 } else {
766 // TODO(turnidge): Use this for all commands.
767 var matches = matchCommand(command, true);
768 if (matches.length == 0) {
769 huh();
770 cmdo.show();
771 } else if (matches.length == 1) {
772 matches[0].run(args).then((_) {
773 cmdo.show();
774 });
775 } else {
776 var matchNames = matches.map((handler) => handler.name);
777 print("Ambiguous command '$command' : ${matchNames.toList()}");
778 cmdo.show();
779 }
780 }
781 }
782
783
784 void processError(error, trace) {
785 cmdo.hide();
786 print("\nInternal error:\n$error\n$trace");
787 cmdo.show();
788 }
789
790
791 void processDone() {
792 debuggerQuit();
793 }
794
795
796 String remoteObject(value) {
797 var kind = value["kind"];
798 var text = value["text"];
799 var id = value["objectId"];
800 if (kind == "string") {
801 return "(string, id $id) '$text'";
802 } else if (kind == "list") {
803 var len = value["length"];
804 return "(list, id $id, len $len) $text";
805 } else if (kind == "object") {
806 return "(obj, id $id) $text";
807 } else if (kind == "function") {
808 var location = formatLocation(value['location']);
809 var name = value['name'];
810 var signature = value['signature'];
811 return "(closure ${name}${signature} $location)";
812 } else {
813 return "$text";
814 }
815 }
816
817
818 printNamedObject(obj) {
819 var name = obj["name"];
820 var value = obj["value"];
821 print(" $name = ${remoteObject(value)}");
822 }
823
824
825 handleGetObjPropsResponse(Map response) {
826 Map props = response["result"];
827 int class_id = props["classId"];
828 if (class_id == -1) {
829 print(" null");
830 return;
831 }
832 List fields = props["fields"];
833 print(" class id: $class_id");
834 for (int i = 0; i < fields.length; i++) {
835 printNamedObject(fields[i]);
836 }
837 }
838
839 handleGetListResponse(Map response) {
840 Map result = response["result"];
841 if (result["elements"] != null) {
842 // List slice.
843 var index = result["index"];
844 var length = result["length"];
845 List elements = result["elements"];
846 assert(length == elements.length);
847 for (int i = 0; i < length; i++) {
848 var kind = elements[i]["kind"];
849 var text = elements[i]["text"];
850 print(" ${index + i}: ($kind) $text");
851 }
852 } else {
853 // One element, a remote object.
854 print(result);
855 print(" ${remoteObject(result)}");
856 }
857 }
858
859
860 handleGetClassPropsResponse(Map response) {
861 Map props = response["result"];
862 assert(props["name"] != null);
863 int libId = props["libraryId"];
864 assert(libId != null);
865 print(" class ${props["name"]} (library id: $libId)");
866 List fields = props["fields"];
867 if (fields.length > 0) {
868 print(" static fields:");
869 for (int i = 0; i < fields.length; i++) {
870 printNamedObject(fields[i]);
871 }
872 }
873 }
874
875
876 handleGetLibraryPropsResponse(Map response) {
877 Map props = response["result"];
878 assert(props["url"] != null);
879 print(" library url: ${props["url"]}");
880 assert(props["debuggingEnabled"] != null);
881 print(" debugging enabled: ${props["debuggingEnabled"]}");
882 List imports = props["imports"];
883 assert(imports != null);
884 if (imports.length > 0) {
885 print(" imports:");
886 for (int i = 0; i < imports.length; i++) {
887 print(" id ${imports[i]["libraryId"]} prefix ${imports[i]["prefix"]}");
888 }
889 }
890 List globals = props["globals"];
891 assert(globals != null);
892 if (globals.length > 0) {
893 print(" global variables:");
894 for (int i = 0; i < globals.length; i++) {
895 printNamedObject(globals[i]);
896 }
897 }
898 }
899
900
901 handleSetLibraryPropsResponse(Map response) {
902 Map props = response["result"];
903 assert(props["debuggingEnabled"] != null);
904 print(" debugging enabled: ${props["debuggingEnabled"]}");
905 }
906
907
908 handleGetGlobalVarsResponse(Map response) {
909 List globals = response["result"]["globals"];
910 for (int i = 0; i < globals.length; i++) {
911 printNamedObject(globals[i]);
912 }
913 }
914
915
916 handleGetSourceResponse(Map response) {
917 Map result = response["result"];
918 String source = result["text"];
919 print("Source text:\n$source\n--------");
920 }
921
922
923 handleGetLineTableResponse(Map response) {
924 Map result = response["result"];
925 var info = result["lines"];
926 print("Line info table:\n$info");
927 }
928
929
930 void handleGetIsolatesResponse(Map response) {
931 Map result = response["result"];
932 List ids = result["isolateIds"];
933 assert(ids != null);
934 print("List of isolates:");
935 for (int id in ids) {
936 TargetIsolate isolate = targetIsolates[id];
937 var state = (isolate != null) ? "running" : "<unknown isolate>";
938 if (isolate != null && isolate.isPaused) {
939 var loc = formatLocation(isolate.pausedLocation);
940 state = "paused at $loc";
941 }
942 var marker = " ";
943 if (currentIsolate != null && id == currentIsolate.id) {
944 marker = "*";
945 }
946 print("$marker $id $state");
947 }
948 }
949
950
951 void handleGetLibraryResponse(Map response) {
952 Map result = response["result"];
953 List libs = result["libraries"];
954 print("Loaded libraries:");
955 print(libs);
956 for (int i = 0; i < libs.length; i++) {
957 print(" ${libs[i]["id"]} ${libs[i]["url"]}");
958 }
959 }
960
961
962 void handleGetScriptsResponse(Map response) {
963 Map result = response["result"];
964 List urls = result["urls"];
965 print("Loaded scripts:");
966 for (int i = 0; i < urls.length; i++) {
967 print(" $i ${urls[i]}");
968 }
969 }
970
971
972 void handleEvalResponse(Map response) {
973 Map result = response["result"];
974 print(remoteObject(result));
975 }
976
977
978 void handleSetBpResponse(Map response) {
979 Map result = response["result"];
980 var id = result["breakpointId"];
981 assert(id != null);
982 print("Set BP $id");
983 }
984
985
986 void handleGenericResponse(Map response) {
987 if (response["error"] != null) {
988 print("Error: ${response["error"]}");
989 }
990 }
991
992 void handleResumedResponse(Map response) {
993 if (response["error"] != null) {
994 print("Error: ${response["error"]}");
995 return;
996 }
997 assert(currentIsolate != null);
998 currentIsolate.pausedLocation = null;
999 }
1000
1001
1002 void handleStackTraceResponse(Map response) {
1003 Map result = response["result"];
1004 List callFrames = result["callFrames"];
1005 assert(callFrames != null);
1006 printStackTrace(callFrames);
1007 }
1008
1009
1010 void printStackFrame(frame_num, Map frame) {
1011 var fname = frame["functionName"];
1012 var loc = formatLocation(frame["location"]);
1013 print("#${_leftJustify(frame_num,2)} $fname at $loc");
1014 List locals = frame["locals"];
1015 for (int i = 0; i < locals.length; i++) {
1016 printNamedObject(locals[i]);
1017 }
1018 }
1019
1020
1021 void printStackTrace(List frames) {
1022 for (int i = 0; i < frames.length; i++) {
1023 printStackFrame(i, frames[i]);
1024 }
1025 }
1026
1027
1028 Map<int, int> parseLineNumberTable(List<List<int>> table) {
1029 Map tokenToLine = {};
1030 for (var line in table) {
1031 // Each entry begins with a line number...
1032 var lineNumber = line[0];
1033 for (var pos = 1; pos < line.length; pos += 2) {
1034 // ...and is followed by (token offset, col number) pairs.
1035 // We ignore the column numbers.
1036 var tokenOffset = line[pos];
1037 tokenToLine[tokenOffset] = lineNumber;
1038 }
1039 }
1040 return tokenToLine;
1041 }
1042
1043
1044 Future<TargetScript> getTargetScript(Map location) {
1045 var isolate = targetIsolates[currentIsolate.id];
1046 var url = location['url'];
1047 var script = isolate.scripts[url];
1048 if (script != null) {
1049 return new Future.value(script);
1050 }
1051 script = new TargetScript();
1052
1053 // Ask the vm for the source and line number table.
1054 var sourceCmd = {
1055 "id": seqNum++,
1056 "command": "getScriptSource",
1057 "params": { "isolateId": currentIsolate.id,
1058 "libraryId": location['libraryId'],
1059 "url": url } };
1060
1061 var lineNumberCmd = {
1062 "id": seqNum++,
1063 "command": "getLineNumberTable",
1064 "params": { "isolateId": currentIsolate.id,
1065 "libraryId": location['libraryId'],
1066 "url": url } };
1067
1068 // Send the source command
1069 var sourceResponse = sendCmd(sourceCmd).then((response) {
1070 Map result = response["result"];
1071 script.source = result['text'];
1072 // Line numbers are 1-based so add a dummy for line 0.
1073 script.lineToSource = [''];
1074 script.lineToSource.addAll(script.source.split('\n'));
1075 });
1076
1077 // Send the line numbers command
1078 var lineNumberResponse = sendCmd(lineNumberCmd).then((response) {
1079 Map result = response["result"];
1080 script.tokenToLine = parseLineNumberTable(result['lines']);
1081 });
1082
1083 return Future.wait([sourceResponse, lineNumberResponse]).then((_) {
1084 // When both commands complete, cache the result.
1085 isolate.scripts[url] = script;
1086 return script;
1087 });
1088 }
1089
1090
1091 Future printLocation(String label, Map location) {
1092 // Figure out the line number.
1093 return getTargetScript(location).then((script) {
1094 var lineNumber = script.tokenToLine[location['tokenOffset']];
1095 var text = script.lineToSource[lineNumber];
1096 if (label != null) {
1097 var fileName = location['url'].split("/").last;
1098 print("$label \n"
1099 " at $fileName:$lineNumber");
1100 }
1101 print("${_leftJustify(lineNumber, 8)}$text");
1102 });
1103 }
1104
1105
1106 Future handlePausedEvent(msg) {
1107 assert(msg["params"] != null);
1108 var reason = msg["params"]["reason"];
1109 int isolateId = msg["params"]["isolateId"];
1110 assert(isolateId != null);
1111 var isolate = targetIsolates[isolateId];
1112 assert(isolate != null);
1113 assert(!isolate.isPaused);
1114 var location = msg["params"]["location"];;
1115 setCurrentIsolate(isolate);
1116 isolate.pausedLocation = (location == null) ? UnknownLocation : location;
1117 if (reason == "breakpoint") {
1118 assert(location != null);
1119 var bpId = (msg["params"]["breakpointId"]);
1120 var label = (bpId != null) ? "Breakpoint $bpId" : null;
1121 return printLocation(label, location);
1122 } else if (reason == "interrupted") {
1123 assert(location != null);
1124 return printLocation("Interrupted", location);
1125 } else {
1126 assert(reason == "exception");
1127 var excObj = msg["params"]["exception"];
1128 print("Isolate $isolateId paused on exception");
1129 print(remoteObject(excObj));
1130 return new Future.value();
1131 }
1132 }
1133
1134 void handleIsolateEvent(msg) {
1135 Map params = msg["params"];
1136 assert(params != null);
1137 var isolateId = params["id"];
1138 var reason = params["reason"];
1139 if (reason == "created") {
1140 print("Isolate $isolateId has been created.");
1141 assert(targetIsolates[isolateId] == null);
1142 targetIsolates[isolateId] = new TargetIsolate(isolateId);
1143 } else {
1144 assert(reason == "shutdown");
1145 var isolate = targetIsolates.remove(isolateId);
1146 assert(isolate != null);
1147 if (isolate == mainIsolate) {
1148 mainIsolate = null;
1149 print("Main isolate ${isolate.id} has terminated.");
1150 } else {
1151 print("Isolate ${isolate.id} has terminated.");
1152 }
1153 if (isolate == currentIsolate) {
1154 currentIsolate = mainIsolate;
1155 if (currentIsolate == null && !targetIsolates.isEmpty) {
1156 currentIsolate = targetIsolates.values.first;
1157 }
1158 if (currentIsolate != null) {
1159 print("Setting current isolate to ${currentIsolate.id}.");
1160 } else {
1161 print("All isolates have terminated.");
1162 }
1163 }
1164 }
1165 }
1166
1167 void processVmMessage(String jsonString) {
1168 var msg = JSON.decode(jsonString);
1169 if (msg == null) {
1170 return;
1171 }
1172 var event = msg["event"];
1173 if (event == "isolate") {
1174 cmdo.hide();
1175 handleIsolateEvent(msg);
1176 cmdo.show();
1177 return;
1178 }
1179 if (event == "paused") {
1180 cmdo.hide();
1181 handlePausedEvent(msg).then((_) {
1182 cmdo.show();
1183 });
1184 return;
1185 }
1186 if (event == "breakpointResolved") {
1187 Map params = msg["params"];
1188 assert(params != null);
1189 var isolateId = params["isolateId"];
1190 var location = formatLocation(params["location"]);
1191 cmdo.hide();
1192 print("Breakpoint ${params["breakpointId"]} resolved in isolate $isolateId"
1193 " at $location.");
1194 cmdo.show();
1195 return;
1196 }
1197 if (msg["id"] != null) {
1198 var id = msg["id"];
1199 if (outstandingCommands.containsKey(id)) {
1200 var completer = outstandingCommands.remove(id);
1201 if (msg["error"] != null) {
1202 print("VM says: ${msg["error"]}");
1203 // TODO(turnidge): Rework how hide/show happens. For now we
1204 // show here explicitly.
1205 cmdo.show();
1206 } else {
1207 completer.complete(msg);
1208 }
1209 }
1210 }
1211 }
1212
1213 bool haveGarbageVmData() {
1214 if (vmData == null || vmData.length == 0) return false;
1215 var i = 0, char = " ";
1216 while (i < vmData.length) {
1217 char = vmData[i];
1218 if (char != " " && char != "\n" && char != "\r" && char != "\t") break;
1219 i++;
1220 }
1221 if (i >= vmData.length) {
1222 return false;
1223 } else {
1224 return char != "{";
1225 }
1226 }
1227
1228
1229 void processVmData(String data) {
1230 if (vmData == null || vmData.length == 0) {
1231 vmData = data;
1232 } else {
1233 vmData = vmData + data;
1234 }
1235 if (haveGarbageVmData()) {
1236 print("Error: have garbage data from VM: '$vmData'");
1237 return;
1238 }
1239 int msg_len = jsonObjectLength(vmData);
1240 if (printMessages && msg_len == 0) {
1241 print("have partial or illegal json message"
1242 " of ${vmData.length} chars:\n'$vmData'");
1243 return;
1244 }
1245 while (msg_len > 0 && msg_len <= vmData.length) {
1246 if (msg_len == vmData.length) {
1247 if (printMessages) { print("have one full message:\n$vmData"); }
1248 processVmMessage(vmData);
1249 vmData = null;
1250 return;
1251 }
1252 if (printMessages) { print("at least one message: '$vmData'"); }
1253 var msg = vmData.substring(0, msg_len);
1254 if (printMessages) { print("first message: $msg"); }
1255 vmData = vmData.substring(msg_len);
1256 if (haveGarbageVmData()) {
1257 print("Error: garbage data after previous message: '$vmData'");
1258 print("Previous message was: '$msg'");
1259 return;
1260 }
1261 processVmMessage(msg);
1262 msg_len = jsonObjectLength(vmData);
1263 }
1264 if (printMessages) { print("leftover vm data '$vmData'"); }
1265 }
1266
1267 /**
1268 * Skip past a JSON object value.
1269 * The object value must start with '{' and continues to the
1270 * matching '}'. No attempt is made to otherwise validate the contents
1271 * as JSON. If it is invalid, a later [parseJson] will fail.
1272 */
1273 int jsonObjectLength(String string) {
1274 int skipWhitespace(int index) {
1275 while (index < string.length) {
1276 String char = string[index];
1277 if (char != " " && char != "\n" && char != "\r" && char != "\t") break;
1278 index++;
1279 }
1280 return index;
1281 }
1282 int skipString(int index) {
1283 assert(string[index - 1] == '"');
1284 while (index < string.length) {
1285 String char = string[index];
1286 if (char == '"') return index + 1;
1287 if (char == r'\') index++;
1288 if (index == string.length) return index;
1289 index++;
1290 }
1291 return index;
1292 }
1293 int index = 0;
1294 index = skipWhitespace(index);
1295 // Bail out if the first non-whitespace character isn't '{'.
1296 if (index == string.length || string[index] != '{') return 0;
1297 int nesting = 0;
1298 while (index < string.length) {
1299 String char = string[index++];
1300 if (char == '{') {
1301 nesting++;
1302 } else if (char == '}') {
1303 nesting--;
1304 if (nesting == 0) return index;
1305 } else if (char == '"') {
1306 // Strings can contain braces. Skip their content.
1307 index = skipString(index);
1308 }
1309 }
1310 return 0;
1311 }
1312
1313 List<String> debuggerCommandCompleter(List<String> commandParts) {
1314 List<String> completions = new List<String>();
1315
1316 // TODO(turnidge): Have a global command table and use it to for
1317 // help messages, command completion, and command dispatching. For now
1318 // we hardcode the list here.
1319 //
1320 // TODO(turnidge): Implement completion for arguments as well.
1321 List<String> oldCommands = ['bt', 'r', 's', 'so', 'si', 'sbp', 'rbp',
1322 'po', 'eval', 'pl', 'pc', 'll', 'plib', 'slib',
1323 'pg', 'ls', 'gs', 'tok', 'epi', 'li', 'i' ];
1324
1325 // Completion of first word in the command.
1326 if (commandParts.length == 1) {
1327 String prefix = commandParts.last;
1328 for (var command in oldCommands) {
1329 if (command.startsWith(prefix)) {
1330 completions.add(command);
1331 }
1332 }
1333 for (var command in commandList) {
1334 if (command.name.startsWith(prefix)) {
1335 completions.add(command.name);
1336 }
1337 }
1338 }
1339
1340 return completions;
1341 }
1342
1343 Future closeCommando() {
1344 var subscription = cmdSubscription;
1345 cmdSubscription = null;
1346 cmdo = null;
1347
1348 var future = subscription.cancel();
1349 if (future != null) {
1350 return future;
1351 } else {
1352 return new Future.value();
1353 }
1354 }
1355
1356
1357 Future openVmSocket(int attempt) {
1358 return Socket.connect("127.0.0.1", debugPort).then(
1359 setupVmSocket,
1360 onError: (e) {
1361 // We were unable to connect to the debugger's port. Try again.
1362 retryOpenVmSocket(e, attempt);
1363 });
1364 }
1365
1366
1367 void setupVmSocket(Socket s) {
1368 vmSock = s;
1369 vmSock.setOption(SocketOption.TCP_NODELAY, true);
1370 var stringStream = vmSock.transform(UTF8.decoder);
1371 outstandingCommands = new Map<int, Completer>();
1372 vmSubscription = stringStream.listen(
1373 (String data) {
1374 processVmData(data);
1375 },
1376 onDone: () {
1377 cmdo.hide();
1378 if (verbose) {
1379 print("VM debugger connection closed");
1380 }
1381 closeVmSocket().then((_) {
1382 cmdo.show();
1383 });
1384 },
1385 onError: (err) {
1386 cmdo.hide();
1387 // TODO(floitsch): do we want to print the stack trace?
1388 print("Error in debug connection: $err");
1389
1390 // TODO(turnidge): Kill the debugged process here?
1391 closeVmSocket().then((_) {
1392 cmdo.show();
1393 });
1394 });
1395 }
1396
1397
1398 Future retryOpenVmSocket(error, int attempt) {
1399 var delay;
1400 if (attempt < 10) {
1401 delay = new Duration(milliseconds:10);
1402 } else if (attempt < 20) {
1403 delay = new Duration(seconds:1);
1404 } else {
1405 // Too many retries. Give up.
1406 //
1407 // TODO(turnidge): Kill the debugged process here?
1408 print('Timed out waiting for debugger to start.\nError: $e');
1409 return closeVmSocket();
1410 }
1411 // Wait and retry.
1412 return new Future.delayed(delay, () {
1413 openVmSocket(attempt + 1);
1414 });
1415 }
1416
1417
1418 Future closeVmSocket() {
1419 if (vmSubscription == null) {
1420 // Already closed, nothing to do.
1421 assert(vmSock == null);
1422 return new Future.value();
1423 }
1424
1425 isDebugging = false;
1426 var subscription = vmSubscription;
1427 var sock = vmSock;
1428
1429 // Wait for the socket to close and the subscription to be
1430 // cancelled. Perhaps overkill, but it means we know these will be
1431 // done.
1432 //
1433 // This is uglier than it needs to be since cancel can return null.
1434 var cleanupFutures = [sock.close()];
1435 var future = subscription.cancel();
1436 if (future != null) {
1437 cleanupFutures.add(future);
1438 }
1439
1440 vmSubscription = null;
1441 vmSock = null;
1442 outstandingCommands = null;
1443 return Future.wait(cleanupFutures);
1444 }
1445
1446 void debuggerError(self, parent, zone, error, StackTrace trace) {
1447 print('\n--------\nExiting due to unexpected error:\n'
1448 ' $error\n$trace\n');
1449 debuggerQuit();
1450 }
1451
1452 Future debuggerQuit() {
1453 // Kill target process, if any.
1454 if (targetProcess != null) {
1455 if (!targetProcess.kill()) {
1456 print('Unable to kill process ${targetProcess.pid}');
1457 }
1458 }
1459
1460 // Restore terminal settings, close connections.
1461 return Future.wait([closeCommando(), closeVmSocket()]).then((_) {
1462 exit(0);
1463
1464 // Unreachable.
1465 return new Future.value();
1466 });
1467 }
1468
1469
1470 void parseArgs(List<String> args) {
1471 int pos = 0;
1472 settings['vm'] = Platform.executable;
1473 while (pos < args.length && args[pos].startsWith('-')) {
1474 pos++;
1475 }
1476 if (pos < args.length) {
1477 settings['vmargs'] = args.getRange(0, pos).join(' ');
1478 settings['script'] = args[pos];
1479 settings['args'] = args.getRange(pos + 1, args.length).join(' ');
1480 }
1481 }
1482
1483 void main(List<String> args) {
1484 // Setup a zone which will exit the debugger cleanly on any uncaught
1485 // exception.
1486 var zone = Zone.ROOT.fork(specification:new ZoneSpecification(
1487 handleUncaughtError: debuggerError));
1488
1489 zone.run(() {
1490 parseArgs(args);
1491 cmdo = new Commando(completer: debuggerCommandCompleter);
1492 cmdSubscription = cmdo.commands.listen(processCommand,
1493 onError: processError,
1494 onDone: processDone);
1495 });
1496 }
OLDNEW
« no previous file with comments | « tools/coverage.dart ('k') | tools/ddbg/lib/commando.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698