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