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