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