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