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