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