Switch some basic ints to NSUInteger in GDBpConnection.
[macgdbp.git] / Source / GDBpConnection.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 "GDBpConnection.h"
21
22 #import "AppDelegate.h"
23
24 // GDBpConnection (Private) ////////////////////////////////////////////////////
25
26 @interface GDBpConnection ()
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 GDBpConnection* connection = (GDBpConnection*)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 GDBpConnection* connection = (GDBpConnection*)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 GDBpConnection* connection = (GDBpConnection*)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 GDBpConnection
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 * Gets the port number
239 */
240 - (NSUInteger)port
241 {
242 return port;
243 }
244
245 /**
246 * Returns the name of the remote host
247 */
248 - (NSString*)remoteHost
249 {
250 if (!connected)
251 {
252 return @"(DISCONNECTED)";
253 }
254 // TODO: Either impl or remove.
255 return @"";
256 }
257
258 /**
259 * Returns whether or not we have an active connection
260 */
261 - (BOOL)isConnected
262 {
263 return connected;
264 }
265
266 /**
267 * Called by SocketWrapper after the connection is successful. This immediately calls
268 * -[SocketWrapper receive] to clear the way for communication, though the information
269 * could be useful server information that we don't use right now.
270 */
271 - (void)socketDidAccept
272 {
273 connected = YES;
274 transactionID = 1;
275 stackFrames_ = [[NSMutableDictionary alloc] init];
276 self.queuedWrites = [NSMutableArray array];
277 writeQueueLock_ = [NSRecursiveLock new];
278 callTable_ = [NSMutableDictionary new];
279 callbackContext_ = [NSMutableDictionary new];
280 }
281
282 /**
283 * Receives errors from the SocketWrapper and updates the display
284 */
285 - (void)errorEncountered:(NSString*)error
286 {
287 [delegate errorEncountered:error];
288 }
289
290 /**
291 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
292 * created every time you want to debug a page
293 */
294 - (void)reconnect
295 {
296 [self close];
297 self.status = @"Connecting";
298 [self connect];
299 }
300
301 /**
302 * Creates an entirely new stack and returns it as an array of StackFrame objects.
303 */
304 - (NSArray*)getCurrentStack
305 {
306 NSMutableArray* stack = [NSMutableArray array];
307 NSLog(@"NOTIMPLEMENTED(): %s", _cmd);
308 return stack;
309 }
310
311 /**
312 * Tells the debugger to continue running the script. Returns the current stack frame.
313 */
314 - (void)run
315 {
316 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"run"];
317 }
318
319 /**
320 * Tells the debugger to step into the current command.
321 */
322 - (void)stepIn
323 {
324 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"step_into"];
325 }
326
327 /**
328 * Tells the debugger to step out of the current context
329 */
330 - (void)stepOut
331 {
332 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"step_out"];
333 }
334
335 /**
336 * Tells the debugger to step over the current function
337 */
338 - (void)stepOver
339 {
340 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"step_over"];
341 }
342
343 /**
344 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
345 * that requested it so that the child can be attached.
346 */
347 - (NSArray*)getProperty:(NSString*)property
348 {
349 [socket send:[self createCommand:[NSString stringWithFormat:@"property_get -n \"%@\"", property]]];
350
351 NSXMLDocument* doc = [self processData:[socket receive]];
352
353 /*
354 <response>
355 <property> <!-- this is the one we requested -->
356 <property ... /> <!-- these are what we want -->
357 </property>
358 </repsonse>
359 */
360
361 // we now have to detach all the children so we can insert them into another document
362 NSXMLElement* parent = (NSXMLElement*)[[doc rootElement] childAtIndex:0];
363 NSArray* children = [parent children];
364 [parent setChildren:nil];
365 return children;
366 }
367
368 #pragma mark Breakpoints
369
370 /**
371 * Send an add breakpoint command
372 */
373 - (void)addBreakpoint:(Breakpoint*)bp
374 {
375 if (!connected)
376 return;
377
378 NSString* file = [self escapedURIPath:[bp transformedPath]];
379 NSNumber* transaction = [self sendCommandWithCallback:@selector(breakpointReceived:)
380 format:@"breakpoint_set -t line -f %@ -n %i", file, [bp line]];
381 [callbackContext_ setObject:bp forKey:transaction];
382 }
383
384 /**
385 * Callback for setting a breakpoint.
386 */
387 - (void)breakpointReceived:(NSXMLDocument*)response
388 {
389 NSNumber* transaction = [NSNumber numberWithInt:[self transactionIDFromResponse:response]];
390 Breakpoint* bp = [callbackContext_ objectForKey:transaction];
391 if (!bp)
392 return;
393
394 [callbackContext_ removeObjectForKey:callbackContext_];
395 [bp setDebuggerId:[[[[response rootElement] attributeForName:@"id"] stringValue] intValue]];
396 }
397
398 /**
399 * Removes a breakpoint
400 */
401 - (void)removeBreakpoint:(Breakpoint*)bp
402 {
403 if (!connected)
404 {
405 return;
406 }
407
408 [self sendCommandWithCallback:nil format:@"breakpoint_remove -d %i", [bp debuggerId]];
409 }
410
411 #pragma mark Socket and Stream Callbacks
412
413 /**
414 * Creates, connects to, and schedules a CFSocket.
415 */
416 - (void)connect
417 {
418 // Pass ourselves to the callback so we don't have to use ugly globals.
419 CFSocketContext context;
420 context.version = 0;
421 context.info = self;
422 context.retain = NULL;
423 context.release = NULL;
424 context.copyDescription = NULL;
425
426 // Create the address structure.
427 struct sockaddr_in address;
428 memset(&address, 0, sizeof(address));
429 address.sin_len = sizeof(address);
430 address.sin_family = AF_INET;
431 address.sin_port = htons(port);
432 address.sin_addr.s_addr = htonl(INADDR_ANY);
433
434 // Create the socket signature.
435 CFSocketSignature signature;
436 signature.protocolFamily = PF_INET;
437 signature.socketType = SOCK_STREAM;
438 signature.protocol = IPPROTO_TCP;
439 signature.address = (CFDataRef)[NSData dataWithBytes:&address length:sizeof(address)];
440
441 socket_ = CFSocketCreateWithSocketSignature(kCFAllocatorDefault,
442 &signature, // Socket signature.
443 kCFSocketAcceptCallBack, // Callback types.
444 SocketAcceptCallback, // Callout function pointer.
445 &context); // Context to pass to callout.
446 if (!socket_)
447 {
448 [self errorEncountered:@"Could not open socket."];
449 return;
450 }
451
452 // Allow old, yet-to-be recycled sockets to be reused.
453 BOOL yes = YES;
454 setsockopt(CFSocketGetNative(socket_), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(BOOL));
455
456 // Schedule the socket on the run loop.
457 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
458 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
459 CFRelease(source);
460
461 self.status = @"Connecting";
462 }
463
464 /**
465 * Closes a socket and releases the ref.
466 */
467 - (void)close
468 {
469 // The socket goes down, so do the streams, which clean themselves up.
470 CFSocketInvalidate(socket_);
471 CFRelease(socket_);
472 [stackFrames_ release];
473 self.queuedWrites = nil;
474 [writeQueueLock_ release];
475 [callTable_ release];
476 [callbackContext_ release];
477 }
478
479 /**
480 * Notification that the socket disconnected.
481 */
482 - (void)socketDisconnected
483 {
484 [self close];
485 [delegate debuggerDisconnected];
486 }
487
488 /**
489 * Callback from the CFReadStream that there is data waiting to be read.
490 */
491 - (void)readStreamHasData
492 {
493 UInt8 buffer[1024];
494 CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, 1024);
495 const char* charBuffer = (const char*)buffer;
496
497 // We haven't finished reading a packet, so just read more data in.
498 if (currentPacketIndex_ < packetSize_)
499 {
500 currentPacketIndex_ += bytesRead;
501 CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
502 buffer,
503 bytesRead,
504 kCFStringEncodingUTF8,
505 true);
506 [self.currentPacket appendString:(NSString*)bufferString];
507 CFRelease(bufferString);
508 }
509 // Time to read a new packet.
510 else
511 {
512 // Read the message header: the size.
513 packetSize_ = atoi(charBuffer);
514 currentPacketIndex_ = bytesRead - strlen(charBuffer);
515 CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
516 buffer + strlen(charBuffer) + 1,
517 bytesRead - strlen(charBuffer) - 1,
518 kCFStringEncodingUTF8,
519 true);
520 self.currentPacket = [NSMutableString stringWithString:(NSString*)bufferString];
521 CFRelease(bufferString);
522 }
523
524 // We have finished reading the packet.
525 if (currentPacketIndex_ >= packetSize_)
526 {
527 packetSize_ = 0;
528 currentPacketIndex_ = 0;
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 if (error)
534 {
535 NSLog(@"Could not parse XML? --- %@", error);
536 NSLog(@"Error UserInfo: %@", [error userInfo]);
537 NSLog(@"This is the XML Document: %@", currentPacket_);
538 return;
539 }
540 [self handleResponse:[xmlTest autorelease]];
541 }
542 }
543
544 /**
545 * Writes a command into the write stream. If the stream is ready for writing,
546 * we do so immediately. If not, the command is queued and will be written
547 * when the stream is ready.
548 */
549 - (void)send:(NSString*)command
550 {
551 if (lastReadTransaction_ >= lastWrittenTransaction_ && CFWriteStreamCanAcceptBytes(writeStream_))
552 [self performSend:command];
553 else
554 [queuedWrites_ addObject:command];
555 }
556
557 /**
558 * This performs a blocking send. This should ONLY be called when we know we
559 * have write access to the stream. We will busy wait in case we don't do a full
560 * send.
561 */
562 - (void)performSend:(NSString*)command
563 {
564 BOOL done = NO;
565
566 char* string = (char*)[command UTF8String];
567 int stringLength = strlen(string);
568
569 // Log the command if TransportDebug is enabled.
570 if ([[[[NSProcessInfo processInfo] environment] objectForKey:@"TransportDebug"] boolValue])
571 NSLog(@"--> %@", command);
572
573 // Busy wait while writing. BAADD. Should background this operation.
574 while (!done)
575 {
576 if (CFWriteStreamCanAcceptBytes(writeStream_))
577 {
578 // Include the NULL byte in the string when we write.
579 int bytesWritten = CFWriteStreamWrite(writeStream_, (UInt8*)string, stringLength + 1);
580 if (bytesWritten < 0)
581 {
582 NSLog(@"write error");
583 }
584 // Incomplete write.
585 else if (bytesWritten < strlen(string))
586 {
587 // Adjust the buffer and wait for another chance to write.
588 stringLength -= bytesWritten;
589 memmove(string, string + bytesWritten, stringLength);
590 }
591 else
592 {
593 done = YES;
594
595 // We need to scan the string to find the transactionID.
596 NSRange occurrence = [command rangeOfString:@"-i "];
597 if (occurrence.location == NSNotFound)
598 {
599 NSLog(@"sent %@ without a transaction ID", command);
600 continue;
601 }
602 NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
603 lastWrittenTransaction_ = [transaction intValue];
604 NSLog(@"command = %@", command);
605 NSLog(@"read=%d, write=%d", lastReadTransaction_, lastWrittenTransaction_);
606 }
607 }
608 }
609 }
610
611 #pragma mark Response Handlers
612
613 - (void)handleResponse:(NSXMLDocument*)response
614 {
615 // Check and see if there's an error.
616 NSArray* error = [[response rootElement] elementsForName:@"error"];
617 if ([error count] > 0)
618 {
619 NSLog(@"Xdebug error: %@", error);
620 [delegate errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
621 }
622
623 // If TransportDebug is enabled, log the response.
624 if ([[[[NSProcessInfo processInfo] environment] objectForKey:@"TransportDebug"] boolValue])
625 NSLog(@"<-- %@", response);
626
627 // Get the name of the command from the engine's response.
628 NSInteger transaction = [self transactionIDFromResponse:response];
629 if (transaction < lastReadTransaction_)
630 NSLog(@"out of date transaction %@", response);
631
632 if (transaction != lastWrittenTransaction_)
633 NSLog(@"txn doesn't match last written %@", response);
634
635 lastReadTransaction_ = transaction;
636 NSLog(@"read=%d, write=%d", lastReadTransaction_, lastWrittenTransaction_);
637
638 if ([[[response rootElement] name] isEqualToString:@"init"])
639 {
640 [self initReceived:response];
641 return;
642 }
643
644 NSString* callbackStr = [callTable_ objectForKey:[NSNumber numberWithInt:lastReadTransaction_]];
645 if (callbackStr)
646 {
647 SEL callback = NSSelectorFromString(callbackStr);
648 [self performSelector:callback withObject:response];
649 }
650
651 [self sendQueuedWrites];
652 }
653
654 /**
655 * Initial packet received. We've started a brand-new connection to the engine.
656 */
657 - (void)initReceived:(NSXMLDocument*)response
658 {
659 // Register any breakpoints that exist offline.
660 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
661 [self addBreakpoint:bp];
662
663 // Load the debugger to make it look active.
664 [delegate debuggerConnected];
665
666 // TODO: update the status.
667 }
668
669 /**
670 * Receiver for status updates. This just freshens up the UI.
671 */
672 - (void)updateStatus:(NSXMLDocument*)response
673 {
674 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
675 if (status == nil || [status isEqualToString:@"Stopped"] || [status isEqualToString:@"Stopping"])
676 {
677 connected = NO;
678 [self close];
679 [delegate debuggerDisconnected];
680
681 self.status = @"Stopped";
682 }
683 }
684
685 /**
686 * Step in/out/over and run all take this path. We first get the status of the
687 * debugger and then request fresh stack information.
688 */
689 - (void)debuggerStep:(NSXMLDocument*)response
690 {
691 [self sendCommandWithCallback:@selector(updateStatus:) format:@"status"];
692 NSString* command = [[[response rootElement] attributeForName:@"command"] stringValue];
693
694 // If this is the run command, tell the delegate that a bunch of updates
695 // are coming. Also remove all existing stack routes and request a new stack.
696 // TODO: figure out if we can not clobber the stack every time.
697 if (YES || [command isEqualToString:@"run"])
698 {
699 if ([delegate respondsToSelector:@selector(clobberStack)])
700 [delegate clobberStack];
701 [stackFrames_ removeAllObjects];
702 stackFirstTransactionID_ = [[self sendCommandWithCallback:@selector(rebuildStack:) format:@"stack_depth"] intValue];
703 }
704 }
705
706 /**
707 * We ask for the stack_depth and now we clobber the stack and start rebuilding
708 * it.
709 */
710 - (void)rebuildStack:(NSXMLDocument*)response
711 {
712 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
713
714 if (stackFirstTransactionID_ == [self transactionIDFromResponse:response])
715 stackDepth_ = depth;
716
717 // We now need to alloc a bunch of stack frames and get the basic information
718 // for them.
719 for (NSInteger i = 0; i < depth; i++)
720 {
721 // Use the transaction ID to create a routing path.
722 NSNumber* routingID = [self sendCommandWithCallback:@selector(getStackFrame:) format:@"stack_get -d %d", i];
723 [stackFrames_ setObject:[StackFrame alloc] forKey:routingID];
724 }
725 }
726
727 /**
728 * The initial rebuild of the stack frame. We now have enough to initialize
729 * a StackFrame object.
730 */
731 - (void)getStackFrame:(NSXMLDocument*)response
732 {
733 // Get the routing information.
734 NSInteger routingID = [self transactionIDFromResponse:response];
735 if (routingID < stackFirstTransactionID_)
736 return;
737 NSNumber* routingNumber = [NSNumber numberWithInt:routingID];
738
739 // Make sure we initialized this frame in our last |-rebuildStack:|.
740 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
741 if (!frame)
742 return;
743
744 NSXMLElement* xmlframe = [[[response rootElement] children] objectAtIndex:0];
745
746 // Initialize the stack frame.
747 [frame initWithIndex:[[[xmlframe attributeForName:@"level"] stringValue] intValue]
748 withFilename:[[xmlframe attributeForName:@"filename"] stringValue]
749 withSource:nil
750 atLine:[[[xmlframe attributeForName:@"lineno"] stringValue] intValue]
751 inFunction:[[xmlframe attributeForName:@"where"] stringValue]
752 withVariables:nil];
753
754 // Get the source code of the file. Escape % in URL chars.
755 NSString* escapedFilename = [frame.filename stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
756 NSNumber* transaction = [self sendCommandWithCallback:@selector(setSource:) format:@"source -f %@", escapedFilename];
757 [callbackContext_ setObject:routingNumber forKey:transaction];
758
759 // Get the names of all the contexts.
760 transaction = [self sendCommandWithCallback:@selector(contextsReceived:) format:@"context_names -d %d", frame.index];
761 [callbackContext_ setObject:routingNumber forKey:transaction];
762
763 if ([delegate respondsToSelector:@selector(newStackFrame:)])
764 [delegate newStackFrame:frame];
765 }
766
767 /**
768 * Callback for setting the source of a file while rebuilding a specific stack
769 * frame.
770 */
771 - (void)setSource:(NSXMLDocument*)response
772 {
773 NSNumber* transaction = [NSNumber numberWithInt:[self transactionIDFromResponse:response]];
774 if ([transaction intValue] < stackFirstTransactionID_)
775 return;
776 NSNumber* routingNumber = [callbackContext_ objectForKey:transaction];
777 if (!routingNumber)
778 return;
779
780 [callbackContext_ removeObjectForKey:transaction];
781 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
782 if (!frame)
783 return;
784
785 frame.source = [[response rootElement] value];
786
787 if ([delegate respondsToSelector:@selector(sourceUpdated:)])
788 [delegate sourceUpdated:frame];
789 }
790
791 /**
792 * Enumerates all the contexts of a given stack frame. We then in turn get the
793 * contents of each one of these contexts.
794 */
795 - (void)contextsReceived:(NSXMLDocument*)response
796 {
797 // Get the stack frame's routing ID and use it again.
798 NSNumber* receivedTransaction = [NSNumber numberWithInt:[self transactionIDFromResponse:response]];
799 if ([receivedTransaction intValue] < stackFirstTransactionID_)
800 return;
801 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
802 if (!routingID)
803 return;
804
805 // Get the stack frame by the |routingID|.
806 StackFrame* frame = [stackFrames_ objectForKey:routingID];
807
808 NSXMLElement* contextNames = [response rootElement];
809 for (NSXMLElement* context in [contextNames children])
810 {
811 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
812
813 // Fetch each context's variables.
814 NSNumber* transaction = [self sendCommandWithCallback:@selector(variablesReceived:)
815 format:@"context_get -d %d -c %d", frame.index, cid];
816 [callbackContext_ setObject:routingID forKey:transaction];
817 }
818 }
819
820 /**
821 * Receives the variables from the context and attaches them to the stack frame.
822 */
823 - (void)variablesReceived:(NSXMLDocument*)response
824 {
825 // Get the stack frame's routing ID and use it again.
826 NSInteger transaction = [self transactionIDFromResponse:response];
827 if (transaction < stackFirstTransactionID_)
828 return;
829 NSNumber* receivedTransaction = [NSNumber numberWithInt:transaction];
830 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
831 if (!routingID)
832 return;
833
834 // Get the stack frame by the |routingID|.
835 StackFrame* frame = [stackFrames_ objectForKey:routingID];
836
837 NSMutableArray* variables = [NSMutableArray array];
838
839 // Merge the frame's existing variables.
840 if (frame.variables)
841 [variables addObjectsFromArray:frame.variables];
842
843 // Add these new variables.
844 NSArray* addVariables = [[response rootElement] children];
845 if (addVariables)
846 [variables addObjectsFromArray:addVariables];
847
848 frame.variables = variables;
849 }
850
851 #pragma mark Private
852
853 /**
854 * This will send a command to the debugger engine. It will append the
855 * transaction ID automatically. It accepts a NSString command along with a
856 * a variable number of arguments to substitute into the command, a la
857 * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
858 */
859 - (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...
860 {
861 // Collect varargs and format command.
862 va_list args;
863 va_start(args, format);
864 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
865 va_end(args);
866
867 NSNumber* callbackKey = [NSNumber numberWithInt:transactionID++];
868 if (callback)
869 [callTable_ setObject:NSStringFromSelector(callback) forKey:callbackKey];
870
871 [self send:[NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]];
872
873 return callbackKey;
874 }
875
876 /**
877 * Checks if there are unsent commands in the |queuedWrites_| queue and sends
878 * them if it's OK to do so. This will not block.
879 */
880 - (void)sendQueuedWrites
881 {
882 [writeQueueLock_ lock];
883 if (lastReadTransaction_ >= lastWrittenTransaction_ && [queuedWrites_ count] > 0)
884 {
885 NSString* command = [queuedWrites_ objectAtIndex:0];
886 NSLog(@"Sending queued write: %@", command);
887
888 // We don't want to block because this is called from the main thread.
889 // |-performSend:| busy waits when the stream is not ready. Bail out
890 // before we do that becuase busy waiting is BAD.
891 if (CFWriteStreamCanAcceptBytes(writeStream_))
892 {
893 [self performSend:command];
894 [queuedWrites_ removeObjectAtIndex:0];
895 }
896 }
897 [writeQueueLock_ unlock];
898 }
899
900 /**
901 * Given a file path, this returns a file:// URI and escapes any spaces for the
902 * debugger engine.
903 */
904 - (NSString*)escapedURIPath:(NSString*)path
905 {
906 // Custon GDBp paths are fine.
907 if ([[path substringToIndex:4] isEqualToString:@"gdbp"])
908 return path;
909
910 // Create a temporary URL that will escape all the nasty characters.
911 NSURL* url = [NSURL fileURLWithPath:path];
912 NSString* urlString = [url absoluteString];
913
914 // Remove the host because this is a file:// URL;
915 urlString = [urlString stringByReplacingOccurrencesOfString:[url host] withString:@""];
916
917 // Escape % for use in printf-style NSString formatters.
918 urlString = [urlString stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
919 return urlString;
920 }
921
922 /**
923 * Returns the transaction_id from an NSXMLDocument.
924 */
925 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response
926 {
927 return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
928 }
929
930 @end