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