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