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