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