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