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 |