Try to recover from malformed XML errors by manually parsing out the transaction_id.
[macgdbp.git] / Source / DebuggerConnection.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import <sys/socket.h>
18 #import <netinet/in.h>
19
20 #import "DebuggerConnection.h"
21
22 #import "AppDelegate.h"
23
24 // GDBpConnection (Private) ////////////////////////////////////////////////////
25
26 @interface DebuggerConnection ()
27 @property (readwrite, copy) NSString* status;
28 @property (assign) CFSocketRef socket;
29 @property (assign) CFReadStreamRef readStream;
30 @property NSUInteger lastReadTransaction;
31 @property (retain) NSMutableString* currentPacket;
32 @property (assign) CFWriteStreamRef writeStream;
33 @property NSUInteger lastWrittenTransaction;
34 @property (retain) NSMutableArray* queuedWrites;
35
36 - (void)connect;
37 - (void)close;
38 - (void)socketDidAccept;
39 - (void)socketDisconnected;
40 - (void)readStreamHasData;
41 - (void)send:(NSString*)command;
42 - (void)performSend:(NSString*)command;
43 - (void)errorEncountered:(NSString*)error;
44
45 - (void)handleResponse:(NSXMLDocument*)response;
46 - (void)initReceived:(NSXMLDocument*)response;
47 - (void)updateStatus:(NSXMLDocument*)response;
48 - (void)debuggerStep:(NSXMLDocument*)response;
49 - (void)rebuildStack:(NSXMLDocument*)response;
50 - (void)getStackFrame:(NSXMLDocument*)response;
51 - (void)setSource:(NSXMLDocument*)response;
52 - (void)contextsReceived:(NSXMLDocument*)response;
53 - (void)variablesReceived:(NSXMLDocument*)response;
54
55 - (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...;
56
57 - (void)sendQueuedWrites;
58
59 - (NSString*)escapedURIPath:(NSString*)path;
60 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response;
61 @end
62
63 // CFNetwork Callbacks /////////////////////////////////////////////////////////
64
65 void ReadStreamCallback(CFReadStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
66 {
67 DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
68 switch (eventType)
69 {
70 case kCFStreamEventHasBytesAvailable:
71 NSLog(@"About to read.");
72 [connection readStreamHasData];
73 break;
74
75 case kCFStreamEventErrorOccurred:
76 {
77 CFErrorRef error = CFReadStreamCopyError(stream);
78 CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
79 CFReadStreamClose(stream);
80 CFRelease(stream);
81 [connection errorEncountered:[[(NSError*)error autorelease] description]];
82 break;
83 }
84
85 case kCFStreamEventEndEncountered:
86 CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
87 CFReadStreamClose(stream);
88 CFRelease(stream);
89 [connection socketDisconnected];
90 break;
91 };
92 }
93
94 void WriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
95 {
96 DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
97 switch (eventType)
98 {
99 case kCFStreamEventCanAcceptBytes:
100 [connection sendQueuedWrites];
101 break;
102
103 case kCFStreamEventErrorOccurred:
104 {
105 CFErrorRef error = CFWriteStreamCopyError(stream);
106 CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
107 CFWriteStreamClose(stream);
108 CFRelease(stream);
109 [connection errorEncountered:[[(NSError*)error autorelease] description]];
110 break;
111 }
112
113 case kCFStreamEventEndEncountered:
114 CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
115 CFWriteStreamClose(stream);
116 CFRelease(stream);
117 [connection socketDisconnected];
118 break;
119 }
120 }
121
122 void SocketAcceptCallback(CFSocketRef socket,
123 CFSocketCallBackType callbackType,
124 CFDataRef address,
125 const void* data,
126 void* connectionRaw)
127 {
128 assert(callbackType == kCFSocketAcceptCallBack);
129 NSLog(@"SocketAcceptCallback()");
130
131 DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
132
133 CFReadStreamRef readStream;
134 CFWriteStreamRef writeStream;
135
136 // Create the streams on the socket.
137 CFStreamCreatePairWithSocket(kCFAllocatorDefault,
138 *(CFSocketNativeHandle*)data, // Socket handle.
139 &readStream, // Read stream in-pointer.
140 &writeStream); // Write stream in-pointer.
141
142 // Create struct to register callbacks for the stream.
143 CFStreamClientContext context;
144 context.version = 0;
145 context.info = connection;
146 context.retain = NULL;
147 context.release = NULL;
148 context.copyDescription = NULL;
149
150 // Set the client of the read stream.
151 CFOptionFlags readFlags =
152 kCFStreamEventOpenCompleted |
153 kCFStreamEventHasBytesAvailable |
154 kCFStreamEventErrorOccurred |
155 kCFStreamEventEndEncountered;
156 if (CFReadStreamSetClient(readStream, readFlags, ReadStreamCallback, &context))
157 // Schedule in run loop to do asynchronous communication with the engine.
158 CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
159 else
160 return;
161
162 // Open the stream now that it's scheduled on the run loop.
163 if (!CFReadStreamOpen(readStream))
164 {
165 CFStreamError error = CFReadStreamGetError(readStream);
166 NSLog(@"error! %@", error);
167 return;
168 }
169
170 // Set the client of the write stream.
171 CFOptionFlags writeFlags =
172 kCFStreamEventOpenCompleted |
173 kCFStreamEventCanAcceptBytes |
174 kCFStreamEventErrorOccurred |
175 kCFStreamEventEndEncountered;
176 if (CFWriteStreamSetClient(writeStream, writeFlags, WriteStreamCallback, &context))
177 // Schedule it in the run loop to receive error information.
178 CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
179 else
180 return;
181
182 // Open the write stream.
183 if (!CFWriteStreamOpen(writeStream))
184 {
185 CFStreamError error = CFWriteStreamGetError(writeStream);
186 NSLog(@"error! %@", error);
187 return;
188 }
189
190 connection.readStream = readStream;
191 connection.writeStream = writeStream;
192 [connection socketDidAccept];
193 }
194
195 // GDBpConnection //////////////////////////////////////////////////////////////
196
197 @implementation DebuggerConnection
198 @synthesize socket = socket_;
199 @synthesize readStream = readStream_;
200 @synthesize lastReadTransaction = lastReadTransaction_;
201 @synthesize currentPacket = currentPacket_;
202 @synthesize writeStream = writeStream_;
203 @synthesize lastWrittenTransaction = lastWrittenTransaction_;
204 @synthesize queuedWrites = queuedWrites_;
205 @synthesize status;
206 @synthesize delegate;
207
208 /**
209 * Creates a new DebuggerConnection and initializes the socket from the given connection
210 * paramters.
211 */
212 - (id)initWithPort:(NSUInteger)aPort
213 {
214 if (self = [super init])
215 {
216 port = aPort;
217 connected = NO;
218
219 [[BreakpointManager sharedManager] setConnection:self];
220
221 [self connect];
222 }
223 return self;
224 }
225
226 /**
227 * Deallocates the object
228 */
229 - (void)dealloc
230 {
231 [self close];
232 self.currentPacket = nil;
233
234 [super dealloc];
235 }
236
237
238 // Getters /////////////////////////////////////////////////////////////////////
239 #pragma mark Getters
240
241 /**
242 * Gets the port number
243 */
244 - (NSUInteger)port
245 {
246 return port;
247 }
248
249 /**
250 * Returns the name of the remote host
251 */
252 - (NSString*)remoteHost
253 {
254 if (!connected)
255 {
256 return @"(DISCONNECTED)";
257 }
258 // TODO: Either impl or remove.
259 return @"";
260 }
261
262 /**
263 * Returns whether or not we have an active connection
264 */
265 - (BOOL)isConnected
266 {
267 return connected;
268 }
269
270 // Commands ////////////////////////////////////////////////////////////////////
271 #pragma mark Commands
272
273 /**
274 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
275 * created every time you want to debug a page
276 */
277 - (void)reconnect
278 {
279 [self close];
280 self.status = @"Connecting";
281 [self connect];
282 }
283
284 /**
285 * Creates an entirely new stack and returns it as an array of StackFrame objects.
286 */
287 - (NSArray*)getCurrentStack
288 {
289 NSMutableArray* stack = [NSMutableArray array];
290 NSLog(@"NOTIMPLEMENTED(): %s", _cmd);
291 return stack;
292 }
293
294 /**
295 * Tells the debugger to continue running the script. Returns the current stack frame.
296 */
297 - (void)run
298 {
299 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"run"];
300 }
301
302 /**
303 * Tells the debugger to step into the current command.
304 */
305 - (void)stepIn
306 {
307 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"step_into"];
308 }
309
310 /**
311 * Tells the debugger to step out of the current context
312 */
313 - (void)stepOut
314 {
315 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"step_out"];
316 }
317
318 /**
319 * Tells the debugger to step over the current function
320 */
321 - (void)stepOver
322 {
323 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"step_over"];
324 }
325
326 /**
327 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
328 * that requested it so that the child can be attached.
329 */
330 - (NSArray*)getProperty:(NSString*)property
331 {
332 [socket send:[self createCommand:[NSString stringWithFormat:@"property_get -n \"%@\"", property]]];
333
334 NSXMLDocument* doc = [self processData:[socket receive]];
335
336 /*
337 <response>
338 <property> <!-- this is the one we requested -->
339 <property ... /> <!-- these are what we want -->
340 </property>
341 </repsonse>
342 */
343
344 // we now have to detach all the children so we can insert them into another document
345 NSXMLElement* parent = (NSXMLElement*)[[doc rootElement] childAtIndex:0];
346 NSArray* children = [parent children];
347 [parent setChildren:nil];
348 return children;
349 }
350
351 // Breakpoint Management ///////////////////////////////////////////////////////
352 #pragma mark Breakpoints
353
354 /**
355 * Send an add breakpoint command
356 */
357 - (void)addBreakpoint:(Breakpoint*)bp
358 {
359 if (!connected)
360 return;
361
362 NSString* file = [self escapedURIPath:[bp transformedPath]];
363 NSNumber* transaction = [self sendCommandWithCallback:@selector(breakpointReceived:)
364 format:@"breakpoint_set -t line -f %@ -n %i", file, [bp line]];
365 [callbackContext_ setObject:bp forKey:transaction];
366 }
367
368 /**
369 * Removes a breakpoint
370 */
371 - (void)removeBreakpoint:(Breakpoint*)bp
372 {
373 if (!connected)
374 {
375 return;
376 }
377
378 [self sendCommandWithCallback:nil format:@"breakpoint_remove -d %i", [bp debuggerId]];
379 }
380
381
382 // Socket and Stream Callbacks /////////////////////////////////////////////////
383 #pragma mark Callbacks
384
385 /**
386 * Called by SocketWrapper after the connection is successful. This immediately calls
387 * -[SocketWrapper receive] to clear the way for communication, though the information
388 * could be useful server information that we don't use right now.
389 */
390 - (void)socketDidAccept
391 {
392 connected = YES;
393 transactionID = 1;
394 stackFrames_ = [[NSMutableDictionary alloc] init];
395 self.queuedWrites = [NSMutableArray array];
396 writeQueueLock_ = [NSRecursiveLock new];
397 callTable_ = [NSMutableDictionary new];
398 callbackContext_ = [NSMutableDictionary new];
399 }
400
401 /**
402 * Receives errors from the SocketWrapper and updates the display
403 */
404 - (void)errorEncountered:(NSString*)error
405 {
406 [delegate errorEncountered:error];
407 }
408
409 /**
410 * Creates, connects to, and schedules a CFSocket.
411 */
412 - (void)connect
413 {
414 // Pass ourselves to the callback so we don't have to use ugly globals.
415 CFSocketContext context;
416 context.version = 0;
417 context.info = self;
418 context.retain = NULL;
419 context.release = NULL;
420 context.copyDescription = NULL;
421
422 // Create the address structure.
423 struct sockaddr_in address;
424 memset(&address, 0, sizeof(address));
425 address.sin_len = sizeof(address);
426 address.sin_family = AF_INET;
427 address.sin_port = htons(port);
428 address.sin_addr.s_addr = htonl(INADDR_ANY);
429
430 // Create the socket signature.
431 CFSocketSignature signature;
432 signature.protocolFamily = PF_INET;
433 signature.socketType = SOCK_STREAM;
434 signature.protocol = IPPROTO_TCP;
435 signature.address = (CFDataRef)[NSData dataWithBytes:&address length:sizeof(address)];
436
437 socket_ = CFSocketCreateWithSocketSignature(kCFAllocatorDefault,
438 &signature, // Socket signature.
439 kCFSocketAcceptCallBack, // Callback types.
440 SocketAcceptCallback, // Callout function pointer.
441 &context); // Context to pass to callout.
442 if (!socket_)
443 {
444 [self errorEncountered:@"Could not open socket."];
445 return;
446 }
447
448 // Allow old, yet-to-be recycled sockets to be reused.
449 BOOL yes = YES;
450 setsockopt(CFSocketGetNative(socket_), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(BOOL));
451
452 // Schedule the socket on the run loop.
453 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
454 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
455 CFRelease(source);
456
457 self.status = @"Connecting";
458 }
459
460 /**
461 * Closes a socket and releases the ref.
462 */
463 - (void)close
464 {
465 // The socket goes down, so do the streams, which clean themselves up.
466 CFSocketInvalidate(socket_);
467 CFRelease(socket_);
468 [stackFrames_ release];
469 self.queuedWrites = nil;
470 [writeQueueLock_ release];
471 [callTable_ release];
472 [callbackContext_ release];
473 }
474
475 /**
476 * Notification that the socket disconnected.
477 */
478 - (void)socketDisconnected
479 {
480 [self close];
481 [delegate debuggerDisconnected];
482 }
483
484 /**
485 * Callback from the CFReadStream that there is data waiting to be read.
486 */
487 - (void)readStreamHasData
488 {
489 UInt8 buffer[1024];
490 CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, 1024);
491 const char* charBuffer = (const char*)buffer;
492
493 // We haven't finished reading a packet, so just read more data in.
494 if (currentPacketIndex_ < packetSize_)
495 {
496 currentPacketIndex_ += bytesRead;
497 CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
498 buffer,
499 bytesRead,
500 kCFStringEncodingUTF8,
501 true);
502 [self.currentPacket appendString:(NSString*)bufferString];
503 CFRelease(bufferString);
504 }
505 // Time to read a new packet.
506 else
507 {
508 // Read the message header: the size.
509 packetSize_ = atoi(charBuffer);
510 currentPacketIndex_ = bytesRead - strlen(charBuffer);
511 CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
512 buffer + strlen(charBuffer) + 1,
513 bytesRead - strlen(charBuffer) - 1,
514 kCFStringEncodingUTF8,
515 true);
516 self.currentPacket = [NSMutableString stringWithString:(NSString*)bufferString];
517 CFRelease(bufferString);
518 }
519
520 // We have finished reading the packet.
521 if (currentPacketIndex_ >= packetSize_)
522 {
523 packetSize_ = 0;
524 currentPacketIndex_ = 0;
525
526 // Test if we can convert it into an NSXMLDocument.
527 NSError* error = nil;
528 NSXMLDocument* xmlTest = [[NSXMLDocument alloc] initWithXMLString:currentPacket_ options:NSXMLDocumentTidyXML error:&error];
529 if (error)
530 {
531 NSLog(@"Could not parse XML? --- %@", error);
532 NSLog(@"Error UserInfo: %@", [error userInfo]);
533 NSLog(@"This is the XML Document: %@", currentPacket_);
534
535 // We do not want to starve the write queue, so manually parse out the
536 // transaction ID.
537 NSRange location = [currentPacket_ rangeOfString:@"transaction_id"];
538 if (location.location != NSNotFound)
539 {
540 NSUInteger start = location.location + location.length;
541 NSUInteger end = start;
542
543 NSCharacterSet* numericSet = [NSCharacterSet decimalDigitCharacterSet];
544
545 // Loop over the characters after the attribute name to extract the ID.
546 while (end < [currentPacket_ length])
547 {
548 unichar c = [currentPacket_ characterAtIndex:end];
549 if ([numericSet characterIsMember:c])
550 {
551 // If this character is numeric, extend the range to substring.
552 ++end;
553 }
554 else
555 {
556 if (start == end)
557 {
558 // If this character is nonnumeric and we have nothing in the
559 // range, skip this character.
560 ++start;
561 ++end;
562 }
563 else
564 {
565 // We've moved past the numeric ID so we should stop searching.
566 break;
567 }
568 }
569 }
570
571 // If we were able to extract the transaction ID, update the last read.
572 NSRange substringRange = NSMakeRange(start, end - start);
573 NSString* transaction = [currentPacket_ substringWithRange:substringRange];
574 if ([transaction length])
575 {
576 lastReadTransaction_ = [transaction intValue];
577 return;
578 }
579 }
580
581 // Otherwise, assume +1 and hope it works.
582 ++lastReadTransaction_;
583 return;
584 }
585 [self handleResponse:[xmlTest autorelease]];
586 }
587 }
588
589 /**
590 * Writes a command into the write stream. If the stream is ready for writing,
591 * we do so immediately. If not, the command is queued and will be written
592 * when the stream is ready.
593 */
594 - (void)send:(NSString*)command
595 {
596 if (lastReadTransaction_ >= lastWrittenTransaction_ && CFWriteStreamCanAcceptBytes(writeStream_))
597 [self performSend:command];
598 else
599 [queuedWrites_ addObject:command];
600 }
601
602 /**
603 * This performs a blocking send. This should ONLY be called when we know we
604 * have write access to the stream. We will busy wait in case we don't do a full
605 * send.
606 */
607 - (void)performSend:(NSString*)command
608 {
609 BOOL done = NO;
610
611 char* string = (char*)[command UTF8String];
612 int stringLength = strlen(string);
613
614 // Log the command if TransportDebug is enabled.
615 if ([[[[NSProcessInfo processInfo] environment] objectForKey:@"TransportDebug"] boolValue])
616 NSLog(@"--> %@", command);
617
618 // Busy wait while writing. BAADD. Should background this operation.
619 while (!done)
620 {
621 if (CFWriteStreamCanAcceptBytes(writeStream_))
622 {
623 // Include the NULL byte in the string when we write.
624 int bytesWritten = CFWriteStreamWrite(writeStream_, (UInt8*)string, stringLength + 1);
625 if (bytesWritten < 0)
626 {
627 NSLog(@"write error");
628 }
629 // Incomplete write.
630 else if (bytesWritten < strlen(string))
631 {
632 // Adjust the buffer and wait for another chance to write.
633 stringLength -= bytesWritten;
634 memmove(string, string + bytesWritten, stringLength);
635 }
636 else
637 {
638 done = YES;
639
640 // We need to scan the string to find the transactionID.
641 NSRange occurrence = [command rangeOfString:@"-i "];
642 if (occurrence.location == NSNotFound)
643 {
644 NSLog(@"sent %@ without a transaction ID", command);
645 continue;
646 }
647 NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
648 lastWrittenTransaction_ = [transaction intValue];
649 NSLog(@"command = %@", command);
650 NSLog(@"read=%d, write=%d", lastReadTransaction_, lastWrittenTransaction_);
651 }
652 }
653 }
654 }
655
656 - (void)handleResponse:(NSXMLDocument*)response
657 {
658 // Check and see if there's an error.
659 NSArray* error = [[response rootElement] elementsForName:@"error"];
660 if ([error count] > 0)
661 {
662 NSLog(@"Xdebug error: %@", error);
663 [delegate errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
664 }
665
666 // If TransportDebug is enabled, log the response.
667 if ([[[[NSProcessInfo processInfo] environment] objectForKey:@"TransportDebug"] boolValue])
668 NSLog(@"<-- %@", response);
669
670 // Get the name of the command from the engine's response.
671 NSInteger transaction = [self transactionIDFromResponse:response];
672 if (transaction < lastReadTransaction_)
673 NSLog(@"out of date transaction %@", response);
674
675 if (transaction != lastWrittenTransaction_)
676 NSLog(@"txn doesn't match last written %@", response);
677
678 lastReadTransaction_ = transaction;
679 NSLog(@"read=%d, write=%d", lastReadTransaction_, lastWrittenTransaction_);
680
681 if ([[[response rootElement] name] isEqualToString:@"init"])
682 {
683 [self initReceived:response];
684 return;
685 }
686
687 NSString* callbackStr = [callTable_ objectForKey:[NSNumber numberWithInt:lastReadTransaction_]];
688 if (callbackStr)
689 {
690 SEL callback = NSSelectorFromString(callbackStr);
691 [self performSelector:callback withObject:response];
692 }
693
694 [self sendQueuedWrites];
695 }
696
697 // Specific Response Handlers //////////////////////////////////////////////////
698 #pragma mark Response Handlers
699
700 /**
701 * Initial packet received. We've started a brand-new connection to the engine.
702 */
703 - (void)initReceived:(NSXMLDocument*)response
704 {
705 // Register any breakpoints that exist offline.
706 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
707 [self addBreakpoint:bp];
708
709 // Load the debugger to make it look active.
710 [delegate debuggerConnected];
711
712 // TODO: update the status.
713 }
714
715 /**
716 * Receiver for status updates. This just freshens up the UI.
717 */
718 - (void)updateStatus:(NSXMLDocument*)response
719 {
720 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
721 if (status == nil || [status isEqualToString:@"Stopped"] || [status isEqualToString:@"Stopping"])
722 {
723 connected = NO;
724 [self close];
725 [delegate debuggerDisconnected];
726
727 self.status = @"Stopped";
728 }
729 }
730
731 /**
732 * Step in/out/over and run all take this path. We first get the status of the
733 * debugger and then request fresh stack information.
734 */
735 - (void)debuggerStep:(NSXMLDocument*)response
736 {
737 [self sendCommandWithCallback:@selector(updateStatus:) format:@"status"];
738 NSString* command = [[[response rootElement] attributeForName:@"command"] stringValue];
739
740 // If this is the run command, tell the delegate that a bunch of updates
741 // are coming. Also remove all existing stack routes and request a new stack.
742 // TODO: figure out if we can not clobber the stack every time.
743 if (YES || [command isEqualToString:@"run"])
744 {
745 if ([delegate respondsToSelector:@selector(clobberStack)])
746 [delegate clobberStack];
747 [stackFrames_ removeAllObjects];
748 stackFirstTransactionID_ = [[self sendCommandWithCallback:@selector(rebuildStack:) format:@"stack_depth"] intValue];
749 }
750 }
751
752 /**
753 * We ask for the stack_depth and now we clobber the stack and start rebuilding
754 * it.
755 */
756 - (void)rebuildStack:(NSXMLDocument*)response
757 {
758 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
759
760 if (stackFirstTransactionID_ == [self transactionIDFromResponse:response])
761 stackDepth_ = depth;
762
763 // We now need to alloc a bunch of stack frames and get the basic information
764 // for them.
765 for (NSInteger i = 0; i < depth; i++)
766 {
767 // Use the transaction ID to create a routing path.
768 NSNumber* routingID = [self sendCommandWithCallback:@selector(getStackFrame:) format:@"stack_get -d %d", i];
769 [stackFrames_ setObject:[StackFrame alloc] forKey:routingID];
770 }
771 }
772
773 /**
774 * The initial rebuild of the stack frame. We now have enough to initialize
775 * a StackFrame object.
776 */
777 - (void)getStackFrame:(NSXMLDocument*)response
778 {
779 // Get the routing information.
780 NSInteger routingID = [self transactionIDFromResponse:response];
781 if (routingID < stackFirstTransactionID_)
782 return;
783 NSNumber* routingNumber = [NSNumber numberWithInt:routingID];
784
785 // Make sure we initialized this frame in our last |-rebuildStack:|.
786 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
787 if (!frame)
788 return;
789
790 NSXMLElement* xmlframe = [[[response rootElement] children] objectAtIndex:0];
791
792 // Initialize the stack frame.
793 [frame initWithIndex:[[[xmlframe attributeForName:@"level"] stringValue] intValue]
794 withFilename:[[xmlframe attributeForName:@"filename"] stringValue]
795 withSource:nil
796 atLine:[[[xmlframe attributeForName:@"lineno"] stringValue] intValue]
797 inFunction:[[xmlframe attributeForName:@"where"] stringValue]
798 withVariables:nil];
799
800 // Get the source code of the file. Escape % in URL chars.
801 NSString* escapedFilename = [frame.filename stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
802 NSNumber* transaction = [self sendCommandWithCallback:@selector(setSource:) format:@"source -f %@", escapedFilename];
803 [callbackContext_ setObject:routingNumber forKey:transaction];
804
805 // Get the names of all the contexts.
806 transaction = [self sendCommandWithCallback:@selector(contextsReceived:) format:@"context_names -d %d", frame.index];
807 [callbackContext_ setObject:routingNumber forKey:transaction];
808
809 if ([delegate respondsToSelector:@selector(newStackFrame:)])
810 [delegate newStackFrame:frame];
811 }
812
813 /**
814 * Callback for setting the source of a file while rebuilding a specific stack
815 * frame.
816 */
817 - (void)setSource:(NSXMLDocument*)response
818 {
819 NSNumber* transaction = [NSNumber numberWithInt:[self transactionIDFromResponse:response]];
820 if ([transaction intValue] < stackFirstTransactionID_)
821 return;
822 NSNumber* routingNumber = [callbackContext_ objectForKey:transaction];
823 if (!routingNumber)
824 return;
825
826 [callbackContext_ removeObjectForKey:transaction];
827 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
828 if (!frame)
829 return;
830
831 frame.source = [[response rootElement] value];
832
833 if ([delegate respondsToSelector:@selector(sourceUpdated:)])
834 [delegate sourceUpdated:frame];
835 }
836
837 /**
838 * Enumerates all the contexts of a given stack frame. We then in turn get the
839 * contents of each one of these contexts.
840 */
841 - (void)contextsReceived:(NSXMLDocument*)response
842 {
843 // Get the stack frame's routing ID and use it again.
844 NSNumber* receivedTransaction = [NSNumber numberWithInt:[self transactionIDFromResponse:response]];
845 if ([receivedTransaction intValue] < stackFirstTransactionID_)
846 return;
847 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
848 if (!routingID)
849 return;
850
851 // Get the stack frame by the |routingID|.
852 StackFrame* frame = [stackFrames_ objectForKey:routingID];
853
854 NSXMLElement* contextNames = [response rootElement];
855 for (NSXMLElement* context in [contextNames children])
856 {
857 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
858
859 // Fetch each context's variables.
860 NSNumber* transaction = [self sendCommandWithCallback:@selector(variablesReceived:)
861 format:@"context_get -d %d -c %d", frame.index, cid];
862 [callbackContext_ setObject:routingID forKey:transaction];
863 }
864 }
865
866 /**
867 * Receives the variables from the context and attaches them to the stack frame.
868 */
869 - (void)variablesReceived:(NSXMLDocument*)response
870 {
871 // Get the stack frame's routing ID and use it again.
872 NSInteger transaction = [self transactionIDFromResponse:response];
873 if (transaction < stackFirstTransactionID_)
874 return;
875 NSNumber* receivedTransaction = [NSNumber numberWithInt:transaction];
876 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
877 if (!routingID)
878 return;
879
880 // Get the stack frame by the |routingID|.
881 StackFrame* frame = [stackFrames_ objectForKey:routingID];
882
883 NSMutableArray* variables = [NSMutableArray array];
884
885 // Merge the frame's existing variables.
886 if (frame.variables)
887 [variables addObjectsFromArray:frame.variables];
888
889 // Add these new variables.
890 NSArray* addVariables = [[response rootElement] children];
891 if (addVariables)
892 [variables addObjectsFromArray:addVariables];
893
894 frame.variables = variables;
895 }
896
897 /**
898 * Callback for setting a breakpoint.
899 */
900 - (void)breakpointReceived:(NSXMLDocument*)response
901 {
902 NSNumber* transaction = [NSNumber numberWithInt:[self transactionIDFromResponse:response]];
903 Breakpoint* bp = [callbackContext_ objectForKey:transaction];
904 if (!bp)
905 return;
906
907 [callbackContext_ removeObjectForKey:callbackContext_];
908 [bp setDebuggerId:[[[[response rootElement] attributeForName:@"id"] stringValue] intValue]];
909 }
910
911 #pragma mark Private
912
913 /**
914 * This will send a command to the debugger engine. It will append the
915 * transaction ID automatically. It accepts a NSString command along with a
916 * a variable number of arguments to substitute into the command, a la
917 * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
918 */
919 - (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...
920 {
921 // Collect varargs and format command.
922 va_list args;
923 va_start(args, format);
924 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
925 va_end(args);
926
927 NSNumber* callbackKey = [NSNumber numberWithInt:transactionID++];
928 if (callback)
929 [callTable_ setObject:NSStringFromSelector(callback) forKey:callbackKey];
930
931 [self send:[NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]];
932
933 return callbackKey;
934 }
935
936 /**
937 * Checks if there are unsent commands in the |queuedWrites_| queue and sends
938 * them if it's OK to do so. This will not block.
939 */
940 - (void)sendQueuedWrites
941 {
942 [writeQueueLock_ lock];
943 if (lastReadTransaction_ >= lastWrittenTransaction_ && [queuedWrites_ count] > 0)
944 {
945 NSString* command = [queuedWrites_ objectAtIndex:0];
946 NSLog(@"Sending queued write: %@", command);
947
948 // We don't want to block because this is called from the main thread.
949 // |-performSend:| busy waits when the stream is not ready. Bail out
950 // before we do that becuase busy waiting is BAD.
951 if (CFWriteStreamCanAcceptBytes(writeStream_))
952 {
953 [self performSend:command];
954 [queuedWrites_ removeObjectAtIndex:0];
955 }
956 }
957 [writeQueueLock_ unlock];
958 }
959
960 /**
961 * Given a file path, this returns a file:// URI and escapes any spaces for the
962 * debugger engine.
963 */
964 - (NSString*)escapedURIPath:(NSString*)path
965 {
966 // Custon GDBp paths are fine.
967 if ([[path substringToIndex:4] isEqualToString:@"gdbp"])
968 return path;
969
970 // Create a temporary URL that will escape all the nasty characters.
971 NSURL* url = [NSURL fileURLWithPath:path];
972 NSString* urlString = [url absoluteString];
973
974 // Remove the host because this is a file:// URL;
975 urlString = [urlString stringByReplacingOccurrencesOfString:[url host] withString:@""];
976
977 // Escape % for use in printf-style NSString formatters.
978 urlString = [urlString stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
979 return urlString;
980 }
981
982 /**
983 * Returns the transaction_id from an NSXMLDocument.
984 */
985 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response
986 {
987 return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
988 }
989
990 @end