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 "GDBpConnection.h"
22 #import "AppDelegate.h"
25 typedef enum _StackFrameComponents
30 } StackFrameComponent
;
32 // GDBpConnection (Private) ////////////////////////////////////////////////////
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
;
46 - (void)socketDidAccept
;
47 - (void)socketDisconnected
;
48 - (void)readStreamHasData
;
49 - (void)send
:(NSString
*)command
;
50 - (void)performSend
:(NSString
*)command
;
51 - (void)errorEncountered
:(NSString
*)error
;
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
;
61 - (NSString
*)createCommand
:(NSString
*)cmd
, ...
;
62 - (NSString
*)createRouted
:(NSString
*)routingID command
:(NSString
*)cmd
, ...
;
64 - (void)sendQueuedWrites
;
66 - (StackFrame
*)createStackFrame
:(int)depth
;
67 - (NSString
*)escapedURIPath
:(NSString
*)path
;
70 // CFNetwork Callbacks /////////////////////////////////////////////////////////
72 void ReadStreamCallback(CFReadStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
74 GDBpConnection
* connection
= (GDBpConnection
*)connectionRaw
;
77 case kCFStreamEventHasBytesAvailable
:
78 NSLog(@
"About to read.");
79 [connection readStreamHasData
];
82 case kCFStreamEventErrorOccurred
:
84 CFErrorRef error
= CFReadStreamCopyError(stream
);
85 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
86 CFReadStreamClose(stream
);
88 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
92 case kCFStreamEventEndEncountered
:
93 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
94 CFReadStreamClose(stream
);
96 [connection socketDisconnected
];
101 void WriteStreamCallback(CFWriteStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
103 GDBpConnection
* connection
= (GDBpConnection
*)connectionRaw
;
106 case kCFStreamEventCanAcceptBytes
:
107 [connection sendQueuedWrites
];
110 case kCFStreamEventErrorOccurred
:
112 CFErrorRef error
= CFWriteStreamCopyError(stream
);
113 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
114 CFWriteStreamClose(stream
);
116 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
120 case kCFStreamEventEndEncountered
:
121 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
122 CFWriteStreamClose(stream
);
124 [connection socketDisconnected
];
129 void SocketAcceptCallback(CFSocketRef socket
,
130 CFSocketCallBackType callbackType
,
135 assert(callbackType
== kCFSocketAcceptCallBack
);
136 NSLog(@
"SocketAcceptCallback()");
138 GDBpConnection
* connection
= (GDBpConnection
*)connectionRaw
;
140 CFReadStreamRef readStream
;
141 CFWriteStreamRef writeStream
;
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.
149 // Create struct to register callbacks for the stream.
150 CFStreamClientContext context
;
152 context.info
= connection
;
153 context.retain
= NULL
;
154 context.release
= NULL
;
155 context.copyDescription
= NULL
;
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
);
169 // Open the stream now that it's scheduled on the run loop.
170 if (!CFReadStreamOpen(readStream
))
172 CFStreamError error
= CFReadStreamGetError(readStream
);
173 NSLog(@
"error! %@", error
);
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
);
189 // Open the write stream.
190 if (!CFWriteStreamOpen(writeStream
))
192 CFStreamError error
= CFWriteStreamGetError(writeStream
);
193 NSLog(@
"error! %@", error
);
197 connection.readStream
= readStream
;
198 connection.writeStream
= writeStream
;
199 [connection socketDidAccept
];
202 // GDBpConnection //////////////////////////////////////////////////////////////
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_
;
213 @synthesize delegate
;
216 * Creates a new DebuggerConnection and initializes the socket from the given connection
219 - (id)initWithPort
:(int)aPort
221 if (self = [super init
])
226 [[BreakpointManager sharedManager
] setConnection
:self];
234 * Deallocates the object
239 self.currentPacket
= nil;
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
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.
278 - (void)socketDidAccept
282 stackFrames_
= [[NSMutableDictionary alloc
] init
];
283 self.queuedWrites
= [NSMutableArray array
];
284 writeQueueLock_
= [NSRecursiveLock
new];
288 * Receives errors from the SocketWrapper and updates the display
290 - (void)errorEncountered
:(NSString
*)error
292 [delegate errorEncountered
:error
];
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
302 self.status
= @
"Connecting";
307 * Creates an entirely new stack and returns it as an array of StackFrame objects.
309 - (NSArray
*)getCurrentStack
311 NSMutableArray
* stack
= [NSMutableArray array
];
312 NSLog(@
"NOTIMPLEMENTED(): %s", _cmd
);
317 * Tells the debugger to continue running the script. Returns the current stack frame.
321 [self send
:[self createCommand
:@
"run"]];
325 * Tells the debugger to step into the current command.
329 [self send
:[self createCommand
:@
"step_into"]];
333 * Tells the debugger to step out of the current context
337 [self send
:[self createCommand
:@
"step_out"]];
341 * Tells the debugger to step over the current function
345 [self send
:[self createCommand
:@
"step_over"]];
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.
352 - (NSArray
*)getProperty
:(NSString
*)property
354 [socket send
:[self createCommand
:[NSString stringWithFormat
:@
"property_get -n \"%@\"", property
]]];
356 NSXMLDocument
* doc
= [self processData
:[socket receive
]];
360 <property> <!-- this is the one we requested -->
361 <property ... /> <!-- these are what we want -->
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];
373 #pragma mark Breakpoints
376 * Send an add breakpoint command
378 - (void)addBreakpoint
:(Breakpoint
*)bp
383 NSString
* file
= [self escapedURIPath
:[bp transformedPath
]];
384 NSString
* cmd
= [self createCommand
:[NSString stringWithFormat
:@
"breakpoint_set -t line -f %@ -n %i", file
, [bp line
]]];
386 NSXMLDocument
* info
= [self processData
:[socket receive
]];
387 [bp setDebuggerId
:[[[[info rootElement
] attributeForName
:@
"id"] stringValue
] intValue
]];
391 * Removes a breakpoint
393 - (void)removeBreakpoint
:(Breakpoint
*)bp
400 [socket send
:[self createCommand
:[NSString stringWithFormat
:@
"breakpoint_remove -d %i", [bp debuggerId
]]]];
404 #pragma mark Socket and Stream Callbacks
407 * Creates, connects to, and schedules a CFSocket.
411 // Pass ourselves to the callback so we don't have to use ugly globals.
412 CFSocketContext context
;
415 context.retain
= NULL
;
416 context.release
= NULL
;
417 context.copyDescription
= NULL
;
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
);
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
)];
434 socket_
= CFSocketCreateWithSocketSignature(kCFAllocatorDefault
,
435 &signature
, // Socket signature.
436 kCFSocketAcceptCallBack
, // Callback types.
437 SocketAcceptCallback
, // Callout function pointer.
438 &context
); // Context to pass to callout.
441 [self errorEncountered
:@
"Could not open socket."];
445 // Allow old, yet-to-be recycled sockets to be reused.
447 setsockopt(CFSocketGetNative(socket_
), SOL_SOCKET
, SO_REUSEADDR
, &yes
, sizeof(BOOL));
449 // Schedule the socket on the run loop.
450 CFRunLoopSourceRef source
= CFSocketCreateRunLoopSource(kCFAllocatorDefault
, socket_
, 0);
451 CFRunLoopAddSource(CFRunLoopGetCurrent(), source
, kCFRunLoopCommonModes
);
454 self.status
= @
"Connecting";
458 * Closes a socket and releases the ref.
462 // The socket goes down, so do the streams, which clean themselves up.
463 CFSocketInvalidate(socket_
);
465 [stackFrames_ release
];
466 self.queuedWrites
= nil;
467 [writeQueueLock_ release
];
471 * Notification that the socket disconnected.
473 - (void)socketDisconnected
476 [delegate debuggerDisconnected
];
480 * Callback from the CFReadStream that there is data waiting to be read.
482 - (void)readStreamHasData
485 CFIndex bytesRead
= CFReadStreamRead(readStream_
, buffer
, 1024);
486 const char* charBuffer
= (const char*)buffer
;
488 // We haven't finished reading a packet, so just read more data in.
489 if (currentPacketIndex_
< packetSize_
)
491 currentPacketIndex_
+= bytesRead
;
492 CFStringRef bufferString
= CFStringCreateWithBytes(kCFAllocatorDefault
,
495 kCFStringEncodingUTF8
,
497 [self.currentPacket appendString
:(NSString
*)bufferString
];
498 CFRelease(bufferString
);
500 // Time to read a new packet.
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
,
511 self.currentPacket
= [NSMutableString stringWithString
:(NSString
*)bufferString
];
512 CFRelease(bufferString
);
515 // We have finished reading the packet.
516 if (currentPacketIndex_
>= packetSize_
)
519 currentPacketIndex_
= 0;
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
];
526 NSLog(@
"Could not parse XML? --- %@", error
);
527 NSLog(@
"Error UserInfo: %@", [error userInfo
]);
528 NSLog(@
"This is the XML Document: %@", currentPacket_
);
531 [self handleResponse
:[xmlTest autorelease
]];
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.
540 - (void)send
:(NSString
*)command
542 if (lastReadTransaction_
>= lastWrittenTransaction_
&& CFWriteStreamCanAcceptBytes(writeStream_
))
543 [self performSend
:command
];
545 [queuedWrites_ addObject
:command
];
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
553 - (void)performSend
:(NSString
*)command
557 char* string
= (char*)[command UTF8String
];
558 int stringLength
= strlen(string
);
560 // Log the command if TransportDebug is enabled.
561 if ([[[[NSProcessInfo processInfo
] environment
] objectForKey
:@
"TransportDebug"] boolValue
])
562 NSLog(@
"--> %@", command
);
564 // Busy wait while writing. BAADD. Should background this operation.
567 if (CFWriteStreamCanAcceptBytes(writeStream_
))
569 // Include the NULL byte in the string when we write.
570 int bytesWritten
= CFWriteStreamWrite(writeStream_
, (UInt8
*)string
, stringLength
+ 1);
571 if (bytesWritten
< 0)
573 NSLog(@
"write error");
576 else if (bytesWritten
< strlen(string
))
578 // Adjust the buffer and wait for another chance to write.
579 stringLength
-= bytesWritten
;
580 memmove(string
, string
+ bytesWritten
, stringLength
);
586 // We need to scan the string to find the transactionID.
587 NSRange occurrence
= [command rangeOfString
:@
"-i "];
588 if (occurrence.location
== NSNotFound
)
590 NSLog(@
"sent %@ without a transaction ID", command
);
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_
);
602 #pragma mark Response Handlers
604 - (void)handleResponse
:(NSXMLDocument
*)response
606 // Check and see if there's an error.
607 NSArray
* error
= [[response rootElement
] elementsForName
:@
"error"];
608 if ([error count
] > 0)
610 NSLog(@
"Xdebug error: %@", error
);
611 [delegate errorEncountered
:[[[[error objectAtIndex
:0] children
] objectAtIndex
:0] stringValue
]];
614 // If TransportDebug is enabled, log the response.
615 if ([[[[NSProcessInfo processInfo
] environment
] objectForKey
:@
"TransportDebug"] boolValue
])
616 NSLog(@
"<-- %@", response
);
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
:@
"."];
623 NSInteger txnID
= [[routingPath objectAtIndex
:0] intValue
];
624 if (txnID
< lastReadTransaction_
)
625 NSLog(@
"out of date transaction %@", response
);
627 if (txnID
!= lastWrittenTransaction_
)
628 NSLog(@
"txn doesn't match last written %@", response
);
630 lastReadTransaction_
= [transaction intValue
];
631 NSLog(@
"read=%d, write=%d", lastReadTransaction_
, lastWrittenTransaction_
);
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
];
648 [self sendQueuedWrites
];
652 * Initial packet received. We've started a brand-new connection to the engine.
654 - (void)initReceived
:(NSXMLDocument
*)response
656 // Register any breakpoints that exist offline.
657 for (Breakpoint
* bp
in [[BreakpointManager sharedManager
] breakpoints
])
658 [self addBreakpoint
:bp
];
660 // Load the debugger to make it look active.
661 [delegate debuggerConnected
];
663 // TODO: update the status.
667 * Receiver for status updates. This just freshens up the UI.
669 - (void)updateStatus
:(NSXMLDocument
*)response
671 self.status
= [[[[response rootElement
] attributeForName
:@
"status"] stringValue
] capitalizedString
];
672 if (status
== nil ||
[status isEqualToString
:@
"Stopped"] ||
[status isEqualToString
:@
"Stopping"])
676 [delegate debuggerDisconnected
];
678 self.status
= @
"Stopped";
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.
686 - (void)debuggerStep
:(NSXMLDocument
*)response
688 [self send
:[self createCommand
:@
"status"]];
689 NSString
* command
= [[[response rootElement
] attributeForName
:@
"command"] stringValue
];
690 NSUInteger routingID
= [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
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"])
696 //[delegate clobberStack];
697 [stackFrames_ removeAllObjects
];
698 [self send
:[self createCommand
:@
"stack_depth"]];
701 //[self send:[self createRouted:[NSString stringWithFormat:@"%u", routingID] command:@"stack_get -d 0"]];
705 * We ask for the stack_depth and now we clobber the stack and start rebuilding
708 - (void)rebuildStack
:(NSXMLDocument
*)response
710 NSInteger depth
= [[[[response rootElement
] attributeForName
:@
"depth"] stringValue
] intValue
];
712 // We now need to alloc a bunch of stack frames and get the basic information
714 for (NSInteger i
= 0; i
< depth
; i
++)
716 NSString
* command
= [self createCommand
:@
"stack_get -d %d", i
];
718 // Use the transaction ID to create a routing path.
719 NSNumber
* routingID
= [NSNumber numberWithInt
:transactionID
- 1];
720 [stackFrames_ setObject
:[StackFrame alloc
] forKey
:routingID
];
727 * The initial rebuild of the stack frame. We now have enough to initialize
728 * a StackFrame object.
730 - (void)getStackFrame
:(NSXMLDocument
*)response
732 // Get the routing information.
733 NSUInteger routingID
= [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
734 NSNumber
* routingNumber
= [NSNumber numberWithInt
:routingID
];
736 // Make sure we initialized this frame in our last |-rebuildStack:|.
737 StackFrame
* frame
= [stackFrames_ objectForKey
:routingNumber
];
741 NSXMLElement
* xmlframe
= [[[response rootElement
] children
] objectAtIndex
:0];
743 // Initialize the stack frame.
744 [frame initWithIndex
:[[[xmlframe attributeForName
:@
"level"] stringValue
] intValue
]
745 withFilename
:[[xmlframe attributeForName
:@
"filename"] stringValue
]
747 atLine
:[[[xmlframe attributeForName
:@
"lineno"] stringValue
] intValue
]
748 inFunction
:[[xmlframe attributeForName
:@
"where"] stringValue
]
751 // Now that we have an initialized frame, request additional information.
752 NSString
* routingFormat
= @
"%u.%d";
753 NSString
* routingPath
= nil;
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
]]];
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
]];
766 * Routed responses are currently only used in getting all the stack frame data.
768 - (void)handleRouted
:(NSArray
*)path response
:(NSXMLDocument
*)response
770 NSLog(@
"routed %@ = %@", path
, response
);
772 // Format of |path|: transactionID.routingID.component
773 StackFrameComponent component
= [[path objectAtIndex
:2] intValue
];
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
];
781 if (component
== kStackFrameSource
)
782 frame.source
= [[response rootElement
] value
];
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:]
791 - (NSString
*)createCommand
:(NSString
*)cmd
, ...
795 va_start(argList
, cmd
);
796 NSString
* format
= [[NSString alloc
] initWithFormat
:cmd arguments
:argList
]; // format the command
799 return [NSString stringWithFormat
:@
"%@ -i %d", [format autorelease
], transactionID
++];
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
807 - (NSString
*)createRouted
:(NSString
*)routingID command
:(NSString
*)cmd
, ...
810 va_start(argList
, cmd
);
811 NSString
* format
= [[NSString alloc
] initWithFormat
:cmd arguments
:argList
];
814 return [NSString stringWithFormat
:@
"%@ -i %d.%@", [format autorelease
], transactionID
++, routingID
];
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.
821 - (void)sendQueuedWrites
823 [writeQueueLock_ lock
];
824 if (lastReadTransaction_
>= lastWrittenTransaction_
&& [queuedWrites_ count
] > 0)
826 NSString
* command
= [queuedWrites_ objectAtIndex
:0];
827 NSLog(@
"Sending queued write: %@", command
);
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_
))
834 [self performSend
:command
];
835 [queuedWrites_ removeObjectAtIndex
:0];
838 [writeQueueLock_ unlock
];
842 * Generates a stack frame for the given depth
844 - (StackFrame
*)createStackFrame
:(int)stackDepth
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
])
852 NSString
* name
= [[context attributeForName
:@
"name"] stringValue
];
853 int cid
= [[[context attributeForName
:@
"id"] stringValue
] intValue
];
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
];
866 * Given a file path, this returns a file:// URI and escapes any spaces for the
869 - (NSString
*)escapedURIPath
:(NSString
*)path
871 // Custon GDBp paths are fine.
872 if ([[path substringToIndex
:4] isEqualToString
:@
"gdbp"])
875 // Create a temporary URL that will escape all the nasty characters.
876 NSURL
* url
= [NSURL fileURLWithPath
:path
];
877 NSString
* urlString
= [url absoluteString
];
879 // Remove the host because this is a file:// URL;
880 urlString
= [urlString stringByReplacingOccurrencesOfString
:[url host
] withString
:@
""];
882 // Escape % for use in printf-style NSString formatters.
883 urlString
= [urlString stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];