Don't drain the pool, just release it.
[macgdbp.git] / Source / DebuggerConnection.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 "DebuggerConnection.h"
18
19 #import <sys/socket.h>
20 #import <netinet/in.h>
21
22 #import "AppDelegate.h"
23 #import "LoggingController.h"
24
25 // DebuggerConnection (Private) ////////////////////////////////////////////////
26
27 @interface DebuggerConnection ()
28
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;
36
37 - (void)connectInternal;
38
39 - (void)socketDidAccept;
40 - (void)socketDisconnected;
41 - (void)readStreamHasData;
42
43 - (void)performSend:(NSString*)command;
44 - (void)sendQueuedWrites;
45
46 - (void)handleResponse:(NSXMLDocument*)response;
47 - (void)handlePacket:(NSString*)packet;
48
49 - (void)errorEncountered:(NSString*)error;
50
51 @end
52
53 // CFNetwork Callbacks /////////////////////////////////////////////////////////
54
55 void ReadStreamCallback(CFReadStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
56 {
57 DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
58 switch (eventType)
59 {
60 case kCFStreamEventHasBytesAvailable:
61 [connection readStreamHasData];
62 break;
63
64 case kCFStreamEventErrorOccurred:
65 {
66 CFErrorRef error = CFReadStreamCopyError(stream);
67 CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
68 CFReadStreamClose(stream);
69 CFRelease(stream);
70 [connection errorEncountered:[[(NSError*)error autorelease] description]];
71 break;
72 }
73
74 case kCFStreamEventEndEncountered:
75 CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
76 CFReadStreamClose(stream);
77 CFRelease(stream);
78 [connection socketDisconnected];
79 break;
80 };
81 }
82
83 void WriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
84 {
85 DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
86 switch (eventType)
87 {
88 case kCFStreamEventCanAcceptBytes:
89 [connection sendQueuedWrites];
90 break;
91
92 case kCFStreamEventErrorOccurred:
93 {
94 CFErrorRef error = CFWriteStreamCopyError(stream);
95 CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
96 CFWriteStreamClose(stream);
97 CFRelease(stream);
98 [connection errorEncountered:[[(NSError*)error autorelease] description]];
99 break;
100 }
101
102 case kCFStreamEventEndEncountered:
103 CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
104 CFWriteStreamClose(stream);
105 CFRelease(stream);
106 [connection socketDisconnected];
107 break;
108 }
109 }
110
111 void SocketAcceptCallback(CFSocketRef socket,
112 CFSocketCallBackType callbackType,
113 CFDataRef address,
114 const void* data,
115 void* connectionRaw)
116 {
117 assert(callbackType == kCFSocketAcceptCallBack);
118 NSLog(@"SocketAcceptCallback()");
119
120 DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
121
122 CFReadStreamRef readStream;
123 CFWriteStreamRef writeStream;
124
125 // Create the streams on the socket.
126 CFStreamCreatePairWithSocket(kCFAllocatorDefault,
127 *(CFSocketNativeHandle*)data, // Socket handle.
128 &readStream, // Read stream in-pointer.
129 &writeStream); // Write stream in-pointer.
130
131 // Create struct to register callbacks for the stream.
132 CFStreamClientContext context;
133 context.version = 0;
134 context.info = connection;
135 context.retain = NULL;
136 context.release = NULL;
137 context.copyDescription = NULL;
138
139 // Set the client of the read stream.
140 CFOptionFlags readFlags =
141 kCFStreamEventOpenCompleted |
142 kCFStreamEventHasBytesAvailable |
143 kCFStreamEventErrorOccurred |
144 kCFStreamEventEndEncountered;
145 if (CFReadStreamSetClient(readStream, readFlags, ReadStreamCallback, &context))
146 // Schedule in run loop to do asynchronous communication with the engine.
147 CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
148 else
149 return;
150
151 // Open the stream now that it's scheduled on the run loop.
152 if (!CFReadStreamOpen(readStream))
153 {
154 CFStreamError error = CFReadStreamGetError(readStream);
155 NSLog(@"error! %@", error);
156 return;
157 }
158
159 // Set the client of the write stream.
160 CFOptionFlags writeFlags =
161 kCFStreamEventOpenCompleted |
162 kCFStreamEventCanAcceptBytes |
163 kCFStreamEventErrorOccurred |
164 kCFStreamEventEndEncountered;
165 if (CFWriteStreamSetClient(writeStream, writeFlags, WriteStreamCallback, &context))
166 // Schedule it in the run loop to receive error information.
167 CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
168 else
169 return;
170
171 // Open the write stream.
172 if (!CFWriteStreamOpen(writeStream))
173 {
174 CFStreamError error = CFWriteStreamGetError(writeStream);
175 NSLog(@"error! %@", error);
176 return;
177 }
178
179 connection.readStream = readStream;
180 connection.writeStream = writeStream;
181 [connection socketDidAccept];
182 }
183
184 ////////////////////////////////////////////////////////////////////////////////
185
186 @implementation DebuggerConnection
187
188 @synthesize port = port_;
189 @synthesize connected = connected_;
190 @synthesize delegate = delegate_;
191
192 @synthesize socket = socket_;
193 @synthesize readStream = readStream_;
194 @synthesize lastReadTransaction = lastReadTransaction_;
195 @synthesize currentPacket = currentPacket_;
196 @synthesize writeStream = writeStream_;
197 @synthesize lastWrittenTransaction = lastWrittenTransaction_;
198 @synthesize queuedWrites = queuedWrites_;
199
200 - (id)initWithPort:(NSUInteger)aPort
201 {
202 if (self = [super init])
203 {
204 port_ = aPort;
205 }
206 return self;
207 }
208
209 - (void)dealloc
210 {
211 self.currentPacket = nil;
212 [super dealloc];
213 }
214
215 /**
216 * Kicks off the socket on another thread.
217 */
218 - (void)connect
219 {
220 [NSThread detachNewThreadSelector:@selector(connectInternal) toTarget:self withObject:nil];
221 }
222
223 /**
224 * Creates, connects to, and schedules a CFSocket.
225 */
226 - (void)connectInternal
227 {
228 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
229
230 runLoop_ = [NSRunLoop currentRunLoop];
231
232 // Pass ourselves to the callback so we don't have to use ugly globals.
233 CFSocketContext context;
234 context.version = 0;
235 context.info = self;
236 context.retain = NULL;
237 context.release = NULL;
238 context.copyDescription = NULL;
239
240 // Create the address structure.
241 struct sockaddr_in address;
242 memset(&address, 0, sizeof(address));
243 address.sin_len = sizeof(address);
244 address.sin_family = AF_INET;
245 address.sin_port = htons(port_);
246 address.sin_addr.s_addr = htonl(INADDR_ANY);
247
248 // Create the socket signature.
249 CFSocketSignature signature;
250 signature.protocolFamily = PF_INET;
251 signature.socketType = SOCK_STREAM;
252 signature.protocol = IPPROTO_TCP;
253 signature.address = (CFDataRef)[NSData dataWithBytes:&address length:sizeof(address)];
254
255 socket_ = CFSocketCreateWithSocketSignature(kCFAllocatorDefault,
256 &signature, // Socket signature.
257 kCFSocketAcceptCallBack, // Callback types.
258 SocketAcceptCallback, // Callout function pointer.
259 &context); // Context to pass to callout.
260 if (!socket_)
261 {
262 [self errorEncountered:@"Could not open socket."];
263 return;
264 }
265
266 // Allow old, yet-to-be recycled sockets to be reused.
267 BOOL yes = YES;
268 setsockopt(CFSocketGetNative(socket_), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(BOOL));
269
270 // Schedule the socket on the run loop.
271 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
272 CFRunLoopAddSource([runLoop_ getCFRunLoop], source, kCFRunLoopCommonModes);
273 CFRelease(source);
274
275 [runLoop_ run];
276
277 [pool release];
278 }
279
280 /**
281 * Called by SocketWrapper after the connection is successful. This immediately calls
282 * -[SocketWrapper receive] to clear the way for communication, though the information
283 * could be useful server information that we don't use right now.
284 */
285 - (void)socketDidAccept
286 {
287 connected_ = YES;
288 transactionID = 1;
289 self.queuedWrites = [NSMutableArray array];
290 writeQueueLock_ = [NSRecursiveLock new];
291 }
292
293 /**
294 * Closes a socket and releases the ref.
295 */
296 - (void)close
297 {
298 if (runLoop_) {
299 CFRunLoopStop([runLoop_ getCFRunLoop]);
300 }
301
302 // The socket goes down, so do the streams, which clean themselves up.
303 if (socket_) {
304 CFSocketInvalidate(socket_);
305 CFRelease(socket_);
306 }
307 self.queuedWrites = nil;
308 [writeQueueLock_ release];
309 }
310
311 /**
312 * Notification that the socket disconnected.
313 */
314 - (void)socketDisconnected
315 {
316 [self close];
317 [delegate_ connectionDidClose:self];
318 }
319
320 /**
321 * Writes a command into the write stream. If the stream is ready for writing,
322 * we do so immediately. If not, the command is queued and will be written
323 * when the stream is ready.
324 */
325 - (void)send:(NSString*)command
326 {
327 if (lastReadTransaction_ >= lastWrittenTransaction_ && CFWriteStreamCanAcceptBytes(writeStream_)) {
328 [self performSend:command];
329 } else {
330 [writeQueueLock_ lock];
331 [queuedWrites_ addObject:command];
332 [writeQueueLock_ unlock];
333 }
334 [self sendQueuedWrites];
335 }
336
337 /**
338 * This will send a command to the debugger engine. It will append the
339 * transaction ID automatically. It accepts a NSString command along with a
340 * a variable number of arguments to substitute into the command, a la
341 * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
342 */
343 - (NSNumber*)sendCommandWithFormat:(NSString*)format, ...
344 {
345 // Collect varargs and format command.
346 va_list args;
347 va_start(args, format);
348 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
349 va_end(args);
350
351 NSNumber* callbackKey = [NSNumber numberWithInt:transactionID++];
352 [self send:[NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]];
353
354 return callbackKey;
355 }
356
357 /**
358 * Given a file path, this returns a file:// URI and escapes any spaces for the
359 * debugger engine.
360 */
361 - (NSString*)escapedURIPath:(NSString*)path
362 {
363 // Custon GDBp paths are fine.
364 if ([[path substringToIndex:4] isEqualToString:@"gdbp"])
365 return path;
366
367 // Create a temporary URL that will escape all the nasty characters.
368 NSURL* url = [NSURL fileURLWithPath:path];
369 NSString* urlString = [url absoluteString];
370
371 // Remove the host because this is a file:// URL;
372 urlString = [urlString stringByReplacingOccurrencesOfString:[url host] withString:@""];
373
374 // Escape % for use in printf-style NSString formatters.
375 urlString = [urlString stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
376 return urlString;
377 }
378
379 /**
380 * Returns the transaction_id from an NSXMLDocument.
381 */
382 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response
383 {
384 return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
385 }
386
387 /**
388 * Scans a command string for the transaction ID component. If it is not found,
389 * returns NSNotFound.
390 */
391 - (NSInteger)transactionIDFromCommand:(NSString*)command
392 {
393 NSRange occurrence = [command rangeOfString:@"-i "];
394 if (occurrence.location == NSNotFound)
395 return NSNotFound;
396 NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
397 return [transaction intValue];
398 }
399
400 // Private /////////////////////////////////////////////////////////////////////
401 #pragma mark Private
402
403 /**
404 * Receives errors from the SocketWrapper and updates the display
405 */
406 - (void)errorEncountered:(NSString*)error
407 {
408 [delegate_ errorEncountered:error];
409 }
410
411 /**
412 * Callback from the CFReadStream that there is data waiting to be read.
413 */
414 - (void)readStreamHasData
415 {
416 const NSUInteger kBufferSize = 1024;
417 UInt8 buffer[kBufferSize];
418 CFIndex bufferOffset = 0; // Starting point in |buffer| to work with.
419 CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, kBufferSize);
420 const char* charBuffer = (const char*)buffer;
421
422 // The read loop works by going through the buffer until all the bytes have
423 // been processed.
424 while (bufferOffset < bytesRead)
425 {
426 // Find the NULL separator, or the end of the string.
427 NSUInteger partLength = 0;
428 for (NSUInteger i = bufferOffset; i < bytesRead && charBuffer[i] != '\0'; ++i, ++partLength) ;
429
430 // If there is not a current packet, set some state.
431 if (!self.currentPacket)
432 {
433 // Read the message header: the size. This will be |partLength| bytes.
434 packetSize_ = atoi(charBuffer + bufferOffset);
435 currentPacketIndex_ = 0;
436 self.currentPacket = [NSMutableString stringWithCapacity:packetSize_];
437 bufferOffset += partLength + 1; // Pass over the NULL byte.
438 continue; // Spin the loop to begin reading actual data.
439 }
440
441 // Substring the byte stream and append it to the packet string.
442 CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
443 buffer + bufferOffset, // Byte pointer, offset by start index.
444 partLength, // Length.
445 kCFStringEncodingUTF8,
446 true);
447 [self.currentPacket appendString:(NSString*)bufferString];
448 CFRelease(bufferString);
449
450 // Advance counters.
451 currentPacketIndex_ += partLength;
452 bufferOffset += partLength + 1;
453
454 // If this read finished the packet, handle it and reset.
455 NSLog(@"cpi %d ps %d br %d ds %d", currentPacketIndex_, packetSize_, bytesRead, partLength);
456 if (currentPacketIndex_ >= packetSize_)
457 {
458 [self handlePacket:[[currentPacket_ retain] autorelease]];
459 self.currentPacket = nil;
460 packetSize_ = 0;
461 currentPacketIndex_ = 0;
462 }
463 }
464 }
465
466 /**
467 * Performs the packet handling of a raw string XML packet. From this point on,
468 * the packets are associated with a transaction and are then dispatched.
469 */
470 - (void)handlePacket:(NSString*)packet
471 {
472 // Test if we can convert it into an NSXMLDocument.
473 NSError* error = nil;
474 NSXMLDocument* xmlTest = [[NSXMLDocument alloc] initWithXMLString:currentPacket_ options:NSXMLDocumentTidyXML error:&error];
475
476 // Try to recover if we encountered an error.
477 if (!xmlTest)
478 {
479 // We do not want to starve the write queue, so manually parse out the
480 // transaction ID.
481 NSRange location = [currentPacket_ rangeOfString:@"transaction_id"];
482 if (location.location != NSNotFound)
483 {
484 NSUInteger start = location.location + location.length;
485 NSUInteger end = start;
486
487 NSCharacterSet* numericSet = [NSCharacterSet decimalDigitCharacterSet];
488
489 // Loop over the characters after the attribute name to extract the ID.
490 while (end < [currentPacket_ length])
491 {
492 unichar c = [currentPacket_ characterAtIndex:end];
493 if ([numericSet characterIsMember:c])
494 {
495 // If this character is numeric, extend the range to substring.
496 ++end;
497 }
498 else
499 {
500 if (start == end)
501 {
502 // If this character is nonnumeric and we have nothing in the
503 // range, skip this character.
504 ++start;
505 ++end;
506 }
507 else
508 {
509 // We've moved past the numeric ID so we should stop searching.
510 break;
511 }
512 }
513 }
514
515 // If we were able to extract the transaction ID, update the last read.
516 NSRange substringRange = NSMakeRange(start, end - start);
517 NSString* transactionStr = [currentPacket_ substringWithRange:substringRange];
518 if ([transactionStr length])
519 lastReadTransaction_ = [transactionStr intValue];
520 }
521
522 // Otherwise, assume +1 and hope it works.
523 ++lastReadTransaction_;
524 }
525 else
526 {
527 // See if the transaction can be parsed out.
528 NSInteger transaction = [self transactionIDFromResponse:xmlTest];
529 if (transaction < lastReadTransaction_)
530 {
531 NSLog(@"tx = %d vs %d", transaction, lastReadTransaction_);
532 NSLog(@"out of date transaction %@", packet);
533 return;
534 }
535
536 if (transaction != lastWrittenTransaction_)
537 NSLog(@"txn %d <> %d last written, %d last read", transaction, lastWrittenTransaction_, lastReadTransaction_);
538
539 lastReadTransaction_ = transaction;
540 }
541
542 // Log this receive event.
543 LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
544 LogEntry* log = [logger recordReceive:currentPacket_];
545 log.error = error;
546 log.lastWrittenTransactionID = lastWrittenTransaction_;
547 log.lastReadTransactionID = lastReadTransaction_;
548
549 // Finally, dispatch the handler for this response.
550 [self handleResponse:[xmlTest autorelease]];
551 }
552
553 - (void)handleResponse:(NSXMLDocument*)response
554 {
555 // Check and see if there's an error.
556 NSArray* error = [[response rootElement] elementsForName:@"error"];
557 if ([error count] > 0)
558 {
559 NSLog(@"Xdebug error: %@", error);
560 [delegate_ errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
561 }
562
563 if ([[[response rootElement] name] isEqualToString:@"init"])
564 {
565 [delegate_ handleInitialResponse:response];
566 return;
567 }
568
569 if ([delegate_ respondsToSelector:@selector(handleResponse:)])
570 [(NSObject*)delegate_ performSelectorOnMainThread:@selector(handleResponse:) withObject:response waitUntilDone:NO];
571
572 [self sendQueuedWrites];
573 }
574
575 /**
576 * This performs a blocking send. This should ONLY be called when we know we
577 * have write access to the stream. We will busy wait in case we don't do a full
578 * send.
579 */
580 - (void)performSend:(NSString*)command
581 {
582 // If this is an out-of-date transaction, do not bother sending it.
583 NSInteger transaction = [self transactionIDFromCommand:command];
584 if (transaction != NSNotFound && transaction < lastWrittenTransaction_)
585 return;
586
587 BOOL done = NO;
588
589 char* string = (char*)[command UTF8String];
590 int stringLength = strlen(string);
591
592 // Busy wait while writing. BAADD. Should background this operation.
593 while (!done)
594 {
595 if (CFWriteStreamCanAcceptBytes(writeStream_))
596 {
597 // Include the NULL byte in the string when we write.
598 int bytesWritten = CFWriteStreamWrite(writeStream_, (UInt8*)string, stringLength + 1);
599 if (bytesWritten < 0)
600 {
601 NSLog(@"write error");
602 }
603 // Incomplete write.
604 else if (bytesWritten < strlen(string))
605 {
606 // Adjust the buffer and wait for another chance to write.
607 stringLength -= bytesWritten;
608 memmove(string, string + bytesWritten, stringLength);
609 }
610 else
611 {
612 done = YES;
613
614 // We need to scan the string to find the transactionID.
615 if (transaction == NSNotFound)
616 {
617 NSLog(@"sent %@ without a transaction ID", command);
618 continue;
619 }
620 lastWrittenTransaction_ = transaction;
621 }
622 }
623 }
624
625 // Log this trancation.
626 LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
627 LogEntry* log = [logger recordSend:command];
628 log.lastWrittenTransactionID = lastWrittenTransaction_;
629 log.lastReadTransactionID = lastReadTransaction_;
630 }
631
632 /**
633 * Checks if there are unsent commands in the |queuedWrites_| queue and sends
634 * them if it's OK to do so. This will not block.
635 */
636 - (void)sendQueuedWrites
637 {
638 if (!connected_)
639 return;
640
641 [writeQueueLock_ lock];
642 if (lastReadTransaction_ >= lastWrittenTransaction_ && [queuedWrites_ count] > 0)
643 {
644 NSString* command = [queuedWrites_ objectAtIndex:0];
645
646 // We don't want to block because this is called from the main thread.
647 // |-performSend:| busy waits when the stream is not ready. Bail out
648 // before we do that becuase busy waiting is BAD.
649 if (CFWriteStreamCanAcceptBytes(writeStream_))
650 {
651 [self performSend:command];
652 [queuedWrites_ removeObjectAtIndex:0];
653 }
654 }
655 [writeQueueLock_ unlock];
656 }
657
658 @end