* Start making |-createStackFrame:| asynchronous
[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
25 typedef enum _StackFrameComponents
26 {
27 kStackFrameContexts,
28 kStackFrameSource,
29 kStackFrameVariables
30 } StackFrameComponent;
31
32 // GDBpConnection (Private) ////////////////////////////////////////////////////
33
34 @interface GDBpConnection ()
35 @property (readwrite, copy) NSString* status;
36 @property (assign) CFSocketRef socket;
37 @property (assign) CFReadStreamRef readStream;
38 @property (retain) NSMutableString* currentPacket;
39 @property (assign) CFWriteStreamRef writeStream;
40 @property (retain) NSMutableArray* queuedWrites;
41
42 - (void)connect;
43 - (void)close;
44 - (void)socketDidAccept;
45 - (void)socketDisconnected;
46 - (void)readStreamHasData;
47 - (void)send:(NSString*)command;
48 - (void)performSend:(NSString*)command;
49 - (void)errorEncountered:(NSString*)error;
50
51 - (void)handleResponse:(NSXMLDocument*)response;
52 - (void)initReceived:(NSXMLDocument*)response;
53 - (void)updateStatus:(NSXMLDocument*)response;
54 - (void)debuggerStep:(NSXMLDocument*)response;
55 - (void)rebuildStack:(NSXMLDocument*)response;
56 - (void)getStackFrame:(NSXMLDocument*)response;
57 - (void)handleRouted:(NSArray*)path response:(NSXMLDocument*)response;
58
59 - (NSString*)createCommand:(NSString*)cmd, ...;
60 - (NSString*)createRouted:(NSString*)routingID command:(NSString*)cmd, ...;
61
62 - (StackFrame*)createStackFrame:(int)depth;
63 - (NSString*)escapedURIPath:(NSString*)path;
64 @end
65
66 // CFNetwork Callbacks /////////////////////////////////////////////////////////
67
68 void ReadStreamCallback(CFReadStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
69 {
70 NSLog(@"ReadStreamCallback()");
71 GDBpConnection* connection = (GDBpConnection*)connectionRaw;
72 switch (eventType)
73 {
74 case kCFStreamEventHasBytesAvailable:
75 [connection readStreamHasData];
76 break;
77
78 case kCFStreamEventErrorOccurred:
79 {
80 CFErrorRef error = CFReadStreamCopyError(stream);
81 CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
82 CFReadStreamClose(stream);
83 CFRelease(stream);
84 [connection errorEncountered:[[(NSError*)error autorelease] description]];
85 break;
86 }
87
88 case kCFStreamEventEndEncountered:
89 CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
90 CFReadStreamClose(stream);
91 CFRelease(stream);
92 [connection socketDisconnected];
93 break;
94 };
95 }
96
97 void WriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
98 {
99 NSLog(@"WriteStreamCallback()");
100 GDBpConnection* connection = (GDBpConnection*)connectionRaw;
101 switch (eventType)
102 {
103 case kCFStreamEventCanAcceptBytes:
104 NSLog(@"can accept bytes");
105 if ([connection.queuedWrites count] > 0)
106 {
107 NSString* command = [connection.queuedWrites objectAtIndex:0];
108 [connection performSend:command];
109 [connection.queuedWrites removeObjectAtIndex:0];
110 }
111 break;
112
113 case kCFStreamEventErrorOccurred:
114 {
115 CFErrorRef error = CFWriteStreamCopyError(stream);
116 CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
117 CFWriteStreamClose(stream);
118 CFRelease(stream);
119 [connection errorEncountered:[[(NSError*)error autorelease] description]];
120 break;
121 }
122
123 case kCFStreamEventEndEncountered:
124 CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
125 CFWriteStreamClose(stream);
126 CFRelease(stream);
127 [connection socketDisconnected];
128 break;
129 }
130 }
131
132 void SocketAcceptCallback(CFSocketRef socket,
133 CFSocketCallBackType callbackType,
134 CFDataRef address,
135 const void* data,
136 void* connectionRaw)
137 {
138 assert(callbackType == kCFSocketAcceptCallBack);
139 NSLog(@"SocketAcceptCallback()");
140
141 GDBpConnection* connection = (GDBpConnection*)connectionRaw;
142
143 CFReadStreamRef readStream;
144 CFWriteStreamRef writeStream;
145
146 // Create the streams on the socket.
147 CFStreamCreatePairWithSocket(kCFAllocatorDefault,
148 *(CFSocketNativeHandle*)data, // Socket handle.
149 &readStream, // Read stream in-pointer.
150 &writeStream); // Write stream in-pointer.
151
152 // Create struct to register callbacks for the stream.
153 CFStreamClientContext context;
154 context.version = 0;
155 context.info = connection;
156 context.retain = NULL;
157 context.release = NULL;
158 context.copyDescription = NULL;
159
160 // Set the client of the read stream.
161 CFOptionFlags readFlags =
162 kCFStreamEventOpenCompleted |
163 kCFStreamEventHasBytesAvailable |
164 kCFStreamEventErrorOccurred |
165 kCFStreamEventEndEncountered;
166 if (CFReadStreamSetClient(readStream, readFlags, ReadStreamCallback, &context))
167 // Schedule in run loop to do asynchronous communication with the engine.
168 CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
169 else
170 return;
171
172 NSLog(@"Read stream scheduled");
173
174 // Open the stream now that it's scheduled on the run loop.
175 if (!CFReadStreamOpen(readStream))
176 {
177 CFStreamError error = CFReadStreamGetError(readStream);
178 NSLog(@"error! %@", error);
179 return;
180 }
181
182 NSLog(@"Read stream opened");
183
184 // Set the client of the write stream.
185 CFOptionFlags writeFlags =
186 kCFStreamEventOpenCompleted |
187 kCFStreamEventCanAcceptBytes |
188 kCFStreamEventErrorOccurred |
189 kCFStreamEventEndEncountered;
190 if (CFWriteStreamSetClient(writeStream, writeFlags, WriteStreamCallback, &context))
191 // Schedule it in the run loop to receive error information.
192 CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
193 else
194 return;
195
196 NSLog(@"Write stream scheduled");
197
198 // Open the write stream.
199 if (!CFWriteStreamOpen(writeStream))
200 {
201 CFStreamError error = CFWriteStreamGetError(writeStream);
202 NSLog(@"error! %@", error);
203 return;
204 }
205
206 NSLog(@"Write stream opened");
207
208 connection.readStream = readStream;
209 connection.writeStream = writeStream;
210 [connection socketDidAccept];
211 }
212
213 // GDBpConnection //////////////////////////////////////////////////////////////
214
215 @implementation GDBpConnection
216 @synthesize socket = socket_;
217 @synthesize readStream = readStream_;
218 @synthesize currentPacket = currentPacket_;
219 @synthesize writeStream = writeStream_;
220 @synthesize queuedWrites = queuedWrites_;
221 @synthesize status;
222 @synthesize delegate;
223
224 /**
225 * Creates a new DebuggerConnection and initializes the socket from the given connection
226 * paramters.
227 */
228 - (id)initWithPort:(int)aPort
229 {
230 if (self = [super init])
231 {
232 port = aPort;
233 connected = NO;
234
235 [[BreakpointManager sharedManager] setConnection:self];
236
237 [self connect];
238 }
239 return self;
240 }
241
242 /**
243 * Deallocates the object
244 */
245 - (void)dealloc
246 {
247 [self close];
248 self.currentPacket = nil;
249
250 [super dealloc];
251 }
252
253 /**
254 * Gets the port number
255 */
256 - (int)port
257 {
258 return port;
259 }
260
261 /**
262 * Returns the name of the remote host
263 */
264 - (NSString*)remoteHost
265 {
266 if (!connected)
267 {
268 return @"(DISCONNECTED)";
269 }
270 // TODO: Either impl or remove.
271 return @"";
272 }
273
274 /**
275 * Returns whether or not we have an active connection
276 */
277 - (BOOL)isConnected
278 {
279 return connected;
280 }
281
282 /**
283 * Called by SocketWrapper after the connection is successful. This immediately calls
284 * -[SocketWrapper receive] to clear the way for communication, though the information
285 * could be useful server information that we don't use right now.
286 */
287 - (void)socketDidAccept
288 {
289 connected = YES;
290 transactionID = 0;
291 stackFrames_ = [[NSMutableDictionary alloc] init];
292 self.queuedWrites = [NSMutableArray array];
293 }
294
295 /**
296 * Receives errors from the SocketWrapper and updates the display
297 */
298 - (void)errorEncountered:(NSString*)error
299 {
300 [delegate errorEncountered:error];
301 }
302
303 /**
304 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
305 * created every time you want to debug a page
306 */
307 - (void)reconnect
308 {
309 [self close];
310 self.status = @"Connecting";
311 [self connect];
312 }
313
314 /**
315 * Creates an entirely new stack and returns it as an array of StackFrame objects.
316 */
317 - (NSArray*)getCurrentStack
318 {
319 NSMutableArray* stack = [NSMutableArray array];
320 NSLog(@"NOTIMPLEMENTED(): %s", _cmd);
321 return stack;
322 }
323
324 /**
325 * Tells the debugger to continue running the script. Returns the current stack frame.
326 */
327 - (void)run
328 {
329 [self send:[self createCommand:@"run"]];
330 }
331
332 /**
333 * Tells the debugger to step into the current command.
334 */
335 - (void)stepIn
336 {
337 [self send:[self createCommand:@"step_into"]];
338 }
339
340 /**
341 * Tells the debugger to step out of the current context
342 */
343 - (void)stepOut
344 {
345 [self send:[self createCommand:@"step_out"]];
346 }
347
348 /**
349 * Tells the debugger to step over the current function
350 */
351 - (void)stepOver
352 {
353 [self send:[self createCommand:@"step_over"]];
354 }
355
356 /**
357 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
358 * that requested it so that the child can be attached.
359 */
360 - (NSArray*)getProperty:(NSString*)property
361 {
362 [socket send:[self createCommand:[NSString stringWithFormat:@"property_get -n \"%@\"", property]]];
363
364 NSXMLDocument* doc = [self processData:[socket receive]];
365
366 /*
367 <response>
368 <property> <!-- this is the one we requested -->
369 <property ... /> <!-- these are what we want -->
370 </property>
371 </repsonse>
372 */
373
374 // we now have to detach all the children so we can insert them into another document
375 NSXMLElement* parent = (NSXMLElement*)[[doc rootElement] childAtIndex:0];
376 NSArray* children = [parent children];
377 [parent setChildren:nil];
378 return children;
379 }
380
381 #pragma mark Breakpoints
382
383 /**
384 * Send an add breakpoint command
385 */
386 - (void)addBreakpoint:(Breakpoint*)bp
387 {
388 if (!connected)
389 return;
390
391 NSString* file = [self escapedURIPath:[bp transformedPath]];
392 NSString* cmd = [self createCommand:[NSString stringWithFormat:@"breakpoint_set -t line -f %@ -n %i", file, [bp line]]];
393 [socket send:cmd];
394 NSXMLDocument* info = [self processData:[socket receive]];
395 [bp setDebuggerId:[[[[info 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 [socket send:[self createCommand:[NSString stringWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]]]];
409 [socket receive];
410 }
411
412 #pragma mark Socket and Stream Callbacks
413
414 /**
415 * Creates, connects to, and schedules a CFSocket.
416 */
417 - (void)connect
418 {
419 // Pass ourselves to the callback so we don't have to use ugly globals.
420 CFSocketContext context;
421 context.version = 0;
422 context.info = self;
423 context.retain = NULL;
424 context.release = NULL;
425 context.copyDescription = NULL;
426
427 // Create the address structure.
428 struct sockaddr_in address;
429 memset(&address, 0, sizeof(address));
430 address.sin_len = sizeof(address);
431 address.sin_family = AF_INET;
432 address.sin_port = htons(port);
433 address.sin_addr.s_addr = htonl(INADDR_ANY);
434
435 // Create the socket signature.
436 CFSocketSignature signature;
437 signature.protocolFamily = PF_INET;
438 signature.socketType = SOCK_STREAM;
439 signature.protocol = IPPROTO_TCP;
440 signature.address = (CFDataRef)[NSData dataWithBytes:&address length:sizeof(address)];
441
442 socket_ = CFSocketCreateWithSocketSignature(kCFAllocatorDefault,
443 &signature, // Socket signature.
444 kCFSocketAcceptCallBack, // Callback types.
445 SocketAcceptCallback, // Callout function pointer.
446 &context); // Context to pass to callout.
447 if (!socket_)
448 {
449 [self errorEncountered:@"Could not open socket."];
450 return;
451 }
452
453 // Allow old, yet-to-be recycled sockets to be reused.
454 BOOL yes = YES;
455 setsockopt(CFSocketGetNative(socket_), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(BOOL));
456
457 // Schedule the socket on the run loop.
458 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
459 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
460 CFRelease(source);
461
462 self.status = @"Connecting";
463 }
464
465 /**
466 * Closes a socket and releases the ref.
467 */
468 - (void)close
469 {
470 // The socket goes down, so do the streams, which clean themselves up.
471 CFSocketInvalidate(socket_);
472 CFRelease(socket_);
473 [stackFrames_ release];
474 self.queuedWrites = nil;
475 }
476
477 /**
478 * Notification that the socket disconnected.
479 */
480 - (void)socketDisconnected
481 {
482 [self close];
483 [delegate debuggerDisconnected];
484 }
485
486 /**
487 * Callback from the CFReadStream that there is data waiting to be read.
488 */
489 - (void)readStreamHasData
490 {
491 UInt8 buffer[1024];
492 CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, 1024);
493 const char* charBuffer = (const char*)buffer;
494
495 // We haven't finished reading a packet, so just read more data in.
496 if (currentPacketIndex_ < packetSize_)
497 {
498 [currentPacket_ appendFormat:@"%s", buffer];
499 currentPacketIndex_ += bytesRead;
500 }
501 // Time to read a new packet.
502 else
503 {
504 // Read the message header: the size.
505 packetSize_ = atoi(charBuffer);
506 currentPacketIndex_ = bytesRead - strlen(charBuffer);
507 self.currentPacket = [NSMutableString stringWithFormat:@"%s", buffer + strlen(charBuffer) + 1];
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 (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 }
582 }
583 }
584
585 #pragma mark Response Handlers
586
587 - (void)handleResponse:(NSXMLDocument*)response
588 {
589 // Check and see if there's an error.
590 NSArray* error = [[response rootElement] elementsForName:@"error"];
591 if ([error count] > 0)
592 {
593 NSLog(@"Xdebug error: %@", error);
594 [delegate errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
595 }
596
597 // If TransportDebug is enabled, log the response.
598 if ([[[[NSProcessInfo processInfo] environment] objectForKey:@"TransportDebug"] boolValue])
599 NSLog(@"<-- %@", response);
600
601 // Get the name of the command from the engine's response.
602 NSString* command = [[[response rootElement] attributeForName:@"command"] stringValue];
603 NSString* transaction = [[[response rootElement] attributeForName:@"transaction_id"] stringValue];
604
605 // Dispatch the command response to an appropriate handler.
606 if ([[[response rootElement] name] isEqualToString:@"init"])
607 [self initReceived:response];
608 else if ([command isEqualToString:@"status"])
609 [self updateStatus:response];
610 else if ([command isEqualToString:@"run"] || [command isEqualToString:@"step_into"] ||
611 [command isEqualToString:@"step_over"] || [command isEqualToString:@"step_out"])
612 [self debuggerStep:response];
613 else if ([command isEqualToString:@"stack_depth"])
614 [self rebuildStack:response];
615 else if ([command isEqualToString:@"stack_get"])
616 [self getStackFrame:response];
617
618 NSArray* routingPath = [transaction componentsSeparatedByString:@"."];
619 if ([routingPath count] > 1)
620 [self handleRouted:routingPath response:response];
621 }
622
623 /**
624 * Initial packet received. We've started a brand-new connection to the engine.
625 */
626 - (void)initReceived:(NSXMLDocument*)response
627 {
628 // Register any breakpoints that exist offline.
629 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
630 [self addBreakpoint:bp];
631
632 // Load the debugger to make it look active.
633 [delegate debuggerConnected];
634
635 [self send:[self createCommand:@"status"]];
636 }
637
638 /**
639 * Receiver for status updates. This just freshens up the UI.
640 */
641 - (void)updateStatus:(NSXMLDocument*)response
642 {
643 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
644 if (status == nil || [status isEqualToString:@"Stopped"] || [status isEqualToString:@"Stopping"])
645 {
646 connected = NO;
647 [self close];
648 [delegate debuggerDisconnected];
649
650 self.status = @"Stopped";
651 }
652 }
653
654 /**
655 * Step in/out/over and run all take this path. We first get the status of the
656 * debugger and then request fresh stack information.
657 */
658 - (void)debuggerStep:(NSXMLDocument*)response
659 {
660 [self send:[self createCommand:@"status"]];
661 NSString* command = [[[response rootElement] attributeForName:@"command"] stringValue];
662 NSUInteger routingID = [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
663
664 // If this is the run command, tell the delegate that a bunch of updates
665 // are coming. Also remove all existing stack routes and request a new stack.
666 if ([command isEqualToString:@"run"])
667 {
668 [delegate clobberStack];
669 [stackFrames_ removeAllObjects];
670 [self send:[self createCommand:@"stack_depth"]];
671 }
672
673 [self send:[self createRouted:[NSString stringWithFormat:@"%u", routingID] command:@"stack_get -d 0"]];
674 }
675
676 /**
677 * We ask for the stack_depth and now we clobber the stack and start rebuilding
678 * it.
679 */
680 - (void)rebuildStack:(NSXMLDocument*)response
681 {
682 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
683
684 // We now need to alloc a bunch of stack frames and get the basic information
685 // for them.
686 for (NSInteger i = 0; i < depth; i++)
687 {
688 NSString* command = [self createCommand:@"stack_get -d %d", i];
689
690 // Use the transaction ID to create a routing path.
691 NSNumber* routingID = [NSNumber numberWithInt:transactionID - 1];
692 [stackFrames_ setObject:[StackFrame alloc] forKey:routingID];
693
694 [self send:command];
695 }
696 }
697
698 /**
699 * The initial rebuild of the stack frame. We now have enough to initialize
700 * a StackFrame object.
701 */
702 - (void)getStackFrame:(NSXMLDocument*)response
703 {
704 // Get the routing information.
705 NSUInteger routingID = [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
706 NSNumber* routingNumber = [NSNumber numberWithInt:routingID];
707
708 // Make sure we initialized this frame in our last |-rebuildStack:|.
709 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
710 if (!frame)
711 return;
712
713 NSXMLElement* xmlframe = [[[response rootElement] children] objectAtIndex:0];
714
715 // Initialize the stack frame.
716 [frame initWithIndex:[[[xmlframe attributeForName:@"level"] stringValue] intValue]
717 withFilename:[[xmlframe attributeForName:@"filename"] stringValue]
718 withSource:nil
719 atLine:[[[xmlframe attributeForName:@"lineno"] stringValue] intValue]
720 inFunction:[[xmlframe attributeForName:@"where"] stringValue]
721 withVariables:nil];
722
723 // Now that we have an initialized frame, request additional information.
724 NSString* routingFormat = @"%u.%d";
725 NSString* routingPath = nil;
726
727 // Get the source code of the file. Escape % in URL chars.
728 NSString* escapedFilename = [frame.filename stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
729 routingPath = [NSString stringWithFormat:routingFormat, routingID, kStackFrameSource];
730 [self send:[self createRouted:routingPath command:[NSString stringWithFormat:@"source -f %@", escapedFilename]]];
731
732 // Get the names of all the contexts.
733 routingPath = [NSString stringWithFormat:routingFormat, routingID, kStackFrameContexts];
734 [self send:[self createRouted:routingPath command:@"context_names -d %d", frame.index]];
735 }
736
737 /**
738 * Routed responses are currently only used in getting all the stack frame data.
739 */
740 - (void)handleRouted:(NSArray*)path response:(NSXMLDocument*)response
741 {
742 NSLog(@"routed %@ = %@", path, response);
743
744 // Format of |path|: transactionID.routingID.component
745 StackFrameComponent component = [[path objectAtIndex:2] intValue];
746
747 // See if we can find the stack frame based on routingID.
748 NSNumber* routingNumber = [NSNumber numberWithInt:[[path objectAtIndex:1] intValue]];
749 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
750 if (!frame)
751 return;
752
753 if (component == kStackFrameSource)
754 frame.source = [[response rootElement] value];
755 }
756
757 #pragma mark Private
758
759 /**
760 * Helper method to create a string command with the -i <transaction id> automatically tacked on. Takes
761 * a variable number of arguments and parses the given command with +[NSString stringWithFormat:]
762 */
763 - (NSString*)createCommand:(NSString*)cmd, ...
764 {
765 // collect varargs
766 va_list argList;
767 va_start(argList, cmd);
768 NSString* format = [[NSString alloc] initWithFormat:cmd arguments:argList]; // format the command
769 va_end(argList);
770
771 return [NSString stringWithFormat:@"%@ -i %d", [format autorelease], transactionID++];
772 }
773
774 /**
775 * Helper to create a command string. This works a lot like |-createCommand:| but
776 * it also takes a routing ID, which is used to route response data to specific
777 * objects.
778 */
779 - (NSString*)createRouted:(NSString*)routingID command:(NSString*)cmd, ...
780 {
781 va_list argList;
782 va_start(argList, cmd);
783 NSString* format = [[NSString alloc] initWithFormat:cmd arguments:argList];
784 va_end(argList);
785
786 return [NSString stringWithFormat:@"%@ -i %d.%@", [format autorelease], transactionID++, routingID];
787 }
788
789 /**
790 * Generates a stack frame for the given depth
791 */
792 - (StackFrame*)createStackFrame:(int)stackDepth
793 {
794 // get the names of all the contexts
795 [socket send:[self createCommand:@"context_names -d 0"]];
796 NSXMLElement* contextNames = [[self processData:[socket receive]] rootElement];
797 NSMutableArray* variables = [NSMutableArray array];
798 for (NSXMLElement* context in [contextNames children])
799 {
800 NSString* name = [[context attributeForName:@"name"] stringValue];
801 int cid = [[[context attributeForName:@"id"] stringValue] intValue];
802
803 // fetch the contexts
804 [socket send:[self createCommand:[NSString stringWithFormat:@"context_get -d %d -c %d", stackDepth, cid]]];
805 NSArray* addVars = [[[self processData:[socket receive]] rootElement] children];
806 if (addVars != nil && name != nil)
807 [variables addObjectsFromArray:addVars];
808 }
809
810 return nil;
811 }
812
813 /**
814 * Given a file path, this returns a file:// URI and escapes any spaces for the
815 * debugger engine.
816 */
817 - (NSString*)escapedURIPath:(NSString*)path
818 {
819 // Custon GDBp paths are fine.
820 if ([[path substringToIndex:4] isEqualToString:@"gdbp"])
821 return path;
822
823 // Create a temporary URL that will escape all the nasty characters.
824 NSURL* url = [NSURL fileURLWithPath:path];
825 NSString* urlString = [url absoluteString];
826
827 // Remove the host because this is a file:// URL;
828 urlString = [urlString stringByReplacingOccurrencesOfString:[url host] withString:@""];
829
830 // Escape % for use in printf-style NSString formatters.
831 urlString = [urlString stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
832 return urlString;
833 }
834
835 @end