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