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