OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 // Library used by debugger wire protocol tests (standalone VM debugging). | 5 // Library used by debugger wire protocol tests (standalone VM debugging). |
6 | 6 |
7 library DartDebugger; | 7 library DartDebugger; |
8 | 8 |
9 import "dart:io"; | 9 import "dart:io"; |
10 import "dart:math"; | 10 import "dart:math"; |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
147 isMatch = false; | 147 isMatch = false; |
148 } | 148 } |
149 } else { | 149 } else { |
150 isMatch = false; | 150 isMatch = false; |
151 } | 151 } |
152 }); | 152 }); |
153 return isMatch; | 153 return isMatch; |
154 } | 154 } |
155 | 155 |
156 | 156 |
157 class BreakpointEvent { | |
158 String functionName; | |
159 var template = { "event": "paused", "params": { "reason": "breakpoint" }}; | |
160 | |
161 BreakpointEvent({String function: null}) { | |
162 functionName = function; | |
163 } | |
164 | |
165 void match(Debugger debugger) { | |
166 var msg = debugger.currentMessage; | |
167 if (!matchMaps(template, msg)) { | |
168 debugger.error("message does not match $template"); | |
169 } | |
170 var name = getJsonValue(msg, "params:callFrames[0]:functionName"); | |
171 if (name == "main") { | |
172 // Extract script url of debugged script. | |
173 var scriptUrl = getJsonValue(msg, "params:callFrames[0]:location:url"); | |
174 assert(scriptUrl != null); | |
175 debugger.scriptUrl = scriptUrl; | |
176 } | |
177 if (functionName != null) { | |
178 var name = getJsonValue(msg, "params:callFrames[0]:functionName"); | |
179 if (functionName != name) { | |
180 debugger.error("expected function name $functionName but got $name"); | |
181 } | |
182 } | |
183 } | |
184 } | |
185 | |
186 Breakpoint({String function}) { | |
187 return new BreakpointEvent(function: function); | |
188 } | |
189 | |
190 class Matcher { | 157 class Matcher { |
191 void match(Debugger debugger); | 158 void match(Debugger debugger); |
192 } | 159 } |
193 | 160 |
194 class FrameMatcher extends Matcher { | 161 |
| 162 class Command { |
| 163 var template; |
| 164 |
| 165 void send(Debugger debugger) { |
| 166 debugger.sendMessage(template); |
| 167 } |
| 168 |
| 169 void matchResponse(Debugger debugger) { |
| 170 Map response = debugger.currentMessage; |
| 171 var id = template["id"]; |
| 172 assert(id != null && id >= 0); |
| 173 if (response["id"] != id) { |
| 174 debugger.error("Expected messaged id $id but got ${response["id"]}."); |
| 175 } |
| 176 } |
| 177 } |
| 178 |
| 179 |
| 180 class FrameMatcher extends Command { |
195 int frameIndex; | 181 int frameIndex; |
196 List<String> functionNames; | 182 List<String> functionNames; |
197 | 183 |
198 FrameMatcher(this.frameIndex, this.functionNames); | 184 FrameMatcher(this.frameIndex, this.functionNames) { |
| 185 template = {"id": 0, "command": "getStackTrace", "params": {"isolateId": 0}}
; |
| 186 } |
199 | 187 |
200 void match(Debugger debugger) { | 188 void matchResponse(Debugger debugger) { |
| 189 super.matchResponse(debugger); |
201 var msg = debugger.currentMessage; | 190 var msg = debugger.currentMessage; |
202 List frames = getJsonValue(msg, "params:callFrames"); | 191 List frames = getJsonValue(msg, "result:callFrames"); |
203 assert(frames != null); | 192 assert(frames != null); |
| 193 if (debugger.scriptUrl == null) { |
| 194 var name = frames[0]["functionName"]; |
| 195 if (name == "main") { |
| 196 // Extract script url of debugged script. |
| 197 debugger.scriptUrl = frames[0]["location"]["url"]; |
| 198 assert(debugger.scriptUrl != null); |
| 199 } |
| 200 } |
204 if (frames.length < functionNames.length) { | 201 if (frames.length < functionNames.length) { |
205 debugger.error("stack trace not long enough " | 202 debugger.error("stack trace not long enough " |
206 "to match ${functionNames.length} frames"); | 203 "to match ${functionNames.length} frames"); |
207 return; | 204 return; |
208 } | 205 } |
209 for (int i = 0; i < functionNames.length; i++) { | 206 for (int i = 0; i < functionNames.length; i++) { |
210 var idx = i + frameIndex; | 207 var idx = i + frameIndex; |
211 var property = "params:callFrames[$idx]:functionName"; | 208 var name = frames[idx]["functionName"]; |
212 var name = getJsonValue(msg, property); | 209 assert(name != null); |
213 if (name == null) { | |
214 debugger.error("property '$property' not found"); | |
215 return; | |
216 } | |
217 if (name != functionNames[i]) { | 210 if (name != functionNames[i]) { |
218 debugger.error("call frame $idx: " | 211 debugger.error("call frame $idx: " |
219 "expected function name '${functionNames[i]}' but found '$name'"); | 212 "expected function name '${functionNames[i]}' but found '$name'"); |
220 return; | 213 return; |
221 } | 214 } |
222 } | 215 } |
223 } | 216 } |
224 } | 217 } |
225 | 218 |
226 | 219 |
227 MatchFrame(int frameIndex, String functionName) { | 220 MatchFrame(int frameIndex, String functionName) { |
228 return new FrameMatcher(frameIndex, [ functionName ]); | 221 return new FrameMatcher(frameIndex, [ functionName ]); |
229 } | 222 } |
230 | 223 |
231 MatchFrames(List<String> functionNames) { | 224 MatchFrames(List<String> functionNames) { |
232 return new FrameMatcher(0, functionNames); | 225 return new FrameMatcher(0, functionNames); |
233 } | 226 } |
234 | 227 |
235 | 228 |
236 class Command { | 229 class RunCommand extends Command { |
237 var template; | 230 RunCommand.resume() { |
238 Command(); | |
239 Command.resume() { | |
240 template = {"id": 0, "command": "resume", "params": {"isolateId": 0}}; | 231 template = {"id": 0, "command": "resume", "params": {"isolateId": 0}}; |
241 } | 232 } |
242 Command.step() { | 233 RunCommand.step() { |
243 template = {"id": 0, "command": "stepOver", "params": {"isolateId": 0}}; | 234 template = {"id": 0, "command": "stepOver", "params": {"isolateId": 0}}; |
244 } | 235 } |
245 Map makeMsg(int cmdId, int isolateId) { | |
246 template["id"] = cmdId; | |
247 if ((template["params"] != null) | |
248 && (template["params"]["isolateId"] != null)) { | |
249 template["params"]["isolateId"] = isolateId; | |
250 } | |
251 return template; | |
252 } | |
253 | |
254 void send(Debugger debugger) { | 236 void send(Debugger debugger) { |
255 template["id"] = debugger.seqNr; | |
256 template["params"]["isolateId"] = debugger.isolateId; | |
257 debugger.sendMessage(template); | 237 debugger.sendMessage(template); |
258 } | 238 debugger.isPaused = false; |
259 | |
260 void matchResponse(Debugger debugger) { | |
261 Map response = debugger.currentMessage; | |
262 var id = template["id"]; | |
263 assert(id != null && id >= 0); | |
264 if (response["id"] != id) { | |
265 debugger.error("Expected messaged id $id but got ${response["id"]}."); | |
266 } | |
267 } | 239 } |
268 } | 240 } |
269 | 241 |
270 Resume() => new Command.resume(); | 242 |
271 Step() => new Command.step(); | 243 Resume() => new RunCommand.resume(); |
| 244 Step() => new RunCommand.step(); |
| 245 |
272 | 246 |
273 class SetBreakpointCommand extends Command { | 247 class SetBreakpointCommand extends Command { |
274 int line; | 248 int line; |
275 SetBreakpointCommand(int this.line) { | 249 SetBreakpointCommand(int this.line) { |
276 template = {"id": 0, | 250 template = {"id": 0, |
277 "command": "setBreakpoint", | 251 "command": "setBreakpoint", |
278 "params": { "isolateId": 0, | 252 "params": { "isolateId": 0, |
279 "url": null, | 253 "url": null, |
280 "line": null }}; | 254 "line": null }}; |
281 } | 255 } |
| 256 |
282 void send(Debugger debugger) { | 257 void send(Debugger debugger) { |
283 assert(debugger.scriptUrl != null); | 258 assert(debugger.scriptUrl != null); |
284 template["params"]["url"] = debugger.scriptUrl; | 259 template["params"]["url"] = debugger.scriptUrl; |
285 template["params"]["line"] = line; | 260 template["params"]["line"] = line; |
286 super.send(debugger); | 261 debugger.sendMessage(template); |
287 } | 262 } |
288 } | 263 } |
289 | 264 |
290 SetBreakpoint(int line) => new SetBreakpointCommand(line); | 265 SetBreakpoint(int line) => new SetBreakpointCommand(line); |
291 | 266 |
292 | 267 |
293 // A debug script is a list of Event, Matcher and Command objects. | 268 // A debug script is a list of Command objects. |
294 class DebugScript { | 269 class DebugScript { |
295 List entries; | 270 List entries; |
296 int currentIndex; | 271 DebugScript(List scriptEntries) { |
297 DebugScript(List this.entries) : currentIndex = 0; | 272 entries = new List.from(scriptEntries.reversed); |
298 get currentEntry { | 273 entries.add(MatchFrame(0, "main")); |
299 if (currentIndex < entries.length) return entries[currentIndex]; | |
300 return null; | |
301 } | 274 } |
302 advance() { | 275 bool get isEmpty => entries.isEmpty; |
303 currentIndex++; | 276 get currentEntry => entries.last; |
304 } | 277 advance() => entries.removeLast(); |
| 278 add(entry) => entries.add(entry); |
305 } | 279 } |
306 | 280 |
307 | 281 |
308 class Debugger { | 282 class Debugger { |
309 // Debug target process properties. | 283 // Debug target process properties. |
310 Process targetProcess; | 284 Process targetProcess; |
311 int portNumber; | 285 int portNumber; |
312 Socket socket; | 286 Socket socket; |
313 JsonBuffer responses = new JsonBuffer(); | 287 JsonBuffer responses = new JsonBuffer(); |
314 | 288 |
315 DebugScript script; | 289 DebugScript script; |
316 int seqNr = 0; // Sequence number of next debugger command message. | 290 int seqNr = 0; // Sequence number of next debugger command message. |
317 Command lastCommand = null; // Most recent command sent to target. | 291 Command lastCommand = null; // Most recent command sent to target. |
318 List<String> errors = new List(); | 292 List<String> errors = new List(); |
319 | 293 |
320 // Data collected from debug target. | 294 // Data collected from debug target. |
321 Map currentMessage = null; // Currently handled message sent by target. | 295 Map currentMessage = null; // Currently handled message sent by target. |
322 String scriptUrl = null; | 296 String scriptUrl = null; |
323 bool shutdownEventSeen = false; | 297 bool shutdownEventSeen = false; |
324 int isolateId = 0; | 298 int isolateId = 0; |
| 299 bool isPaused = false; |
325 | 300 |
326 Debugger(this.targetProcess, this.portNumber) { | 301 Debugger(this.targetProcess, this.portNumber) { |
327 stdin.listen((_) {}); | 302 stdin.listen((_) {}); |
328 var stdoutStringStream = targetProcess.stdout | 303 var stdoutStringStream = targetProcess.stdout |
329 .transform(new StringDecoder()) | 304 .transform(new StringDecoder()) |
330 .transform(new LineTransformer()); | 305 .transform(new LineTransformer()); |
331 stdoutStringStream.listen((line) { | 306 stdoutStringStream.listen((line) { |
332 if (showDebuggeeOutput) { | 307 if (showDebuggeeOutput) { |
333 print("TARG: $line"); | 308 print("TARG: $line"); |
334 } | 309 } |
335 }); | 310 }); |
336 | 311 |
337 var stderrStringStream = targetProcess.stderr | 312 var stderrStringStream = targetProcess.stderr |
338 .transform(new StringDecoder()) | 313 .transform(new StringDecoder()) |
339 .transform(new LineTransformer()); | 314 .transform(new LineTransformer()); |
340 stderrStringStream.listen((line) { | 315 stderrStringStream.listen((line) { |
341 if (showDebuggeeOutput) { | 316 if (showDebuggeeOutput) { |
342 print("TARG: $line"); | 317 print("TARG: $line"); |
343 } | 318 } |
344 }); | 319 }); |
345 } | 320 } |
346 | 321 |
347 // Handle debugger events for which there is no explicit | 322 // Handle debugger events, updating the debugger state. |
348 // entry in the debug script, for example isolate create and | 323 void handleEvent(Map<String,dynamic> msg) { |
349 // shutdown events, breakpoint resolution events, etc. | |
350 bool handleImplicitEvents(Map<String,dynamic> msg) { | |
351 if (msg["event"] == "isolate") { | 324 if (msg["event"] == "isolate") { |
352 if (msg["params"]["reason"] == "created") { | 325 if (msg["params"]["reason"] == "created") { |
353 isolateId = msg["params"]["id"]; | 326 isolateId = msg["params"]["id"]; |
354 assert(isolateId != null); | 327 assert(isolateId != null); |
355 print("Debuggee isolate id $isolateId created."); | 328 print("Debuggee isolate id $isolateId created."); |
356 } else if (msg["params"]["reason"] == "shutdown") { | 329 } else if (msg["params"]["reason"] == "shutdown") { |
357 print("Debuggee isolate id ${msg["params"]["id"]} shut down."); | 330 print("Debuggee isolate id ${msg["params"]["id"]} shut down."); |
358 shutdownEventSeen = true; | 331 shutdownEventSeen = true; |
359 if (script.currentEntry != null) { | 332 if (!script.isEmpty) { |
360 error("Premature isolate shutdown event seen."); | 333 error("Premature isolate shutdown event seen."); |
361 } | 334 } |
362 } | 335 } |
363 return true; | |
364 } else if (msg["event"] == "breakpointResolved") { | 336 } else if (msg["event"] == "breakpointResolved") { |
365 // Ignore the event. We may want to maintain a table of | 337 // Ignore the event. We may want to maintain a table of |
366 // breakpoints in the future. | 338 // breakpoints in the future. |
367 return true; | 339 } else if (msg["event"] == "paused") { |
| 340 isPaused = true; |
| 341 } else { |
| 342 error("unknown debugger event received"); |
368 } | 343 } |
369 return false; | |
370 } | 344 } |
371 | 345 |
372 // Handle one JSON message object and match it to the | 346 // Handle one JSON message object and match it to the |
373 // expected events and responses in the debugging script. | 347 // expected events and responses in the debugging script. |
374 void handleMessage(Map<String,dynamic> receivedMsg) { | 348 void handleMessage(Map<String,dynamic> receivedMsg) { |
375 currentMessage = receivedMsg; | 349 currentMessage = receivedMsg; |
376 var isHandled = handleImplicitEvents(receivedMsg); | 350 if (receivedMsg["event"] != null) { |
377 if (isHandled) return; | 351 handleEvent(receivedMsg); |
378 | 352 if (errorsDetected) { |
379 if (receivedMsg["id"] != null) { | 353 error("Error while handling debugger event"); |
| 354 error("Event received from debug target: $receivedMsg"); |
| 355 } |
| 356 } else if (receivedMsg["id"] != null) { |
380 // This is a response to the last command we sent. | 357 // This is a response to the last command we sent. |
381 assert(lastCommand != null); | 358 assert(lastCommand != null); |
382 lastCommand.matchResponse(this); | 359 lastCommand.matchResponse(this); |
383 lastCommand = null; | 360 lastCommand = null; |
384 if (errorsDetected) { | 361 if (errorsDetected) { |
385 error("Error while matching response to debugger command"); | 362 error("Error while matching response to debugger command"); |
386 error("Response received from debug target: $receivedMsg"); | 363 error("Response received from debug target: $receivedMsg"); |
387 } | 364 } |
388 return; | |
389 } | |
390 | |
391 // This message must be an event that is expected by the script. | |
392 assert(receivedMsg["event"] != null); | |
393 if ((script.currentEntry == null) || (script.currentEntry is Command)) { | |
394 // Error: unexpected event received. | |
395 error("unexpected event received: $receivedMsg"); | |
396 return; | |
397 } else { | |
398 // Match received message with expected event. | |
399 script.currentEntry.match(this); | |
400 if (errorsDetected) return; | |
401 script.advance(); | |
402 while (script.currentEntry is Matcher) { | |
403 script.currentEntry.match(this); | |
404 if (errorsDetected) return; | |
405 script.advance(); | |
406 } | |
407 } | 365 } |
408 } | 366 } |
409 | 367 |
410 // Send next debugger command in the script, if a response | 368 // Send next debugger command in the script, if a response |
411 // form the last command has been received and processed. | 369 // form the last command has been received and processed. |
412 void sendNextCommand() { | 370 void sendNextCommand() { |
413 if (lastCommand == null) { | 371 if (lastCommand == null) { |
414 if (script.currentEntry is Command) { | 372 if (script.currentEntry is Command) { |
415 script.currentEntry.send(this); | 373 script.currentEntry.send(this); |
416 lastCommand = script.currentEntry; | 374 lastCommand = script.currentEntry; |
417 seqNr++; | 375 seqNr++; |
418 script.advance(); | 376 script.advance(); |
419 } | 377 } |
420 } | 378 } |
421 } | 379 } |
422 | 380 |
423 // Handle data received over the wire from the debug target | 381 // Handle data received over the wire from the debug target |
424 // process. Split input from JSON wire format into individual | 382 // process. Split input from JSON wire format into individual |
425 // message objects (maps). | 383 // message objects (maps). |
426 void handleMessages() { | 384 void handleMessages() { |
427 var msg = responses.getNextMessage(); | 385 var msg = responses.getNextMessage(); |
428 while (msg != null) { | 386 while (msg != null) { |
429 if (verboseWire) print("RECV: $msg"); | 387 if (verboseWire) print("RECV: $msg"); |
430 var msgObj = JSON.parse(msg); | 388 var msgObj = JSON.parse(msg); |
431 handleMessage(msgObj); | 389 handleMessage(msgObj); |
432 if (errorsDetected) { | 390 if (errorsDetected) { |
433 error("Error while handling script entry ${script.currentIndex}"); | 391 error("Error while handling script entry"); |
434 error("Message received from debug target: $msg"); | 392 error("Message received from debug target: $msg"); |
435 close(killDebugee: true); | 393 close(killDebugee: true); |
436 return; | 394 return; |
437 } | 395 } |
438 if (shutdownEventSeen) { | 396 if (shutdownEventSeen) { |
439 close(); | 397 close(); |
440 return; | 398 return; |
441 } | 399 } |
442 sendNextCommand(); | 400 if (isPaused) sendNextCommand(); |
443 msg = responses.getNextMessage(); | 401 msg = responses.getNextMessage(); |
444 } | 402 } |
445 } | 403 } |
446 | 404 |
447 runScript(List entries) { | 405 runScript(List entries) { |
448 script = new DebugScript(entries); | 406 script = new DebugScript(entries); |
449 openConnection(); | 407 openConnection(); |
450 } | 408 } |
451 | 409 |
452 // Send a debugger command to the target VM. | 410 // Send a debugger command to the target VM. |
453 void sendMessage(Map<String,dynamic> msg) { | 411 void sendMessage(Map<String,dynamic> msg) { |
| 412 if (msg["id"] != null) { |
| 413 msg["id"] = seqNr; |
| 414 } |
| 415 if (msg["params"] != null && msg["params"]["isolateId"] != null) { |
| 416 msg["params"]["isolateId"] = isolateId; |
| 417 } |
454 String jsonMsg = JSON.stringify(msg); | 418 String jsonMsg = JSON.stringify(msg); |
455 if (verboseWire) print("SEND: $jsonMsg"); | 419 if (verboseWire) print("SEND: $jsonMsg"); |
456 socket.write(jsonMsg); | 420 socket.write(jsonMsg); |
457 } | 421 } |
458 | 422 |
459 bool get errorsDetected => errors.length > 0; | 423 bool get errorsDetected => errors.length > 0; |
460 | 424 |
461 // Record error message. | 425 // Record error message. |
462 void error(String s) { | 426 void error(String s) { |
463 errors.add(s); | 427 errors.add(s); |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
539 if (++retries >= 3) { | 503 if (++retries >= 3) { |
540 print('unable to find unused port: $e'); | 504 print('unable to find unused port: $e'); |
541 return -1; | 505 return -1; |
542 } else { | 506 } else { |
543 // Retry with another random port. | 507 // Retry with another random port. |
544 RunScript(script); | 508 RunScript(script); |
545 } | 509 } |
546 }); | 510 }); |
547 return true; | 511 return true; |
548 } | 512 } |
OLD | NEW |