Do not ever close the socket until we are definitely closing the connection. We can...
[macgdbp.git] / Source / DebuggerProcessor.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 "DebuggerProcessor.h"
18
19 #import "AppDelegate.h"
20 #import "NSXMLElementAdditions.h"
21
22 // GDBpConnection (Private) ////////////////////////////////////////////////////
23
24 @interface DebuggerProcessor ()
25 @property (readwrite, copy) NSString* status;
26
27 - (void)recordCallback:(SEL)callback forTransaction:(NSNumber*)txn;
28
29 - (void)updateStatus:(NSXMLDocument*)response;
30 - (void)debuggerStep:(NSXMLDocument*)response;
31 - (void)rebuildStack:(NSXMLDocument*)response;
32 - (void)getStackFrame:(NSXMLDocument*)response;
33 - (void)setSource:(NSXMLDocument*)response;
34 - (void)contextsReceived:(NSXMLDocument*)response;
35 - (void)variablesReceived:(NSXMLDocument*)response;
36 - (void)propertiesReceived:(NSXMLDocument*)response;
37
38 @end
39
40 // GDBpConnection //////////////////////////////////////////////////////////////
41
42 @implementation DebuggerProcessor
43
44 @synthesize status;
45 @synthesize delegate;
46
47 /**
48 * Creates a new DebuggerConnection and initializes the socket from the given connection
49 * paramters.
50 */
51 - (id)initWithPort:(NSUInteger)aPort
52 {
53 if (self = [super init])
54 {
55 stackFrames_ = [[NSMutableDictionary alloc] init];
56 callbackContext_ = [NSMutableDictionary new];
57 callTable_ = [NSMutableDictionary new];
58
59 [[BreakpointManager sharedManager] setConnection:self];
60 connection_ = [[DebuggerConnection alloc] initWithPort:aPort];
61 connection_.delegate = self;
62 [connection_ connect];
63 }
64 return self;
65 }
66
67 /**
68 * Deallocates the object
69 */
70 - (void)dealloc
71 {
72 [connection_ close];
73 [stackFrames_ release];
74 [callTable_ release];
75 [callbackContext_ release];
76 [super dealloc];
77 }
78
79
80 // Getters /////////////////////////////////////////////////////////////////////
81 #pragma mark Getters
82
83 /**
84 * Gets the port number
85 */
86 - (NSUInteger)port
87 {
88 return [connection_ port];
89 }
90
91 /**
92 * Returns the name of the remote host
93 */
94 - (NSString*)remoteHost
95 {
96 if (![connection_ connected])
97 return @"(DISCONNECTED)";
98
99 // TODO: Either impl or remove.
100 return @"";
101 }
102
103 /**
104 * Returns whether or not we have an active connection
105 */
106 - (BOOL)isConnected
107 {
108 return [connection_ connected] && active_;
109 }
110
111 // Commands ////////////////////////////////////////////////////////////////////
112 #pragma mark Commands
113
114 /**
115 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
116 * created every time you want to debug a page
117 */
118 - (void)reconnect
119 {
120 self.status = @"Connecting";
121 active_ = NO;
122 [connection_ reconnect];
123 }
124
125 /**
126 * Tells the debugger to continue running the script. Returns the current stack frame.
127 */
128 - (void)run
129 {
130 NSNumber* tx = [connection_ sendCommandWithFormat:@"run"];
131 [self recordCallback:@selector(debuggerStep:) forTransaction:tx];
132 }
133
134 /**
135 * Tells the debugger to step into the current command.
136 */
137 - (void)stepIn
138 {
139 NSNumber* tx = [connection_ sendCommandWithFormat:@"step_into"];
140 [self recordCallback:@selector(debuggerStep:) forTransaction:tx];
141 }
142
143 /**
144 * Tells the debugger to step out of the current context
145 */
146 - (void)stepOut
147 {
148 NSNumber* tx = [connection_ sendCommandWithFormat:@"step_out"];
149 [self recordCallback:@selector(debuggerStep:) forTransaction:tx];
150 }
151
152 /**
153 * Tells the debugger to step over the current function
154 */
155 - (void)stepOver
156 {
157 NSNumber* tx = [connection_ sendCommandWithFormat:@"step_over"];
158 [self recordCallback:@selector(debuggerStep:) forTransaction:tx];
159 }
160
161 /**
162 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
163 * that requested it so that the child can be attached.
164 */
165 - (NSInteger)getProperty:(NSString*)property
166 {
167 NSNumber* tx = [connection_ sendCommandWithFormat:@"property_get -n \"%@\"", property];
168 [self recordCallback:@selector(propertiesReceived:) forTransaction:tx];
169 return [tx intValue];
170 }
171
172 - (void)loadStackFrame:(StackFrame*)frame
173 {
174 if (frame.loaded)
175 return;
176
177 NSNumber* routingNumber = [NSNumber numberWithInt:frame.routingID];
178
179 // Get the source code of the file. Escape % in URL chars.
180 NSString* escapedFilename = [frame.filename stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
181 NSNumber* transaction = [connection_ sendCommandWithFormat:@"source -f %@", escapedFilename];
182 [self recordCallback:@selector(setSource:) forTransaction:transaction];
183 [callbackContext_ setObject:routingNumber forKey:transaction];
184
185 // Get the names of all the contexts.
186 transaction = [connection_ sendCommandWithFormat:@"context_names -d %d", frame.index];
187 [self recordCallback:@selector(contextsReceived:) forTransaction:transaction];
188 [callbackContext_ setObject:routingNumber forKey:transaction];
189
190 // This frame will be fully loaded.
191 frame.loaded = YES;
192 }
193
194 // Breakpoint Management ///////////////////////////////////////////////////////
195 #pragma mark Breakpoints
196
197 /**
198 * Send an add breakpoint command
199 */
200 - (void)addBreakpoint:(Breakpoint*)bp
201 {
202 if (![connection_ connected])
203 return;
204
205 NSString* file = [connection_ escapedURIPath:[bp transformedPath]];
206 NSNumber* tx = [connection_ sendCommandWithFormat:@"breakpoint_set -t line -f %@ -n %i", file, [bp line]];
207 [self recordCallback:@selector(breakpointReceived:) forTransaction:tx];
208 [callbackContext_ setObject:bp forKey:tx];
209 }
210
211 /**
212 * Removes a breakpoint
213 */
214 - (void)removeBreakpoint:(Breakpoint*)bp
215 {
216 if (![connection_ connected])
217 return;
218
219 [connection_ sendCommandWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]];
220 }
221
222 // Specific Response Handlers //////////////////////////////////////////////////
223 #pragma mark Response Handlers
224
225 - (void)errorEncountered:(NSString*)error
226 {
227 [delegate errorEncountered:error];
228 }
229
230 /**
231 * Initial packet received. We've started a brand-new connection to the engine.
232 */
233 - (void)handleInitialResponse:(NSXMLDocument*)response
234 {
235 active_ = YES;
236
237 // Register any breakpoints that exist offline.
238 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
239 [self addBreakpoint:bp];
240
241 // Load the debugger to make it look active.
242 [delegate debuggerConnected];
243
244 // TODO: update the status.
245 }
246
247 - (void)handleResponse:(NSXMLDocument*)response
248 {
249 NSInteger transactionID = [connection_ transactionIDFromResponse:response];
250 NSNumber* key = [NSNumber numberWithInt:transactionID];
251 NSString* callbackStr = [callTable_ objectForKey:key];
252 if (callbackStr)
253 {
254 SEL callback = NSSelectorFromString(callbackStr);
255 [self performSelector:callback withObject:response];
256 }
257 [callTable_ removeObjectForKey:key];
258 }
259
260 /**
261 * Receiver for status updates. This just freshens up the UI.
262 */
263 - (void)updateStatus:(NSXMLDocument*)response
264 {
265 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
266 active_ = YES;
267 if (!status || [status isEqualToString:@"Stopped"]) {
268 [delegate debuggerDisconnected];
269 active_ = NO;
270 } else if ([status isEqualToString:@"Stopping"]) {
271 [connection_ sendCommandWithFormat:@"stop"];
272 active_ = NO;
273 }
274 }
275
276 /**
277 * Step in/out/over and run all take this path. We first get the status of the
278 * debugger and then request fresh stack information.
279 */
280 - (void)debuggerStep:(NSXMLDocument*)response
281 {
282 [self updateStatus:response];
283 if (![self isConnected])
284 return;
285
286 // If this is the run command, tell the delegate that a bunch of updates
287 // are coming. Also remove all existing stack routes and request a new stack.
288 if ([delegate respondsToSelector:@selector(clobberStack)])
289 [delegate clobberStack];
290 [stackFrames_ removeAllObjects];
291 NSNumber* tx = [connection_ sendCommandWithFormat:@"stack_depth"];
292 [self recordCallback:@selector(rebuildStack:) forTransaction:tx];
293 stackFirstTransactionID_ = [tx intValue];
294 }
295
296 /**
297 * We ask for the stack_depth and now we clobber the stack and start rebuilding
298 * it.
299 */
300 - (void)rebuildStack:(NSXMLDocument*)response
301 {
302 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
303
304 if (stackFirstTransactionID_ == [connection_ transactionIDFromResponse:response])
305 stackDepth_ = depth;
306
307 // We now need to alloc a bunch of stack frames and get the basic information
308 // for them.
309 for (NSInteger i = 0; i < depth; i++)
310 {
311 // Use the transaction ID to create a routing path.
312 NSNumber* routingID = [connection_ sendCommandWithFormat:@"stack_get -d %d", i];
313 [self recordCallback:@selector(getStackFrame:) forTransaction:routingID];
314 [stackFrames_ setObject:[[StackFrame new] autorelease] forKey:routingID];
315 }
316 }
317
318 /**
319 * The initial rebuild of the stack frame. We now have enough to initialize
320 * a StackFrame object.
321 */
322 - (void)getStackFrame:(NSXMLDocument*)response
323 {
324 // Get the routing information.
325 NSInteger routingID = [connection_ transactionIDFromResponse:response];
326 if (routingID < stackFirstTransactionID_)
327 return;
328 NSNumber* routingNumber = [NSNumber numberWithInt:routingID];
329
330 // Make sure we initialized this frame in our last |-rebuildStack:|.
331 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
332 if (!frame)
333 return;
334
335 NSXMLElement* xmlframe = [[[response rootElement] children] objectAtIndex:0];
336
337 // Initialize the stack frame.
338 frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue];
339 frame.filename = [[xmlframe attributeForName:@"filename"] stringValue];
340 frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue];
341 frame.function = [[xmlframe attributeForName:@"where"] stringValue];
342 frame.routingID = routingID;
343
344 // Only get the complete frame for the first level. The other frames will get
345 // information loaded lazily when the user clicks on one.
346 if (frame.index == 0) {
347 [self loadStackFrame:frame];
348 }
349
350 if ([delegate respondsToSelector:@selector(newStackFrame:)])
351 [delegate newStackFrame:frame];
352 }
353
354 /**
355 * Callback for setting the source of a file while rebuilding a specific stack
356 * frame.
357 */
358 - (void)setSource:(NSXMLDocument*)response
359 {
360 NSNumber* transaction = [NSNumber numberWithInt:[connection_ transactionIDFromResponse:response]];
361 if ([transaction intValue] < stackFirstTransactionID_)
362 return;
363 NSNumber* routingNumber = [callbackContext_ objectForKey:transaction];
364 if (!routingNumber)
365 return;
366
367 [callbackContext_ removeObjectForKey:transaction];
368 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
369 if (!frame)
370 return;
371
372 frame.source = [[response rootElement] base64DecodedValue];
373
374 if ([delegate respondsToSelector:@selector(sourceUpdated:)])
375 [delegate sourceUpdated:frame];
376 }
377
378 /**
379 * Enumerates all the contexts of a given stack frame. We then in turn get the
380 * contents of each one of these contexts.
381 */
382 - (void)contextsReceived:(NSXMLDocument*)response
383 {
384 // Get the stack frame's routing ID and use it again.
385 NSNumber* receivedTransaction = [NSNumber numberWithInt:[connection_ transactionIDFromResponse:response]];
386 if ([receivedTransaction intValue] < stackFirstTransactionID_)
387 return;
388 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
389 if (!routingID)
390 return;
391
392 // Get the stack frame by the |routingID|.
393 StackFrame* frame = [stackFrames_ objectForKey:routingID];
394
395 NSXMLElement* contextNames = [response rootElement];
396 for (NSXMLElement* context in [contextNames children])
397 {
398 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
399
400 // Fetch each context's variables.
401 NSNumber* tx = [connection_ sendCommandWithFormat:@"context_get -d %d -c %d", frame.index, cid];
402 [self recordCallback:@selector(variablesReceived:) forTransaction:tx];
403 [callbackContext_ setObject:routingID forKey:tx];
404 }
405 }
406
407 /**
408 * Receives the variables from the context and attaches them to the stack frame.
409 */
410 - (void)variablesReceived:(NSXMLDocument*)response
411 {
412 // Get the stack frame's routing ID and use it again.
413 NSInteger transaction = [connection_ transactionIDFromResponse:response];
414 if (transaction < stackFirstTransactionID_)
415 return;
416 NSNumber* receivedTransaction = [NSNumber numberWithInt:transaction];
417 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
418 if (!routingID)
419 return;
420
421 // Get the stack frame by the |routingID|.
422 StackFrame* frame = [stackFrames_ objectForKey:routingID];
423
424 NSMutableArray* variables = [NSMutableArray array];
425
426 // Merge the frame's existing variables.
427 if (frame.variables)
428 [variables addObjectsFromArray:frame.variables];
429
430 // Add these new variables.
431 NSArray* addVariables = [[response rootElement] children];
432 if (addVariables) {
433 for (NSXMLElement* elm in addVariables) {
434 VariableNode* node = [[VariableNode alloc] initWithXMLNode:elm];
435 [variables addObject:[node autorelease]];
436 }
437 }
438
439 frame.variables = variables;
440 }
441
442 /**
443 * Callback from a |-getProperty:| request.
444 */
445 - (void)propertiesReceived:(NSXMLDocument*)response
446 {
447 NSInteger transaction = [connection_ transactionIDFromResponse:response];
448
449 /*
450 <response>
451 <property> <!-- this is the one we requested -->
452 <property ... /> <!-- these are what we want -->
453 </property>
454 </repsonse>
455 */
456
457 // Detach all the children so we can insert them into another document.
458 NSXMLElement* parent = (NSXMLElement*)[[response rootElement] childAtIndex:0];
459 NSArray* children = [parent children];
460 [parent setChildren:nil];
461
462 [delegate receivedProperties:children forTransaction:transaction];
463 }
464
465 /**
466 * Callback for setting a breakpoint.
467 */
468 - (void)breakpointReceived:(NSXMLDocument*)response
469 {
470 NSNumber* transaction = [NSNumber numberWithInt:[connection_ transactionIDFromResponse:response]];
471 Breakpoint* bp = [callbackContext_ objectForKey:transaction];
472 if (!bp)
473 return;
474
475 [callbackContext_ removeObjectForKey:callbackContext_];
476 [bp setDebuggerId:[[[[response rootElement] attributeForName:@"id"] stringValue] intValue]];
477 }
478
479 // Private /////////////////////////////////////////////////////////////////////
480
481 - (void)recordCallback:(SEL)callback forTransaction:(NSNumber*)txn
482 {
483 [callTable_ setObject:NSStringFromSelector(callback) forKey:txn];
484 }
485
486 @end