Introduce DebuggerModel, which will be updated by the BackEnd.
[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 // If this is the run command, tell the delegate that a bunch of updates
336 // are coming. Also remove all existing stack routes and request a new stack.
337 if ([self.delegate respondsToSelector:@selector(clobberStack)])
338 [self.delegate clobberStack];
339
340 [_client sendCommandWithFormat:@"stack_depth" handler:^(NSXMLDocument* message) {
341 [self rebuildStack:message];
342 }];
343 }
344
345 /**
346 * We ask for the stack_depth and now we clobber the stack and start rebuilding
347 * it.
348 */
349 - (void)rebuildStack:(NSXMLDocument*)response {
350 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
351
352 // We now need to alloc a bunch of stack frames and get the basic information
353 // for them.
354 for (NSInteger i = 0; i < depth; i++) {
355 // Use the transaction ID to create a routing path.
356 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
357 StackFrame* frame = [[[StackFrame alloc] init] autorelease];
358 NSXMLElement* xmlframe = (NSXMLElement*)[[[message rootElement] children] objectAtIndex:0];
359
360 // Initialize the stack frame.
361 frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue];
362 frame.filename = [[xmlframe attributeForName:@"filename"] stringValue];
363 frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue];
364 frame.function = [[xmlframe attributeForName:@"where"] stringValue];
365
366 // Only get the complete frame for the first level. The other frames will get
367 // information loaded lazily when the user clicks on one.
368 if (frame.index == 0) {
369 [self loadStackFrame:frame];
370 }
371
372 if ([self.delegate respondsToSelector:@selector(newStackFrame:)])
373 [self.delegate newStackFrame:frame];
374 };
375 [_client sendCommandWithFormat:@"stack_get -d %d" handler:handler, i];
376 }
377 }
378
379 /**
380 * Enumerates all the contexts of a given stack frame. We then in turn get the
381 * contents of each one of these contexts.
382 */
383 - (void)loadContexts:(NSXMLDocument*)response forFrame:(StackFrame*)frame {
384 NSXMLElement* contextNames = [response rootElement];
385 for (NSXMLElement* context in [contextNames children]) {
386 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
387
388 // Fetch each context's variables.
389 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
390 NSMutableArray* variables = [NSMutableArray array];
391
392 // Merge the frame's existing variables.
393 if (frame.variables)
394 [variables addObjectsFromArray:frame.variables];
395
396 // Add these new variables.
397 NSArray* addVariables = [[message rootElement] children];
398 if (addVariables) {
399 for (NSXMLElement* elm in addVariables) {
400 VariableNode* node = [[VariableNode alloc] initWithXMLNode:elm];
401 [variables addObject:[node autorelease]];
402 }
403 }
404
405 frame.variables = variables;
406 };
407 [_client sendCommandWithFormat:@"context_get -d %d -c %d" handler:handler, frame.index, cid];
408 }
409 }
410
411 @end