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 "DebuggerProcessor.h"
22 #import "AppDelegate.h"
23 #import "LoggingController.h"
25 // GDBpConnection (Private) ////////////////////////////////////////////////////
27 @interface DebuggerProcessor ()
28 @property (readwrite
, copy
) NSString
* status
;
29 @property (assign
) CFSocketRef socket
;
30 @property (assign
) CFReadStreamRef readStream
;
31 @property NSUInteger lastReadTransaction
;
32 @property (retain
) NSMutableString
* currentPacket
;
33 @property (assign
) CFWriteStreamRef writeStream
;
34 @property NSUInteger lastWrittenTransaction
;
35 @property (retain
) NSMutableArray
* queuedWrites
;
39 - (void)socketDidAccept
;
40 - (void)socketDisconnected
;
41 - (void)readStreamHasData
;
42 - (void)send
:(NSString
*)command
;
43 - (void)performSend
:(NSString
*)command
;
44 - (void)errorEncountered
:(NSString
*)error
;
46 - (void)handleResponse
:(NSXMLDocument
*)response
;
47 - (void)handlePacket
:(NSString
*)packet
;
49 - (void)initReceived
:(NSXMLDocument
*)response
;
50 - (void)updateStatus
:(NSXMLDocument
*)response
;
51 - (void)debuggerStep
:(NSXMLDocument
*)response
;
52 - (void)rebuildStack
:(NSXMLDocument
*)response
;
53 - (void)getStackFrame
:(NSXMLDocument
*)response
;
54 - (void)setSource
:(NSXMLDocument
*)response
;
55 - (void)contextsReceived
:(NSXMLDocument
*)response
;
56 - (void)variablesReceived
:(NSXMLDocument
*)response
;
57 - (void)propertiesReceived
:(NSXMLDocument
*)response
;
59 - (NSNumber
*)sendCommandWithCallback
:(SEL)callback format
:(NSString
*)format
, ...
;
61 - (void)sendQueuedWrites
;
63 - (NSString
*)escapedURIPath
:(NSString
*)path
;
64 - (NSInteger
)transactionIDFromResponse
:(NSXMLDocument
*)response
;
65 - (NSInteger
)transactionIDFromCommand
:(NSString
*)command
;
68 // CFNetwork Callbacks /////////////////////////////////////////////////////////
70 void ReadStreamCallback(CFReadStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
72 DebuggerProcessor
* connection
= (DebuggerProcessor
*)connectionRaw
;
75 case kCFStreamEventHasBytesAvailable
:
76 [connection readStreamHasData
];
79 case kCFStreamEventErrorOccurred
:
81 CFErrorRef error
= CFReadStreamCopyError(stream
);
82 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
83 CFReadStreamClose(stream
);
85 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
89 case kCFStreamEventEndEncountered
:
90 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
91 CFReadStreamClose(stream
);
93 [connection socketDisconnected
];
98 void WriteStreamCallback(CFWriteStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
100 DebuggerProcessor
* connection
= (DebuggerProcessor
*)connectionRaw
;
103 case kCFStreamEventCanAcceptBytes
:
104 [connection sendQueuedWrites
];
107 case kCFStreamEventErrorOccurred
:
109 CFErrorRef error
= CFWriteStreamCopyError(stream
);
110 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
111 CFWriteStreamClose(stream
);
113 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
117 case kCFStreamEventEndEncountered
:
118 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
119 CFWriteStreamClose(stream
);
121 [connection socketDisconnected
];
126 void SocketAcceptCallback(CFSocketRef socket
,
127 CFSocketCallBackType callbackType
,
132 assert(callbackType
== kCFSocketAcceptCallBack
);
133 NSLog(@
"SocketAcceptCallback()");
135 DebuggerProcessor
* connection
= (DebuggerProcessor
*)connectionRaw
;
137 CFReadStreamRef readStream
;
138 CFWriteStreamRef writeStream
;
140 // Create the streams on the socket.
141 CFStreamCreatePairWithSocket(kCFAllocatorDefault
,
142 *(CFSocketNativeHandle
*)data
, // Socket handle.
143 &readStream
, // Read stream in-pointer.
144 &writeStream
); // Write stream in-pointer.
146 // Create struct to register callbacks for the stream.
147 CFStreamClientContext context
;
149 context.info
= connection
;
150 context.retain
= NULL
;
151 context.release
= NULL
;
152 context.copyDescription
= NULL
;
154 // Set the client of the read stream.
155 CFOptionFlags readFlags
=
156 kCFStreamEventOpenCompleted |
157 kCFStreamEventHasBytesAvailable |
158 kCFStreamEventErrorOccurred |
159 kCFStreamEventEndEncountered
;
160 if (CFReadStreamSetClient(readStream
, readFlags
, ReadStreamCallback
, &context
))
161 // Schedule in run loop to do asynchronous communication with the engine.
162 CFReadStreamScheduleWithRunLoop(readStream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
166 // Open the stream now that it's scheduled on the run loop.
167 if (!CFReadStreamOpen(readStream
))
169 CFStreamError error
= CFReadStreamGetError(readStream
);
170 NSLog(@
"error! %@", error
);
174 // Set the client of the write stream.
175 CFOptionFlags writeFlags
=
176 kCFStreamEventOpenCompleted |
177 kCFStreamEventCanAcceptBytes |
178 kCFStreamEventErrorOccurred |
179 kCFStreamEventEndEncountered
;
180 if (CFWriteStreamSetClient(writeStream
, writeFlags
, WriteStreamCallback
, &context
))
181 // Schedule it in the run loop to receive error information.
182 CFWriteStreamScheduleWithRunLoop(writeStream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
186 // Open the write stream.
187 if (!CFWriteStreamOpen(writeStream
))
189 CFStreamError error
= CFWriteStreamGetError(writeStream
);
190 NSLog(@
"error! %@", error
);
194 connection.readStream
= readStream
;
195 connection.writeStream
= writeStream
;
196 [connection socketDidAccept
];
199 // GDBpConnection //////////////////////////////////////////////////////////////
201 @implementation DebuggerProcessor
202 @synthesize socket
= socket_
;
203 @synthesize readStream
= readStream_
;
204 @synthesize lastReadTransaction
= lastReadTransaction_
;
205 @synthesize currentPacket
= currentPacket_
;
206 @synthesize writeStream
= writeStream_
;
207 @synthesize lastWrittenTransaction
= lastWrittenTransaction_
;
208 @synthesize queuedWrites
= queuedWrites_
;
210 @synthesize delegate
;
213 * Creates a new DebuggerConnection and initializes the socket from the given connection
216 - (id)initWithPort
:(NSUInteger
)aPort
218 if (self = [super init
])
223 [[BreakpointManager sharedManager
] setConnection
:self];
230 * Deallocates the object
235 self.currentPacket
= nil;
241 // Getters /////////////////////////////////////////////////////////////////////
245 * Gets the port number
253 * Returns the name of the remote host
255 - (NSString
*)remoteHost
259 return @
"(DISCONNECTED)";
261 // TODO: Either impl or remove.
266 * Returns whether or not we have an active connection
273 // Commands ////////////////////////////////////////////////////////////////////
274 #pragma mark Commands
277 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
278 * created every time you want to debug a page
283 self.status
= @
"Connecting";
288 * Tells the debugger to continue running the script. Returns the current stack frame.
292 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"run"];
296 * Tells the debugger to step into the current command.
300 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"step_into"];
304 * Tells the debugger to step out of the current context
308 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"step_out"];
312 * Tells the debugger to step over the current function
316 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"step_over"];
320 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
321 * that requested it so that the child can be attached.
323 - (NSInteger
)getProperty
:(NSString
*)property
325 [self sendCommandWithCallback
:@selector(propertiesReceived
:) format
:@
"property_get -n \"%@\"", property
];
328 // Breakpoint Management ///////////////////////////////////////////////////////
329 #pragma mark Breakpoints
332 * Send an add breakpoint command
334 - (void)addBreakpoint
:(Breakpoint
*)bp
339 NSString
* file
= [self escapedURIPath
:[bp transformedPath
]];
340 NSNumber
* transaction
= [self sendCommandWithCallback
:@selector(breakpointReceived
:)
341 format
:@
"breakpoint_set -t line -f %@ -n %i", file
, [bp line
]];
342 [callbackContext_ setObject
:bp forKey
:transaction
];
346 * Removes a breakpoint
348 - (void)removeBreakpoint
:(Breakpoint
*)bp
355 [self sendCommandWithCallback
:nil format
:@
"breakpoint_remove -d %i", [bp debuggerId
]];
359 // Socket and Stream Callbacks /////////////////////////////////////////////////
360 #pragma mark Callbacks
363 * Called by SocketWrapper after the connection is successful. This immediately calls
364 * -[SocketWrapper receive] to clear the way for communication, though the information
365 * could be useful server information that we don't use right now.
367 - (void)socketDidAccept
371 stackFrames_
= [[NSMutableDictionary alloc
] init
];
372 self.queuedWrites
= [NSMutableArray array
];
373 writeQueueLock_
= [NSRecursiveLock
new];
374 callTable_
= [NSMutableDictionary
new];
375 callbackContext_
= [NSMutableDictionary
new];
379 * Receives errors from the SocketWrapper and updates the display
381 - (void)errorEncountered
:(NSString
*)error
383 [delegate errorEncountered
:error
];
387 * Creates, connects to, and schedules a CFSocket.
391 // Pass ourselves to the callback so we don't have to use ugly globals.
392 CFSocketContext context
;
395 context.retain
= NULL
;
396 context.release
= NULL
;
397 context.copyDescription
= NULL
;
399 // Create the address structure.
400 struct sockaddr_in address
;
401 memset(&address
, 0, sizeof(address
));
402 address.sin_len
= sizeof(address
);
403 address.sin_family
= AF_INET
;
404 address.sin_port
= htons(port
);
405 address.sin_addr.s_addr
= htonl(INADDR_ANY
);
407 // Create the socket signature.
408 CFSocketSignature signature
;
409 signature.protocolFamily
= PF_INET
;
410 signature.socketType
= SOCK_STREAM
;
411 signature.protocol
= IPPROTO_TCP
;
412 signature.address
= (CFDataRef
)[NSData dataWithBytes
:&address length
:sizeof(address
)];
414 socket_
= CFSocketCreateWithSocketSignature(kCFAllocatorDefault
,
415 &signature
, // Socket signature.
416 kCFSocketAcceptCallBack
, // Callback types.
417 SocketAcceptCallback
, // Callout function pointer.
418 &context
); // Context to pass to callout.
421 [self errorEncountered
:@
"Could not open socket."];
425 // Allow old, yet-to-be recycled sockets to be reused.
427 setsockopt(CFSocketGetNative(socket_
), SOL_SOCKET
, SO_REUSEADDR
, &yes
, sizeof(BOOL));
429 // Schedule the socket on the run loop.
430 CFRunLoopSourceRef source
= CFSocketCreateRunLoopSource(kCFAllocatorDefault
, socket_
, 0);
431 CFRunLoopAddSource(CFRunLoopGetCurrent(), source
, kCFRunLoopCommonModes
);
434 self.status
= @
"Connecting";
438 * Closes a socket and releases the ref.
442 // The socket goes down, so do the streams, which clean themselves up.
443 CFSocketInvalidate(socket_
);
445 [stackFrames_ release
];
446 self.queuedWrites
= nil;
447 [writeQueueLock_ release
];
448 [callTable_ release
];
449 [callbackContext_ release
];
453 * Notification that the socket disconnected.
455 - (void)socketDisconnected
458 [delegate debuggerDisconnected
];
462 * Callback from the CFReadStream that there is data waiting to be read.
464 - (void)readStreamHasData
466 const NSUInteger kBufferSize
= 1024;
467 UInt8 buffer
[kBufferSize
];
468 CFIndex bufferOffset
= 0; // Starting point in |buffer| to work with.
469 CFIndex bytesRead
= CFReadStreamRead(readStream_
, buffer
, kBufferSize
);
470 const char* charBuffer
= (const char*)buffer
;
472 // The read loop works by going through the buffer until all the bytes have
474 while (bufferOffset
< bytesRead
)
476 // Find the NULL separator, or the end of the string.
477 NSUInteger partLength
= 0;
478 for (NSUInteger i
= bufferOffset
; i
< bytesRead
&& charBuffer
[i
] != '\0'; ++i
, ++partLength
) ;
480 // If there is not a current packet, set some state.
481 if (!self.currentPacket
)
483 // Read the message header: the size. This will be |partLength| bytes.
484 packetSize_
= atoi(charBuffer
+ bufferOffset
);
485 currentPacketIndex_
= 0;
486 self.currentPacket
= [NSMutableString stringWithCapacity
:packetSize_
];
487 bufferOffset
+= partLength
+ 1; // Pass over the NULL byte.
488 continue; // Spin the loop to begin reading actual data.
491 // Substring the byte stream and append it to the packet string.
492 CFStringRef bufferString
= CFStringCreateWithBytes(kCFAllocatorDefault
,
493 buffer
+ bufferOffset
, // Byte pointer, offset by start index.
494 partLength
, // Length.
495 kCFStringEncodingUTF8
,
497 [self.currentPacket appendString
:(NSString
*)bufferString
];
498 CFRelease(bufferString
);
501 currentPacketIndex_
+= partLength
;
502 bufferOffset
+= partLength
+ 1;
504 // If this read finished the packet, handle it and reset.
505 NSLog(@
"cpi %d ps %d br %d ds %d", currentPacketIndex_
, packetSize_
, bytesRead
, partLength
);
506 if (currentPacketIndex_
>= packetSize_
)
508 [self handlePacket
:[[currentPacket_ retain
] autorelease
]];
509 self.currentPacket
= nil;
511 currentPacketIndex_
= 0;
517 * Performs the packet handling of a raw string XML packet. From this point on,
518 * the packets are associated with a transaction and are then dispatched.
520 - (void)handlePacket
:(NSString
*)packet
522 // Test if we can convert it into an NSXMLDocument.
523 NSError
* error
= nil;
524 NSXMLDocument
* xmlTest
= [[NSXMLDocument alloc
] initWithXMLString
:currentPacket_ options
:NSXMLDocumentTidyXML error
:&error
];
526 // Try to recover if we encountered an error.
529 // We do not want to starve the write queue, so manually parse out the
531 NSRange location
= [currentPacket_ rangeOfString
:@
"transaction_id"];
532 if (location.location
!= NSNotFound
)
534 NSUInteger start
= location.location
+ location.length
;
535 NSUInteger end
= start
;
537 NSCharacterSet
* numericSet
= [NSCharacterSet decimalDigitCharacterSet
];
539 // Loop over the characters after the attribute name to extract the ID.
540 while (end
< [currentPacket_ length
])
542 unichar c
= [currentPacket_ characterAtIndex
:end
];
543 if ([numericSet characterIsMember
:c
])
545 // If this character is numeric, extend the range to substring.
552 // If this character is nonnumeric and we have nothing in the
553 // range, skip this character.
559 // We've moved past the numeric ID so we should stop searching.
565 // If we were able to extract the transaction ID, update the last read.
566 NSRange substringRange
= NSMakeRange(start
, end
- start
);
567 NSString
* transactionStr
= [currentPacket_ substringWithRange
:substringRange
];
568 if ([transactionStr length
])
569 lastReadTransaction_
= [transactionStr intValue
];
572 // Otherwise, assume +1 and hope it works.
573 ++lastReadTransaction_
;
577 // See if the transaction can be parsed out.
578 NSInteger transaction
= [self transactionIDFromResponse
:xmlTest
];
579 if (transaction
< lastReadTransaction_
)
581 NSLog(@
"tx = %d vs %d", transaction
, lastReadTransaction_
);
582 NSLog(@
"out of date transaction %@", packet
);
586 if (transaction
!= lastWrittenTransaction_
)
587 NSLog(@
"txn %d <> %d last written, %d last read", transaction
, lastWrittenTransaction_
, lastReadTransaction_
);
589 lastReadTransaction_
= transaction
;
592 // Log this receive event.
593 LoggingController
* logger
= [(AppDelegate
*)[NSApp delegate
] loggingController
];
594 LogEntry
* log
= [logger recordReceive
:currentPacket_
];
596 log.lastWrittenTransactionID
= lastWrittenTransaction_
;
597 log.lastReadTransactionID
= lastReadTransaction_
;
599 // Finally, dispatch the handler for this response.
600 [self handleResponse
:[xmlTest autorelease
]];
604 * Writes a command into the write stream. If the stream is ready for writing,
605 * we do so immediately. If not, the command is queued and will be written
606 * when the stream is ready.
608 - (void)send
:(NSString
*)command
610 if (lastReadTransaction_
>= lastWrittenTransaction_
&& CFWriteStreamCanAcceptBytes(writeStream_
))
611 [self performSend
:command
];
613 [queuedWrites_ addObject
:command
];
614 [self sendQueuedWrites
];
618 * This performs a blocking send. This should ONLY be called when we know we
619 * have write access to the stream. We will busy wait in case we don't do a full
622 - (void)performSend
:(NSString
*)command
624 // If this is an out-of-date transaction, do not bother sending it.
625 NSInteger transaction
= [self transactionIDFromCommand
:command
];
626 if (transaction
!= NSNotFound
&& transaction
< lastWrittenTransaction_
)
631 char* string
= (char*)[command UTF8String
];
632 int stringLength
= strlen(string
);
634 // Busy wait while writing. BAADD. Should background this operation.
637 if (CFWriteStreamCanAcceptBytes(writeStream_
))
639 // Include the NULL byte in the string when we write.
640 int bytesWritten
= CFWriteStreamWrite(writeStream_
, (UInt8
*)string
, stringLength
+ 1);
641 if (bytesWritten
< 0)
643 NSLog(@
"write error");
646 else if (bytesWritten
< strlen(string
))
648 // Adjust the buffer and wait for another chance to write.
649 stringLength
-= bytesWritten
;
650 memmove(string
, string
+ bytesWritten
, stringLength
);
656 // We need to scan the string to find the transactionID.
657 if (transaction
== NSNotFound
)
659 NSLog(@
"sent %@ without a transaction ID", command
);
662 lastWrittenTransaction_
= transaction
;
667 // Log this trancation.
668 LoggingController
* logger
= [(AppDelegate
*)[NSApp delegate
] loggingController
];
669 LogEntry
* log
= [logger recordSend
:command
];
670 log.lastWrittenTransactionID
= lastWrittenTransaction_
;
671 log.lastReadTransactionID
= lastReadTransaction_
;
674 - (void)handleResponse
:(NSXMLDocument
*)response
676 // Check and see if there's an error.
677 NSArray
* error
= [[response rootElement
] elementsForName
:@
"error"];
678 if ([error count
] > 0)
680 NSLog(@
"Xdebug error: %@", error
);
681 [delegate errorEncountered
:[[[[error objectAtIndex
:0] children
] objectAtIndex
:0] stringValue
]];
684 if ([[[response rootElement
] name
] isEqualToString
:@
"init"])
686 [self initReceived
:response
];
690 NSString
* callbackStr
= [callTable_ objectForKey
:[NSNumber numberWithInt
:lastReadTransaction_
]];
693 SEL callback
= NSSelectorFromString(callbackStr
);
694 [self performSelector
:callback withObject
:response
];
697 [self sendQueuedWrites
];
700 // Specific Response Handlers //////////////////////////////////////////////////
701 #pragma mark Response Handlers
704 * Initial packet received. We've started a brand-new connection to the engine.
706 - (void)initReceived
:(NSXMLDocument
*)response
708 // Register any breakpoints that exist offline.
709 for (Breakpoint
* bp
in [[BreakpointManager sharedManager
] breakpoints
])
710 [self addBreakpoint
:bp
];
712 // Load the debugger to make it look active.
713 [delegate debuggerConnected
];
715 // TODO: update the status.
719 * Receiver for status updates. This just freshens up the UI.
721 - (void)updateStatus
:(NSXMLDocument
*)response
723 self.status
= [[[[response rootElement
] attributeForName
:@
"status"] stringValue
] capitalizedString
];
724 if (status
== nil ||
[status isEqualToString
:@
"Stopped"] ||
[status isEqualToString
:@
"Stopping"])
728 [delegate debuggerDisconnected
];
730 self.status
= @
"Stopped";
735 * Step in/out/over and run all take this path. We first get the status of the
736 * debugger and then request fresh stack information.
738 - (void)debuggerStep
:(NSXMLDocument
*)response
740 [self updateStatus
:response
];
744 // If this is the run command, tell the delegate that a bunch of updates
745 // are coming. Also remove all existing stack routes and request a new stack.
746 // TODO: figure out if we can not clobber the stack every time.
747 NSString
* command
= [[[response rootElement
] attributeForName
:@
"command"] stringValue
];
748 if (YES ||
[command isEqualToString
:@
"run"])
750 if ([delegate respondsToSelector
:@selector(clobberStack
)])
751 [delegate clobberStack
];
752 [stackFrames_ removeAllObjects
];
753 stackFirstTransactionID_
= [[self sendCommandWithCallback
:@selector(rebuildStack
:) format
:@
"stack_depth"] intValue
];
758 * We ask for the stack_depth and now we clobber the stack and start rebuilding
761 - (void)rebuildStack
:(NSXMLDocument
*)response
763 NSInteger depth
= [[[[response rootElement
] attributeForName
:@
"depth"] stringValue
] intValue
];
765 if (stackFirstTransactionID_
== [self transactionIDFromResponse
:response
])
768 // We now need to alloc a bunch of stack frames and get the basic information
770 for (NSInteger i
= 0; i
< depth
; i
++)
772 // Use the transaction ID to create a routing path.
773 NSNumber
* routingID
= [self sendCommandWithCallback
:@selector(getStackFrame
:) format
:@
"stack_get -d %d", i
];
774 [stackFrames_ setObject
:[StackFrame alloc
] forKey
:routingID
];
779 * The initial rebuild of the stack frame. We now have enough to initialize
780 * a StackFrame object.
782 - (void)getStackFrame
:(NSXMLDocument
*)response
784 // Get the routing information.
785 NSInteger routingID
= [self transactionIDFromResponse
:response
];
786 if (routingID
< stackFirstTransactionID_
)
788 NSNumber
* routingNumber
= [NSNumber numberWithInt
:routingID
];
790 // Make sure we initialized this frame in our last |-rebuildStack:|.
791 StackFrame
* frame
= [stackFrames_ objectForKey
:routingNumber
];
795 NSXMLElement
* xmlframe
= [[[response rootElement
] children
] objectAtIndex
:0];
797 // Initialize the stack frame.
798 [frame initWithIndex
:[[[xmlframe attributeForName
:@
"level"] stringValue
] intValue
]
799 withFilename
:[[xmlframe attributeForName
:@
"filename"] stringValue
]
801 atLine
:[[[xmlframe attributeForName
:@
"lineno"] stringValue
] intValue
]
802 inFunction
:[[xmlframe attributeForName
:@
"where"] stringValue
]
805 // Get the source code of the file. Escape % in URL chars.
806 NSString
* escapedFilename
= [frame.filename stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
807 NSNumber
* transaction
= [self sendCommandWithCallback
:@selector(setSource
:) format
:@
"source -f %@", escapedFilename
];
808 [callbackContext_ setObject
:routingNumber forKey
:transaction
];
810 // Get the names of all the contexts.
811 transaction
= [self sendCommandWithCallback
:@selector(contextsReceived
:) format
:@
"context_names -d %d", frame.index
];
812 [callbackContext_ setObject
:routingNumber forKey
:transaction
];
814 if ([delegate respondsToSelector
:@selector(newStackFrame
:)])
815 [delegate newStackFrame
:frame
];
819 * Callback for setting the source of a file while rebuilding a specific stack
822 - (void)setSource
:(NSXMLDocument
*)response
824 NSNumber
* transaction
= [NSNumber numberWithInt
:[self transactionIDFromResponse
:response
]];
825 if ([transaction intValue
] < stackFirstTransactionID_
)
827 NSNumber
* routingNumber
= [callbackContext_ objectForKey
:transaction
];
831 [callbackContext_ removeObjectForKey
:transaction
];
832 StackFrame
* frame
= [stackFrames_ objectForKey
:routingNumber
];
836 frame.source
= [[response rootElement
] value
];
838 if ([delegate respondsToSelector
:@selector(sourceUpdated
:)])
839 [delegate sourceUpdated
:frame
];
843 * Enumerates all the contexts of a given stack frame. We then in turn get the
844 * contents of each one of these contexts.
846 - (void)contextsReceived
:(NSXMLDocument
*)response
848 // Get the stack frame's routing ID and use it again.
849 NSNumber
* receivedTransaction
= [NSNumber numberWithInt
:[self transactionIDFromResponse
:response
]];
850 if ([receivedTransaction intValue
] < stackFirstTransactionID_
)
852 NSNumber
* routingID
= [callbackContext_ objectForKey
:receivedTransaction
];
856 // Get the stack frame by the |routingID|.
857 StackFrame
* frame
= [stackFrames_ objectForKey
:routingID
];
859 NSXMLElement
* contextNames
= [response rootElement
];
860 for (NSXMLElement
* context
in [contextNames children
])
862 NSInteger cid
= [[[context attributeForName
:@
"id"] stringValue
] intValue
];
864 // Fetch each context's variables.
865 NSNumber
* transaction
= [self sendCommandWithCallback
:@selector(variablesReceived
:)
866 format
:@
"context_get -d %d -c %d", frame.index
, cid
];
867 [callbackContext_ setObject
:routingID forKey
:transaction
];
872 * Receives the variables from the context and attaches them to the stack frame.
874 - (void)variablesReceived
:(NSXMLDocument
*)response
876 // Get the stack frame's routing ID and use it again.
877 NSInteger transaction
= [self transactionIDFromResponse
:response
];
878 if (transaction
< stackFirstTransactionID_
)
880 NSNumber
* receivedTransaction
= [NSNumber numberWithInt
:transaction
];
881 NSNumber
* routingID
= [callbackContext_ objectForKey
:receivedTransaction
];
885 // Get the stack frame by the |routingID|.
886 StackFrame
* frame
= [stackFrames_ objectForKey
:routingID
];
888 NSMutableArray
* variables
= [NSMutableArray array
];
890 // Merge the frame's existing variables.
892 [variables addObjectsFromArray
:frame.variables
];
894 // Add these new variables.
895 NSArray
* addVariables
= [[response rootElement
] children
];
897 [variables addObjectsFromArray
:addVariables
];
899 frame.variables
= variables
;
903 * Callback from a |-getProperty:| request.
905 - (void)propertiesReceived
:(NSXMLDocument
*)response
907 NSInteger transaction
= [self transactionIDFromResponse
:response
];
911 <property> <!-- this is the one we requested -->
912 <property ... /> <!-- these are what we want -->
917 // Detach all the children so we can insert them into another document.
918 NSXMLElement
* parent
= (NSXMLElement
*)[[response rootElement
] childAtIndex
:0];
919 NSArray
* children
= [parent children
];
920 [parent setChildren
:nil];
922 [delegate receivedProperties
:children forTransaction
:transaction
];
926 * Callback for setting a breakpoint.
928 - (void)breakpointReceived
:(NSXMLDocument
*)response
930 NSNumber
* transaction
= [NSNumber numberWithInt
:[self transactionIDFromResponse
:response
]];
931 Breakpoint
* bp
= [callbackContext_ objectForKey
:transaction
];
935 [callbackContext_ removeObjectForKey
:callbackContext_
];
936 [bp setDebuggerId
:[[[[response rootElement
] attributeForName
:@
"id"] stringValue
] intValue
]];
942 * This will send a command to the debugger engine. It will append the
943 * transaction ID automatically. It accepts a NSString command along with a
944 * a variable number of arguments to substitute into the command, a la
945 * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
947 - (NSNumber
*)sendCommandWithCallback
:(SEL)callback format
:(NSString
*)format
, ...
949 // Collect varargs and format command.
951 va_start(args
, format
);
952 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
955 NSNumber
* callbackKey
= [NSNumber numberWithInt
:transactionID
++];
957 [callTable_ setObject
:NSStringFromSelector(callback
) forKey
:callbackKey
];
959 [self send
:[NSString stringWithFormat
:@
"%@ -i %@", [command autorelease
], callbackKey
]];
965 * Checks if there are unsent commands in the |queuedWrites_| queue and sends
966 * them if it's OK to do so. This will not block.
968 - (void)sendQueuedWrites
973 [writeQueueLock_ lock
];
974 if (lastReadTransaction_
>= lastWrittenTransaction_
&& [queuedWrites_ count
] > 0)
976 NSString
* command
= [queuedWrites_ objectAtIndex
:0];
978 // We don't want to block because this is called from the main thread.
979 // |-performSend:| busy waits when the stream is not ready. Bail out
980 // before we do that becuase busy waiting is BAD.
981 if (CFWriteStreamCanAcceptBytes(writeStream_
))
983 [self performSend
:command
];
984 [queuedWrites_ removeObjectAtIndex
:0];
987 [writeQueueLock_ unlock
];
991 * Given a file path, this returns a file:// URI and escapes any spaces for the
994 - (NSString
*)escapedURIPath
:(NSString
*)path
996 // Custon GDBp paths are fine.
997 if ([[path substringToIndex
:4] isEqualToString
:@
"gdbp"])
1000 // Create a temporary URL that will escape all the nasty characters.
1001 NSURL
* url
= [NSURL fileURLWithPath
:path
];
1002 NSString
* urlString
= [url absoluteString
];
1004 // Remove the host because this is a file:// URL;
1005 urlString
= [urlString stringByReplacingOccurrencesOfString
:[url host
] withString
:@
""];
1007 // Escape % for use in printf-style NSString formatters.
1008 urlString
= [urlString stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
1013 * Returns the transaction_id from an NSXMLDocument.
1015 - (NSInteger
)transactionIDFromResponse
:(NSXMLDocument
*)response
1017 return [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
1021 * Scans a command string for the transaction ID component. If it is not found,
1022 * returns NSNotFound.
1024 - (NSInteger
)transactionIDFromCommand
:(NSString
*)command
1026 NSRange occurrence
= [command rangeOfString
:@
"-i "];
1027 if (occurrence.location
== NSNotFound
)
1029 NSString
* transaction
= [command substringFromIndex
:occurrence.location
+ occurrence.length
];
1030 return [transaction intValue
];