Handle sending the |detach| command when not attached in |-handleInitialResponse...
[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)errorEncountered:(NSString*)error
219 {
220 [delegate errorEncountered:error];
221 }
222
223 /**
224 * Initial packet received. We've started a brand-new connection to the engine.
225 */
226 - (void)handleInitialResponse:(NSXMLDocument*)response
227 {
228 if (!self.attached) {
229 [connection_ sendCommandWithFormat:@"detach"];
230 return;
231 }
232
233 active_ = YES;
234
235 // Register any breakpoints that exist offline.
236 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
237 [self addBreakpoint:bp];
238
239 // Load the debugger to make it look active.
240 [delegate debuggerConnected];
241
242 // TODO: update the status.
243 }
244
245 - (void)handleResponse:(NSXMLDocument*)response
246 {
247 NSInteger transactionID = [connection_ transactionIDFromResponse:response];
248 NSNumber* key = [NSNumber numberWithInt:transactionID];
249 NSString* callbackStr = [callTable_ objectForKey:key];
250 if (callbackStr)
251 {
252 SEL callback = NSSelectorFromString(callbackStr);
253 [self performSelector:callback withObject:response];
254 }
255 [callTable_ removeObjectForKey:key];
256 }
257
258 /**
259 * Receiver for status updates. This just freshens up the UI.
260 */
261 - (void)updateStatus:(NSXMLDocument*)response
262 {
263 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
264 active_ = YES;
265 if (!status || [status isEqualToString:@"Stopped"]) {
266 [delegate debuggerDisconnected];
267 active_ = NO;
268 } else if ([status isEqualToString:@"Stopping"]) {
269 [connection_ sendCommandWithFormat:@"stop"];
270 active_ = NO;
271 }
272 }
273
274 /**
275 * Step in/out/over and run all take this path. We first get the status of the
276 * debugger and then request fresh stack information.
277 */
278 - (void)debuggerStep:(NSXMLDocument*)response
279 {
280 [self updateStatus:response];
281 if (![self isConnected])
282 return;
283
284 // If this is the run command, tell the delegate that a bunch of updates
285 // are coming. Also remove all existing stack routes and request a new stack.
286 if ([delegate respondsToSelector:@selector(clobberStack)])
287 [delegate clobberStack];
288 [stackFrames_ removeAllObjects];
289 NSNumber* tx = [connection_ sendCommandWithFormat:@"stack_depth"];
290 [self recordCallback:@selector(rebuildStack:) forTransaction:tx];
291 stackFirstTransactionID_ = [tx intValue];
292 }
293
294 /**
295 * We ask for the stack_depth and now we clobber the stack and start rebuilding
296 * it.
297 */
298 - (void)rebuildStack:(NSXMLDocument*)response
299 {
300 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
301
302 if (stackFirstTransactionID_ == [connection_ transactionIDFromResponse:response])
303 stackDepth_ = depth;
304
305 // We now need to alloc a bunch of stack frames and get the basic information
306 // for them.
307 for (NSInteger i = 0; i < depth; i++)
308 {
309 // Use the transaction ID to create a routing path.
310 NSNumber* routingID = [connection_ sendCommandWithFormat:@"stack_get -d %d", i];
311 [self recordCallback:@selector(getStackFrame:) forTransaction:routingID];
312 [stackFrames_ setObject:[[StackFrame new] autorelease] forKey:routingID];
313 }
314 }
315
316 /**
317 * The initial rebuild of the stack frame. We now have enough to initialize
318 * a StackFrame object.
319 */
320 - (void)getStackFrame:(NSXMLDocument*)response
321 {
322 // Get the routing information.
323 NSInteger routingID = [connection_ transactionIDFromResponse:response];
324 if (routingID < stackFirstTransactionID_)
325 return;
326 NSNumber* routingNumber = [NSNumber numberWithInt:routingID];
327
328 // Make sure we initialized this frame in our last |-rebuildStack:|.
329 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
330 if (!frame)
331 return;
332
333 NSXMLElement* xmlframe = [[[response rootElement] children] objectAtIndex:0];
334
335 // Initialize the stack frame.
336 frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue];
337 frame.filename = [[xmlframe attributeForName:@"filename"] stringValue];
338 frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue];
339 frame.function = [[xmlframe attributeForName:@"where"] stringValue];
340 frame.routingID = routingID;
341
342 // Only get the complete frame for the first level. The other frames will get
343 // information loaded lazily when the user clicks on one.
344 if (frame.index == 0) {
345 [self loadStackFrame:frame];
346 }
347
348 if ([delegate respondsToSelector:@selector(newStackFrame:)])
349 [delegate newStackFrame:frame];
350 }
351
352 /**
353 * Callback for setting the source of a file while rebuilding a specific stack
354 * frame.
355 */
356 - (void)setSource:(NSXMLDocument*)response
357 {
358 NSNumber* transaction = [NSNumber numberWithInt:[connection_ transactionIDFromResponse:response]];
359 if ([transaction intValue] < stackFirstTransactionID_)
360 return;
361 NSNumber* routingNumber = [callbackContext_ objectForKey:transaction];
362 if (!routingNumber)
363 return;
364
365 [callbackContext_ removeObjectForKey:transaction];
366 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
367 if (!frame)
368 return;
369
370 frame.source = [[response rootElement] base64DecodedValue];
371
372 if ([delegate respondsToSelector:@selector(sourceUpdated:)])
373 [delegate sourceUpdated:frame];
374 }
375
376 /**
377 * Enumerates all the contexts of a given stack frame. We then in turn get the
378 * contents of each one of these contexts.
379 */
380 - (void)contextsReceived:(NSXMLDocument*)response
381 {
382 // Get the stack frame's routing ID and use it again.
383 NSNumber* receivedTransaction = [NSNumber numberWithInt:[connection_ transactionIDFromResponse:response]];
384 if ([receivedTransaction intValue] < stackFirstTransactionID_)
385 return;
386 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
387 if (!routingID)
388 return;
389
390 // Get the stack frame by the |routingID|.
391 StackFrame* frame = [stackFrames_ objectForKey:routingID];
392
393 NSXMLElement* contextNames = [response rootElement];
394 for (NSXMLElement* context in [contextNames children])
395 {
396 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
397
398 // Fetch each context's variables.
399 NSNumber* tx = [connection_ sendCommandWithFormat:@"context_get -d %d -c %d", frame.index, cid];
400 [self recordCallback:@selector(variablesReceived:) forTransaction:tx];
401 [callbackContext_ setObject:routingID forKey:tx];
402 }
403 }
404
405 /**
406 * Receives the variables from the context and attaches them to the stack frame.
407 */
408 - (void)variablesReceived:(NSXMLDocument*)response
409 {
410 // Get the stack frame's routing ID and use it again.
411 NSInteger transaction = [connection_ transactionIDFromResponse:response];
412 if (transaction < stackFirstTransactionID_)
413 return;
414 NSNumber* receivedTransaction = [NSNumber numberWithInt:transaction];
415 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
416 if (!routingID)
417 return;
418
419 // Get the stack frame by the |routingID|.
420 StackFrame* frame = [stackFrames_ objectForKey:routingID];
421
422 NSMutableArray* variables = [NSMutableArray array];
423
424 // Merge the frame's existing variables.
425 if (frame.variables)
426 [variables addObjectsFromArray:frame.variables];
427
428 // Add these new variables.
429 NSArray* addVariables = [[response rootElement] children];
430 if (addVariables) {
431 for (NSXMLElement* elm in addVariables) {
432 VariableNode* node = [[VariableNode alloc] initWithXMLNode:elm];
433 [variables addObject:[node autorelease]];
434 }
435 }
436
437 frame.variables = variables;
438 }
439
440 /**
441 * Callback from a |-getProperty:| request.
442 */
443 - (void)propertiesReceived:(NSXMLDocument*)response
444 {
445 NSInteger transaction = [connection_ transactionIDFromResponse:response];
446
447 /*
448 <response>
449 <property> <!-- this is the one we requested -->
450 <property ... /> <!-- these are what we want -->
451 </property>
452 </repsonse>
453 */
454
455 // Detach all the children so we can insert them into another document.
456 NSXMLElement* parent = (NSXMLElement*)[[response rootElement] childAtIndex:0];
457 NSArray* children = [parent children];
458 [parent setChildren:nil];
459
460 [delegate receivedProperties:children forTransaction:transaction];
461 }
462
463 /**
464 * Callback for setting a breakpoint.
465 */
466 - (void)breakpointReceived:(NSXMLDocument*)response
467 {
468 NSNumber* transaction = [NSNumber numberWithInt:[connection_ transactionIDFromResponse:response]];
469 Breakpoint* bp = [callbackContext_ objectForKey:transaction];
470 if (!bp)
471 return;
472
473 [callbackContext_ removeObjectForKey:callbackContext_];
474 [bp setDebuggerId:[[[[response rootElement] attributeForName:@"id"] stringValue] intValue]];
475 }
476
477 // Private /////////////////////////////////////////////////////////////////////
478
479 - (void)recordCallback:(SEL)callback forTransaction:(NSNumber*)txn
480 {
481 [callTable_ setObject:NSStringFromSelector(callback) forKey:txn];
482 }
483
484 @end