3 * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
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.
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.
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
17 #import <sys/socket.h>
18 #import <netinet/in.h>
20 #import "DebuggerConnection.h"
22 #import "AppDelegate.h"
24 // GDBpConnection (Private) ////////////////////////////////////////////////////
26 @interface DebuggerConnection ()
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
;
38 - (void)socketDidAccept
;
39 - (void)socketDisconnected
;
40 - (void)readStreamHasData
;
41 - (void)send
:(NSString
*)command
;
42 - (void)performSend
:(NSString
*)command
;
43 - (void)errorEncountered
:(NSString
*)error
;
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 - (void)propertiesReceived
:(NSXMLDocument
*)response
;
56 - (NSNumber
*)sendCommandWithCallback
:(SEL)callback format
:(NSString
*)format
, ...
;
58 - (void)sendQueuedWrites
;
60 - (NSString
*)escapedURIPath
:(NSString
*)path
;
61 - (NSInteger
)transactionIDFromResponse
:(NSXMLDocument
*)response
;
64 // CFNetwork Callbacks /////////////////////////////////////////////////////////
66 void ReadStreamCallback(CFReadStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
68 DebuggerConnection
* connection
= (DebuggerConnection
*)connectionRaw
;
71 case kCFStreamEventHasBytesAvailable
:
72 NSLog(@
"About to read.");
73 [connection readStreamHasData
];
76 case kCFStreamEventErrorOccurred
:
78 CFErrorRef error
= CFReadStreamCopyError(stream
);
79 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
80 CFReadStreamClose(stream
);
82 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
86 case kCFStreamEventEndEncountered
:
87 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
88 CFReadStreamClose(stream
);
90 [connection socketDisconnected
];
95 void WriteStreamCallback(CFWriteStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
97 DebuggerConnection
* connection
= (DebuggerConnection
*)connectionRaw
;
100 case kCFStreamEventCanAcceptBytes
:
101 [connection sendQueuedWrites
];
104 case kCFStreamEventErrorOccurred
:
106 CFErrorRef error
= CFWriteStreamCopyError(stream
);
107 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
108 CFWriteStreamClose(stream
);
110 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
114 case kCFStreamEventEndEncountered
:
115 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
116 CFWriteStreamClose(stream
);
118 [connection socketDisconnected
];
123 void SocketAcceptCallback(CFSocketRef socket
,
124 CFSocketCallBackType callbackType
,
129 assert(callbackType
== kCFSocketAcceptCallBack
);
130 NSLog(@
"SocketAcceptCallback()");
132 DebuggerConnection
* connection
= (DebuggerConnection
*)connectionRaw
;
134 CFReadStreamRef readStream
;
135 CFWriteStreamRef writeStream
;
137 // Create the streams on the socket.
138 CFStreamCreatePairWithSocket(kCFAllocatorDefault
,
139 *(CFSocketNativeHandle
*)data
, // Socket handle.
140 &readStream
, // Read stream in-pointer.
141 &writeStream
); // Write stream in-pointer.
143 // Create struct to register callbacks for the stream.
144 CFStreamClientContext context
;
146 context.info
= connection
;
147 context.retain
= NULL
;
148 context.release
= NULL
;
149 context.copyDescription
= NULL
;
151 // Set the client of the read stream.
152 CFOptionFlags readFlags
=
153 kCFStreamEventOpenCompleted |
154 kCFStreamEventHasBytesAvailable |
155 kCFStreamEventErrorOccurred |
156 kCFStreamEventEndEncountered
;
157 if (CFReadStreamSetClient(readStream
, readFlags
, ReadStreamCallback
, &context
))
158 // Schedule in run loop to do asynchronous communication with the engine.
159 CFReadStreamScheduleWithRunLoop(readStream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
163 // Open the stream now that it's scheduled on the run loop.
164 if (!CFReadStreamOpen(readStream
))
166 CFStreamError error
= CFReadStreamGetError(readStream
);
167 NSLog(@
"error! %@", error
);
171 // Set the client of the write stream.
172 CFOptionFlags writeFlags
=
173 kCFStreamEventOpenCompleted |
174 kCFStreamEventCanAcceptBytes |
175 kCFStreamEventErrorOccurred |
176 kCFStreamEventEndEncountered
;
177 if (CFWriteStreamSetClient(writeStream
, writeFlags
, WriteStreamCallback
, &context
))
178 // Schedule it in the run loop to receive error information.
179 CFWriteStreamScheduleWithRunLoop(writeStream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
183 // Open the write stream.
184 if (!CFWriteStreamOpen(writeStream
))
186 CFStreamError error
= CFWriteStreamGetError(writeStream
);
187 NSLog(@
"error! %@", error
);
191 connection.readStream
= readStream
;
192 connection.writeStream
= writeStream
;
193 [connection socketDidAccept
];
196 // GDBpConnection //////////////////////////////////////////////////////////////
198 @implementation DebuggerConnection
199 @synthesize socket
= socket_
;
200 @synthesize readStream
= readStream_
;
201 @synthesize lastReadTransaction
= lastReadTransaction_
;
202 @synthesize currentPacket
= currentPacket_
;
203 @synthesize writeStream
= writeStream_
;
204 @synthesize lastWrittenTransaction
= lastWrittenTransaction_
;
205 @synthesize queuedWrites
= queuedWrites_
;
207 @synthesize delegate
;
210 * Creates a new DebuggerConnection and initializes the socket from the given connection
213 - (id)initWithPort
:(NSUInteger
)aPort
215 if (self = [super init
])
220 [[BreakpointManager sharedManager
] setConnection
:self];
228 * Deallocates the object
233 self.currentPacket
= nil;
239 // Getters /////////////////////////////////////////////////////////////////////
243 * Gets the port number
251 * Returns the name of the remote host
253 - (NSString
*)remoteHost
257 return @
"(DISCONNECTED)";
259 // TODO: Either impl or remove.
264 * Returns whether or not we have an active connection
271 // Commands ////////////////////////////////////////////////////////////////////
272 #pragma mark Commands
275 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
276 * created every time you want to debug a page
281 self.status
= @
"Connecting";
286 * Creates an entirely new stack and returns it as an array of StackFrame objects.
288 - (NSArray
*)getCurrentStack
290 NSMutableArray
* stack
= [NSMutableArray array
];
291 NSLog(@
"NOTIMPLEMENTED(): %s", _cmd
);
296 * Tells the debugger to continue running the script. Returns the current stack frame.
300 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"run"];
304 * Tells the debugger to step into the current command.
308 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"step_into"];
312 * Tells the debugger to step out of the current context
316 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"step_out"];
320 * Tells the debugger to step over the current function
324 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"step_over"];
328 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
329 * that requested it so that the child can be attached.
331 - (NSInteger
)getProperty
:(NSString
*)property
333 [self sendCommandWithCallback
:@selector(propertiesReceived
:) format
:@
"property_get -n \"%@\"", property
];
336 // Breakpoint Management ///////////////////////////////////////////////////////
337 #pragma mark Breakpoints
340 * Send an add breakpoint command
342 - (void)addBreakpoint
:(Breakpoint
*)bp
347 NSString
* file
= [self escapedURIPath
:[bp transformedPath
]];
348 NSNumber
* transaction
= [self sendCommandWithCallback
:@selector(breakpointReceived
:)
349 format
:@
"breakpoint_set -t line -f %@ -n %i", file
, [bp line
]];
350 [callbackContext_ setObject
:bp forKey
:transaction
];
354 * Removes a breakpoint
356 - (void)removeBreakpoint
:(Breakpoint
*)bp
363 [self sendCommandWithCallback
:nil format
:@
"breakpoint_remove -d %i", [bp debuggerId
]];
367 // Socket and Stream Callbacks /////////////////////////////////////////////////
368 #pragma mark Callbacks
371 * Called by SocketWrapper after the connection is successful. This immediately calls
372 * -[SocketWrapper receive] to clear the way for communication, though the information
373 * could be useful server information that we don't use right now.
375 - (void)socketDidAccept
379 stackFrames_
= [[NSMutableDictionary alloc
] init
];
380 self.queuedWrites
= [NSMutableArray array
];
381 writeQueueLock_
= [NSRecursiveLock
new];
382 callTable_
= [NSMutableDictionary
new];
383 callbackContext_
= [NSMutableDictionary
new];
387 * Receives errors from the SocketWrapper and updates the display
389 - (void)errorEncountered
:(NSString
*)error
391 [delegate errorEncountered
:error
];
395 * Creates, connects to, and schedules a CFSocket.
399 // Pass ourselves to the callback so we don't have to use ugly globals.
400 CFSocketContext context
;
403 context.retain
= NULL
;
404 context.release
= NULL
;
405 context.copyDescription
= NULL
;
407 // Create the address structure.
408 struct sockaddr_in address
;
409 memset(&address
, 0, sizeof(address
));
410 address.sin_len
= sizeof(address
);
411 address.sin_family
= AF_INET
;
412 address.sin_port
= htons(port
);
413 address.sin_addr.s_addr
= htonl(INADDR_ANY
);
415 // Create the socket signature.
416 CFSocketSignature signature
;
417 signature.protocolFamily
= PF_INET
;
418 signature.socketType
= SOCK_STREAM
;
419 signature.protocol
= IPPROTO_TCP
;
420 signature.address
= (CFDataRef
)[NSData dataWithBytes
:&address length
:sizeof(address
)];
422 socket_
= CFSocketCreateWithSocketSignature(kCFAllocatorDefault
,
423 &signature
, // Socket signature.
424 kCFSocketAcceptCallBack
, // Callback types.
425 SocketAcceptCallback
, // Callout function pointer.
426 &context
); // Context to pass to callout.
429 [self errorEncountered
:@
"Could not open socket."];
433 // Allow old, yet-to-be recycled sockets to be reused.
435 setsockopt(CFSocketGetNative(socket_
), SOL_SOCKET
, SO_REUSEADDR
, &yes
, sizeof(BOOL));
437 // Schedule the socket on the run loop.
438 CFRunLoopSourceRef source
= CFSocketCreateRunLoopSource(kCFAllocatorDefault
, socket_
, 0);
439 CFRunLoopAddSource(CFRunLoopGetCurrent(), source
, kCFRunLoopCommonModes
);
442 self.status
= @
"Connecting";
446 * Closes a socket and releases the ref.
450 // The socket goes down, so do the streams, which clean themselves up.
451 CFSocketInvalidate(socket_
);
453 [stackFrames_ release
];
454 self.queuedWrites
= nil;
455 [writeQueueLock_ release
];
456 [callTable_ release
];
457 [callbackContext_ release
];
461 * Notification that the socket disconnected.
463 - (void)socketDisconnected
466 [delegate debuggerDisconnected
];
470 * Callback from the CFReadStream that there is data waiting to be read.
472 - (void)readStreamHasData
475 CFIndex bytesRead
= CFReadStreamRead(readStream_
, buffer
, 1024);
476 const char* charBuffer
= (const char*)buffer
;
478 // We haven't finished reading a packet, so just read more data in.
479 if (currentPacketIndex_
< packetSize_
)
481 currentPacketIndex_
+= bytesRead
;
482 CFStringRef bufferString
= CFStringCreateWithBytes(kCFAllocatorDefault
,
485 kCFStringEncodingUTF8
,
487 [self.currentPacket appendString
:(NSString
*)bufferString
];
488 CFRelease(bufferString
);
490 // Time to read a new packet.
493 // Read the message header: the size.
494 packetSize_
= atoi(charBuffer
);
495 currentPacketIndex_
= bytesRead
- strlen(charBuffer
);
496 CFStringRef bufferString
= CFStringCreateWithBytes(kCFAllocatorDefault
,
497 buffer
+ strlen(charBuffer
) + 1,
498 bytesRead
- strlen(charBuffer
) - 1,
499 kCFStringEncodingUTF8
,
501 self.currentPacket
= [NSMutableString stringWithString
:(NSString
*)bufferString
];
502 CFRelease(bufferString
);
505 // We have finished reading the packet.
506 if (currentPacketIndex_
>= packetSize_
)
509 currentPacketIndex_
= 0;
511 // Test if we can convert it into an NSXMLDocument.
512 NSError
* error
= nil;
513 NSXMLDocument
* xmlTest
= [[NSXMLDocument alloc
] initWithXMLString
:currentPacket_ options
:NSXMLDocumentTidyXML error
:&error
];
516 NSLog(@
"Could not parse XML? --- %@", error
);
517 NSLog(@
"Error UserInfo: %@", [error userInfo
]);
518 NSLog(@
"This is the XML Document: %@", currentPacket_
);
520 // We do not want to starve the write queue, so manually parse out the
522 NSRange location
= [currentPacket_ rangeOfString
:@
"transaction_id"];
523 if (location.location
!= NSNotFound
)
525 NSUInteger start
= location.location
+ location.length
;
526 NSUInteger end
= start
;
528 NSCharacterSet
* numericSet
= [NSCharacterSet decimalDigitCharacterSet
];
530 // Loop over the characters after the attribute name to extract the ID.
531 while (end
< [currentPacket_ length
])
533 unichar c
= [currentPacket_ characterAtIndex
:end
];
534 if ([numericSet characterIsMember
:c
])
536 // If this character is numeric, extend the range to substring.
543 // If this character is nonnumeric and we have nothing in the
544 // range, skip this character.
550 // We've moved past the numeric ID so we should stop searching.
556 // If we were able to extract the transaction ID, update the last read.
557 NSRange substringRange
= NSMakeRange(start
, end
- start
);
558 NSString
* transaction
= [currentPacket_ substringWithRange
:substringRange
];
559 if ([transaction length
])
561 lastReadTransaction_
= [transaction intValue
];
566 // Otherwise, assume +1 and hope it works.
567 ++lastReadTransaction_
;
570 [self handleResponse
:[xmlTest autorelease
]];
575 * Writes a command into the write stream. If the stream is ready for writing,
576 * we do so immediately. If not, the command is queued and will be written
577 * when the stream is ready.
579 - (void)send
:(NSString
*)command
581 if (lastReadTransaction_
>= lastWrittenTransaction_
&& CFWriteStreamCanAcceptBytes(writeStream_
))
582 [self performSend
:command
];
584 [queuedWrites_ addObject
:command
];
588 * This performs a blocking send. This should ONLY be called when we know we
589 * have write access to the stream. We will busy wait in case we don't do a full
592 - (void)performSend
:(NSString
*)command
596 char* string
= (char*)[command UTF8String
];
597 int stringLength
= strlen(string
);
599 // Log the command if TransportDebug is enabled.
600 if ([[[[NSProcessInfo processInfo
] environment
] objectForKey
:@
"TransportDebug"] boolValue
])
601 NSLog(@
"--> %@", command
);
603 // Busy wait while writing. BAADD. Should background this operation.
606 if (CFWriteStreamCanAcceptBytes(writeStream_
))
608 // Include the NULL byte in the string when we write.
609 int bytesWritten
= CFWriteStreamWrite(writeStream_
, (UInt8
*)string
, stringLength
+ 1);
610 if (bytesWritten
< 0)
612 NSLog(@
"write error");
615 else if (bytesWritten
< strlen(string
))
617 // Adjust the buffer and wait for another chance to write.
618 stringLength
-= bytesWritten
;
619 memmove(string
, string
+ bytesWritten
, stringLength
);
625 // We need to scan the string to find the transactionID.
626 NSRange occurrence
= [command rangeOfString
:@
"-i "];
627 if (occurrence.location
== NSNotFound
)
629 NSLog(@
"sent %@ without a transaction ID", command
);
632 NSString
* transaction
= [command substringFromIndex
:occurrence.location
+ occurrence.length
];
633 lastWrittenTransaction_
= [transaction intValue
];
634 NSLog(@
"command = %@", command
);
635 NSLog(@
"read=%d, write=%d", lastReadTransaction_
, lastWrittenTransaction_
);
641 - (void)handleResponse
:(NSXMLDocument
*)response
643 // Check and see if there's an error.
644 NSArray
* error
= [[response rootElement
] elementsForName
:@
"error"];
645 if ([error count
] > 0)
647 NSLog(@
"Xdebug error: %@", error
);
648 [delegate errorEncountered
:[[[[error objectAtIndex
:0] children
] objectAtIndex
:0] stringValue
]];
651 // If TransportDebug is enabled, log the response.
652 if ([[[[NSProcessInfo processInfo
] environment
] objectForKey
:@
"TransportDebug"] boolValue
])
653 NSLog(@
"<-- %@", response
);
655 // Get the name of the command from the engine's response.
656 NSInteger transaction
= [self transactionIDFromResponse
:response
];
657 if (transaction
< lastReadTransaction_
)
658 NSLog(@
"out of date transaction %@", response
);
660 if (transaction
!= lastWrittenTransaction_
)
661 NSLog(@
"txn doesn't match last written %@", response
);
663 lastReadTransaction_
= transaction
;
664 NSLog(@
"read=%d, write=%d", lastReadTransaction_
, lastWrittenTransaction_
);
666 if ([[[response rootElement
] name
] isEqualToString
:@
"init"])
668 [self initReceived
:response
];
672 NSString
* callbackStr
= [callTable_ objectForKey
:[NSNumber numberWithInt
:lastReadTransaction_
]];
675 SEL callback
= NSSelectorFromString(callbackStr
);
676 [self performSelector
:callback withObject
:response
];
679 [self sendQueuedWrites
];
682 // Specific Response Handlers //////////////////////////////////////////////////
683 #pragma mark Response Handlers
686 * Initial packet received. We've started a brand-new connection to the engine.
688 - (void)initReceived
:(NSXMLDocument
*)response
690 // Register any breakpoints that exist offline.
691 for (Breakpoint
* bp
in [[BreakpointManager sharedManager
] breakpoints
])
692 [self addBreakpoint
:bp
];
694 // Load the debugger to make it look active.
695 [delegate debuggerConnected
];
697 // TODO: update the status.
701 * Receiver for status updates. This just freshens up the UI.
703 - (void)updateStatus
:(NSXMLDocument
*)response
705 self.status
= [[[[response rootElement
] attributeForName
:@
"status"] stringValue
] capitalizedString
];
706 if (status
== nil ||
[status isEqualToString
:@
"Stopped"] ||
[status isEqualToString
:@
"Stopping"])
710 [delegate debuggerDisconnected
];
712 self.status
= @
"Stopped";
717 * Step in/out/over and run all take this path. We first get the status of the
718 * debugger and then request fresh stack information.
720 - (void)debuggerStep
:(NSXMLDocument
*)response
722 [self sendCommandWithCallback
:@selector(updateStatus
:) format
:@
"status"];
723 NSString
* command
= [[[response rootElement
] attributeForName
:@
"command"] stringValue
];
725 // If this is the run command, tell the delegate that a bunch of updates
726 // are coming. Also remove all existing stack routes and request a new stack.
727 // TODO: figure out if we can not clobber the stack every time.
728 if (YES ||
[command isEqualToString
:@
"run"])
730 if ([delegate respondsToSelector
:@selector(clobberStack
)])
731 [delegate clobberStack
];
732 [stackFrames_ removeAllObjects
];
733 stackFirstTransactionID_
= [[self sendCommandWithCallback
:@selector(rebuildStack
:) format
:@
"stack_depth"] intValue
];
738 * We ask for the stack_depth and now we clobber the stack and start rebuilding
741 - (void)rebuildStack
:(NSXMLDocument
*)response
743 NSInteger depth
= [[[[response rootElement
] attributeForName
:@
"depth"] stringValue
] intValue
];
745 if (stackFirstTransactionID_
== [self transactionIDFromResponse
:response
])
748 // We now need to alloc a bunch of stack frames and get the basic information
750 for (NSInteger i
= 0; i
< depth
; i
++)
752 // Use the transaction ID to create a routing path.
753 NSNumber
* routingID
= [self sendCommandWithCallback
:@selector(getStackFrame
:) format
:@
"stack_get -d %d", i
];
754 [stackFrames_ setObject
:[StackFrame alloc
] forKey
:routingID
];
759 * The initial rebuild of the stack frame. We now have enough to initialize
760 * a StackFrame object.
762 - (void)getStackFrame
:(NSXMLDocument
*)response
764 // Get the routing information.
765 NSInteger routingID
= [self transactionIDFromResponse
:response
];
766 if (routingID
< stackFirstTransactionID_
)
768 NSNumber
* routingNumber
= [NSNumber numberWithInt
:routingID
];
770 // Make sure we initialized this frame in our last |-rebuildStack:|.
771 StackFrame
* frame
= [stackFrames_ objectForKey
:routingNumber
];
775 NSXMLElement
* xmlframe
= [[[response rootElement
] children
] objectAtIndex
:0];
777 // Initialize the stack frame.
778 [frame initWithIndex
:[[[xmlframe attributeForName
:@
"level"] stringValue
] intValue
]
779 withFilename
:[[xmlframe attributeForName
:@
"filename"] stringValue
]
781 atLine
:[[[xmlframe attributeForName
:@
"lineno"] stringValue
] intValue
]
782 inFunction
:[[xmlframe attributeForName
:@
"where"] stringValue
]
785 // Get the source code of the file. Escape % in URL chars.
786 NSString
* escapedFilename
= [frame.filename stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
787 NSNumber
* transaction
= [self sendCommandWithCallback
:@selector(setSource
:) format
:@
"source -f %@", escapedFilename
];
788 [callbackContext_ setObject
:routingNumber forKey
:transaction
];
790 // Get the names of all the contexts.
791 transaction
= [self sendCommandWithCallback
:@selector(contextsReceived
:) format
:@
"context_names -d %d", frame.index
];
792 [callbackContext_ setObject
:routingNumber forKey
:transaction
];
794 if ([delegate respondsToSelector
:@selector(newStackFrame
:)])
795 [delegate newStackFrame
:frame
];
799 * Callback for setting the source of a file while rebuilding a specific stack
802 - (void)setSource
:(NSXMLDocument
*)response
804 NSNumber
* transaction
= [NSNumber numberWithInt
:[self transactionIDFromResponse
:response
]];
805 if ([transaction intValue
] < stackFirstTransactionID_
)
807 NSNumber
* routingNumber
= [callbackContext_ objectForKey
:transaction
];
811 [callbackContext_ removeObjectForKey
:transaction
];
812 StackFrame
* frame
= [stackFrames_ objectForKey
:routingNumber
];
816 frame.source
= [[response rootElement
] value
];
818 if ([delegate respondsToSelector
:@selector(sourceUpdated
:)])
819 [delegate sourceUpdated
:frame
];
823 * Enumerates all the contexts of a given stack frame. We then in turn get the
824 * contents of each one of these contexts.
826 - (void)contextsReceived
:(NSXMLDocument
*)response
828 // Get the stack frame's routing ID and use it again.
829 NSNumber
* receivedTransaction
= [NSNumber numberWithInt
:[self transactionIDFromResponse
:response
]];
830 if ([receivedTransaction intValue
] < stackFirstTransactionID_
)
832 NSNumber
* routingID
= [callbackContext_ objectForKey
:receivedTransaction
];
836 // Get the stack frame by the |routingID|.
837 StackFrame
* frame
= [stackFrames_ objectForKey
:routingID
];
839 NSXMLElement
* contextNames
= [response rootElement
];
840 for (NSXMLElement
* context
in [contextNames children
])
842 NSInteger cid
= [[[context attributeForName
:@
"id"] stringValue
] intValue
];
844 // Fetch each context's variables.
845 NSNumber
* transaction
= [self sendCommandWithCallback
:@selector(variablesReceived
:)
846 format
:@
"context_get -d %d -c %d", frame.index
, cid
];
847 [callbackContext_ setObject
:routingID forKey
:transaction
];
852 * Receives the variables from the context and attaches them to the stack frame.
854 - (void)variablesReceived
:(NSXMLDocument
*)response
856 // Get the stack frame's routing ID and use it again.
857 NSInteger transaction
= [self transactionIDFromResponse
:response
];
858 if (transaction
< stackFirstTransactionID_
)
860 NSNumber
* receivedTransaction
= [NSNumber numberWithInt
:transaction
];
861 NSNumber
* routingID
= [callbackContext_ objectForKey
:receivedTransaction
];
865 // Get the stack frame by the |routingID|.
866 StackFrame
* frame
= [stackFrames_ objectForKey
:routingID
];
868 NSMutableArray
* variables
= [NSMutableArray array
];
870 // Merge the frame's existing variables.
872 [variables addObjectsFromArray
:frame.variables
];
874 // Add these new variables.
875 NSArray
* addVariables
= [[response rootElement
] children
];
877 [variables addObjectsFromArray
:addVariables
];
879 frame.variables
= variables
;
883 * Callback from a |-getProperty:| request.
885 - (void)propertiesReceived
:(NSXMLDocument
*)response
887 NSInteger transaction
= [self transactionIDFromResponse
:response
];
891 <property> <!-- this is the one we requested -->
892 <property ... /> <!-- these are what we want -->
897 // Detach all the children so we can insert them into another document.
898 NSXMLElement
* parent
= (NSXMLElement
*)[[response rootElement
] childAtIndex
:0];
899 NSArray
* children
= [parent children
];
900 [parent setChildren
:nil];
902 [delegate receivedProperties
:children forTransaction
:transaction
];
906 * Callback for setting a breakpoint.
908 - (void)breakpointReceived
:(NSXMLDocument
*)response
910 NSNumber
* transaction
= [NSNumber numberWithInt
:[self transactionIDFromResponse
:response
]];
911 Breakpoint
* bp
= [callbackContext_ objectForKey
:transaction
];
915 [callbackContext_ removeObjectForKey
:callbackContext_
];
916 [bp setDebuggerId
:[[[[response rootElement
] attributeForName
:@
"id"] stringValue
] intValue
]];
922 * This will send a command to the debugger engine. It will append the
923 * transaction ID automatically. It accepts a NSString command along with a
924 * a variable number of arguments to substitute into the command, a la
925 * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
927 - (NSNumber
*)sendCommandWithCallback
:(SEL)callback format
:(NSString
*)format
, ...
929 // Collect varargs and format command.
931 va_start(args
, format
);
932 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
935 NSNumber
* callbackKey
= [NSNumber numberWithInt
:transactionID
++];
937 [callTable_ setObject
:NSStringFromSelector(callback
) forKey
:callbackKey
];
939 [self send
:[NSString stringWithFormat
:@
"%@ -i %@", [command autorelease
], callbackKey
]];
945 * Checks if there are unsent commands in the |queuedWrites_| queue and sends
946 * them if it's OK to do so. This will not block.
948 - (void)sendQueuedWrites
950 [writeQueueLock_ lock
];
951 if (lastReadTransaction_
>= lastWrittenTransaction_
&& [queuedWrites_ count
] > 0)
953 NSString
* command
= [queuedWrites_ objectAtIndex
:0];
954 NSLog(@
"Sending queued write: %@", command
);
956 // We don't want to block because this is called from the main thread.
957 // |-performSend:| busy waits when the stream is not ready. Bail out
958 // before we do that becuase busy waiting is BAD.
959 if (CFWriteStreamCanAcceptBytes(writeStream_
))
961 [self performSend
:command
];
962 [queuedWrites_ removeObjectAtIndex
:0];
965 [writeQueueLock_ unlock
];
969 * Given a file path, this returns a file:// URI and escapes any spaces for the
972 - (NSString
*)escapedURIPath
:(NSString
*)path
974 // Custon GDBp paths are fine.
975 if ([[path substringToIndex
:4] isEqualToString
:@
"gdbp"])
978 // Create a temporary URL that will escape all the nasty characters.
979 NSURL
* url
= [NSURL fileURLWithPath
:path
];
980 NSString
* urlString
= [url absoluteString
];
982 // Remove the host because this is a file:// URL;
983 urlString
= [urlString stringByReplacingOccurrencesOfString
:[url host
] withString
:@
""];
985 // Escape % for use in printf-style NSString formatters.
986 urlString
= [urlString stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
991 * Returns the transaction_id from an NSXMLDocument.
993 - (NSInteger
)transactionIDFromResponse
:(NSXMLDocument
*)response
995 return [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];