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