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