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