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