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