Give |-connectInternal| an autorelease pool.
[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 drain];
278 [pool release];
279 }
280
281 /**
282 * Called by SocketWrapper after the connection is successful. This immediately calls
283 * -[SocketWrapper receive] to clear the way for communication, though the information
284 * could be useful server information that we don't use right now.
285 */
286 - (void)socketDidAccept
287 {
288 connected_ = YES;
289 transactionID = 1;
290 self.queuedWrites = [NSMutableArray array];
291 writeQueueLock_ = [NSRecursiveLock new];
292 }
293
294 /**
295 * Closes a socket and releases the ref.
296 */
297 - (void)close
298 {
299 if (runLoop_) {
300 CFRunLoopStop([runLoop_ getCFRunLoop]);
301 }
302
303 // The socket goes down, so do the streams, which clean themselves up.
304 if (socket_) {
305 CFSocketInvalidate(socket_);
306 CFRelease(socket_);
307 }
308 self.queuedWrites = nil;
309 [writeQueueLock_ release];
310 }
311
312 /**
313 * Notification that the socket disconnected.
314 */
315 - (void)socketDisconnected
316 {
317 [self close];
318 [delegate_ connectionDidClose:self];
319 }
320
321 /**
322 * Writes a command into the write stream. If the stream is ready for writing,
323 * we do so immediately. If not, the command is queued and will be written
324 * when the stream is ready.
325 */
326 - (void)send:(NSString*)command
327 {
328 if (lastReadTransaction_ >= lastWrittenTransaction_ && CFWriteStreamCanAcceptBytes(writeStream_)) {
329 [self performSend:command];
330 } else {
331 [writeQueueLock_ lock];
332 [queuedWrites_ addObject:command];
333 [writeQueueLock_ unlock];
334 }
335 [self sendQueuedWrites];
336 }
337
338 /**
339 * This will send a command to the debugger engine. It will append the
340 * transaction ID automatically. It accepts a NSString command along with a
341 * a variable number of arguments to substitute into the command, a la
342 * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
343 */
344 - (NSNumber*)sendCommandWithFormat:(NSString*)format, ...
345 {
346 // Collect varargs and format command.
347 va_list args;
348 va_start(args, format);
349 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
350 va_end(args);
351
352 NSNumber* callbackKey = [NSNumber numberWithInt:transactionID++];
353 [self send:[NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]];
354
355 return callbackKey;
356 }
357
358 /**
359 * Given a file path, this returns a file:// URI and escapes any spaces for the
360 * debugger engine.
361 */
362 - (NSString*)escapedURIPath:(NSString*)path
363 {
364 // Custon GDBp paths are fine.
365 if ([[path substringToIndex:4] isEqualToString:@"gdbp"])
366 return path;
367
368 // Create a temporary URL that will escape all the nasty characters.
369 NSURL* url = [NSURL fileURLWithPath:path];
370 NSString* urlString = [url absoluteString];
371
372 // Remove the host because this is a file:// URL;
373 urlString = [urlString stringByReplacingOccurrencesOfString:[url host] withString:@""];
374
375 // Escape % for use in printf-style NSString formatters.
376 urlString = [urlString stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
377 return urlString;
378 }
379
380 /**
381 * Returns the transaction_id from an NSXMLDocument.
382 */
383 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response
384 {
385 return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
386 }
387
388 /**
389 * Scans a command string for the transaction ID component. If it is not found,
390 * returns NSNotFound.
391 */
392 - (NSInteger)transactionIDFromCommand:(NSString*)command
393 {
394 NSRange occurrence = [command rangeOfString:@"-i "];
395 if (occurrence.location == NSNotFound)
396 return NSNotFound;
397 NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
398 return [transaction intValue];
399 }
400
401 // Private /////////////////////////////////////////////////////////////////////
402 #pragma mark Private
403
404 /**
405 * Receives errors from the SocketWrapper and updates the display
406 */
407 - (void)errorEncountered:(NSString*)error
408 {
409 [delegate_ errorEncountered:error];
410 }
411
412 /**
413 * Callback from the CFReadStream that there is data waiting to be read.
414 */
415 - (void)readStreamHasData
416 {
417 const NSUInteger kBufferSize = 1024;
418 UInt8 buffer[kBufferSize];
419 CFIndex bufferOffset = 0; // Starting point in |buffer| to work with.
420 CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, kBufferSize);
421 const char* charBuffer = (const char*)buffer;
422
423 // The read loop works by going through the buffer until all the bytes have
424 // been processed.
425 while (bufferOffset < bytesRead)
426 {
427 // Find the NULL separator, or the end of the string.
428 NSUInteger partLength = 0;
429 for (NSUInteger i = bufferOffset; i < bytesRead && charBuffer[i] != '\0'; ++i, ++partLength) ;
430
431 // If there is not a current packet, set some state.
432 if (!self.currentPacket)
433 {
434 // Read the message header: the size. This will be |partLength| bytes.
435 packetSize_ = atoi(charBuffer + bufferOffset);
436 currentPacketIndex_ = 0;
437 self.currentPacket = [NSMutableString stringWithCapacity:packetSize_];
438 bufferOffset += partLength + 1; // Pass over the NULL byte.
439 continue; // Spin the loop to begin reading actual data.
440 }
441
442 // Substring the byte stream and append it to the packet string.
443 CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
444 buffer + bufferOffset, // Byte pointer, offset by start index.
445 partLength, // Length.
446 kCFStringEncodingUTF8,
447 true);
448 [self.currentPacket appendString:(NSString*)bufferString];
449 CFRelease(bufferString);
450
451 // Advance counters.
452 currentPacketIndex_ += partLength;
453 bufferOffset += partLength + 1;
454
455 // If this read finished the packet, handle it and reset.
456 NSLog(@"cpi %d ps %d br %d ds %d", currentPacketIndex_, packetSize_, bytesRead, partLength);
457 if (currentPacketIndex_ >= packetSize_)
458 {
459 [self handlePacket:[[currentPacket_ retain] autorelease]];
460 self.currentPacket = nil;
461 packetSize_ = 0;
462 currentPacketIndex_ = 0;
463 }
464 }
465 }
466
467 /**
468 * Performs the packet handling of a raw string XML packet. From this point on,
469 * the packets are associated with a transaction and are then dispatched.
470 */
471 - (void)handlePacket:(NSString*)packet
472 {
473 // Test if we can convert it into an NSXMLDocument.
474 NSError* error = nil;
475 NSXMLDocument* xmlTest = [[NSXMLDocument alloc] initWithXMLString:currentPacket_ options:NSXMLDocumentTidyXML error:&error];
476
477 // Try to recover if we encountered an error.
478 if (!xmlTest)
479 {
480 // We do not want to starve the write queue, so manually parse out the
481 // transaction ID.
482 NSRange location = [currentPacket_ rangeOfString:@"transaction_id"];
483 if (location.location != NSNotFound)
484 {
485 NSUInteger start = location.location + location.length;
486 NSUInteger end = start;
487
488 NSCharacterSet* numericSet = [NSCharacterSet decimalDigitCharacterSet];
489
490 // Loop over the characters after the attribute name to extract the ID.
491 while (end < [currentPacket_ length])
492 {
493 unichar c = [currentPacket_ characterAtIndex:end];
494 if ([numericSet characterIsMember:c])
495 {
496 // If this character is numeric, extend the range to substring.
497 ++end;
498 }
499 else
500 {
501 if (start == end)
502 {
503 // If this character is nonnumeric and we have nothing in the
504 // range, skip this character.
505 ++start;
506 ++end;
507 }
508 else
509 {
510 // We've moved past the numeric ID so we should stop searching.
511 break;
512 }
513 }
514 }
515
516 // If we were able to extract the transaction ID, update the last read.
517 NSRange substringRange = NSMakeRange(start, end - start);
518 NSString* transactionStr = [currentPacket_ substringWithRange:substringRange];
519 if ([transactionStr length])
520 lastReadTransaction_ = [transactionStr intValue];
521 }
522
523 // Otherwise, assume +1 and hope it works.
524 ++lastReadTransaction_;
525 }
526 else
527 {
528 // See if the transaction can be parsed out.
529 NSInteger transaction = [self transactionIDFromResponse:xmlTest];
530 if (transaction < lastReadTransaction_)
531 {
532 NSLog(@"tx = %d vs %d", transaction, lastReadTransaction_);
533 NSLog(@"out of date transaction %@", packet);
534 return;
535 }
536
537 if (transaction != lastWrittenTransaction_)
538 NSLog(@"txn %d <> %d last written, %d last read", transaction, lastWrittenTransaction_, lastReadTransaction_);
539
540 lastReadTransaction_ = transaction;
541 }
542
543 // Log this receive event.
544 LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
545 LogEntry* log = [logger recordReceive:currentPacket_];
546 log.error = error;
547 log.lastWrittenTransactionID = lastWrittenTransaction_;
548 log.lastReadTransactionID = lastReadTransaction_;
549
550 // Finally, dispatch the handler for this response.
551 [self handleResponse:[xmlTest autorelease]];
552 }
553
554 - (void)handleResponse:(NSXMLDocument*)response
555 {
556 // Check and see if there's an error.
557 NSArray* error = [[response rootElement] elementsForName:@"error"];
558 if ([error count] > 0)
559 {
560 NSLog(@"Xdebug error: %@", error);
561 [delegate_ errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
562 }
563
564 if ([[[response rootElement] name] isEqualToString:@"init"])
565 {
566 [delegate_ handleInitialResponse:response];
567 return;
568 }
569
570 if ([delegate_ respondsToSelector:@selector(handleResponse:)])
571 [(NSObject*)delegate_ performSelectorOnMainThread:@selector(handleResponse:) withObject:response waitUntilDone:NO];
572
573 [self sendQueuedWrites];
574 }
575
576 /**
577 * This performs a blocking send. This should ONLY be called when we know we
578 * have write access to the stream. We will busy wait in case we don't do a full
579 * send.
580 */
581 - (void)performSend:(NSString*)command
582 {
583 // If this is an out-of-date transaction, do not bother sending it.
584 NSInteger transaction = [self transactionIDFromCommand:command];
585 if (transaction != NSNotFound && transaction < lastWrittenTransaction_)
586 return;
587
588 BOOL done = NO;
589
590 char* string = (char*)[command UTF8String];
591 int stringLength = strlen(string);
592
593 // Busy wait while writing. BAADD. Should background this operation.
594 while (!done)
595 {
596 if (CFWriteStreamCanAcceptBytes(writeStream_))
597 {
598 // Include the NULL byte in the string when we write.
599 int bytesWritten = CFWriteStreamWrite(writeStream_, (UInt8*)string, stringLength + 1);
600 if (bytesWritten < 0)
601 {
602 NSLog(@"write error");
603 }
604 // Incomplete write.
605 else if (bytesWritten < strlen(string))
606 {
607 // Adjust the buffer and wait for another chance to write.
608 stringLength -= bytesWritten;
609 memmove(string, string + bytesWritten, stringLength);
610 }
611 else
612 {
613 done = YES;
614
615 // We need to scan the string to find the transactionID.
616 if (transaction == NSNotFound)
617 {
618 NSLog(@"sent %@ without a transaction ID", command);
619 continue;
620 }
621 lastWrittenTransaction_ = transaction;
622 }
623 }
624 }
625
626 // Log this trancation.
627 LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
628 LogEntry* log = [logger recordSend:command];
629 log.lastWrittenTransactionID = lastWrittenTransaction_;
630 log.lastReadTransactionID = lastReadTransaction_;
631 }
632
633 /**
634 * Checks if there are unsent commands in the |queuedWrites_| queue and sends
635 * them if it's OK to do so. This will not block.
636 */
637 - (void)sendQueuedWrites
638 {
639 if (!connected_)
640 return;
641
642 [writeQueueLock_ lock];
643 if (lastReadTransaction_ >= lastWrittenTransaction_ && [queuedWrites_ count] > 0)
644 {
645 NSString* command = [queuedWrites_ objectAtIndex:0];
646
647 // We don't want to block because this is called from the main thread.
648 // |-performSend:| busy waits when the stream is not ready. Bail out
649 // before we do that becuase busy waiting is BAD.
650 if (CFWriteStreamCanAcceptBytes(writeStream_))
651 {
652 [self performSend:command];
653 [queuedWrites_ removeObjectAtIndex:0];
654 }
655 }
656 [writeQueueLock_ unlock];
657 }
658
659 @end