Remove -[DebuggerBackEndDelegate sourceUpdated:].
[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 - (void)loadStackFrame:(StackFrame*)frame {
153 if (frame.loaded)
154 return;
155
156 // Get the source code of the file. Escape % in URL chars.
157 if ([frame.filename length]) {
158 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
159 frame.source = [[message rootElement] base64DecodedValue];
160 };
161 [_client sendCommandWithFormat:@"source -f %@" handler:handler, frame.filename];
162 }
163
164 // Get the names of all the contexts.
165 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
166 [self loadContexts:message forFrame:frame];
167 };
168 [_client sendCommandWithFormat:@"context_names -d %d" handler:handler, frame.index];
169
170 // This frame will be fully loaded.
171 frame.loaded = YES;
172 }
173
174 - (void)loadVariableNode:(VariableNode*)variable
175 forStackFrame:(StackFrame*)frame {
176 if (variable.children.count == variable.childCount)
177 return;
178
179 [self loadVariableNode:variable forStackFrame:frame dataPage:0 loadedData:@[]];
180 }
181
182 - (void)loadVariableNode:(VariableNode*)variable
183 forStackFrame:(StackFrame*)frame
184 dataPage:(unsigned int)dataPage
185 loadedData:(NSArray*)loadedData {
186 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
187 /*
188 <response>
189 <property> <!-- this is the one we requested -->
190 <property ... /> <!-- these are what we want -->
191 </property>
192 </repsonse>
193 */
194
195 // Detach all the children so we can insert them into another document.
196 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
197 NSArray* children = [parent children];
198 [parent setChildren:nil];
199
200 // Check to see if there are more children to load.
201 NSArray* newLoadedData = [loadedData arrayByAddingObjectsFromArray:children];
202
203 unsigned int totalChildren = [[[parent attributeForName:@"numchildren"] stringValue] integerValue];
204 if ([newLoadedData count] < totalChildren) {
205 [self loadVariableNode:variable
206 forStackFrame:frame
207 dataPage:dataPage + 1
208 loadedData:newLoadedData];
209 } else {
210 [variable setChildrenFromXMLChildren:newLoadedData];
211 }
212 };
213 [_client sendCommandWithFormat:@"property_get -d %d -n %@ -p %u" handler:handler, frame.index, variable.fullName, dataPage];
214 }
215
216 // Breakpoint Management ///////////////////////////////////////////////////////
217 #pragma mark Breakpoints
218
219 /**
220 * Send an add breakpoint command
221 */
222 - (void)addBreakpoint:(Breakpoint*)bp {
223 if (!_active)
224 return;
225
226 NSString* file = [ProtocolClient escapedFilePathURI:[bp transformedPath]];
227 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
228 [bp setDebuggerId:[[[[message rootElement] attributeForName:@"id"] stringValue] intValue]];
229 };
230 [_client sendCommandWithFormat:@"breakpoint_set -t line -f %@ -n %i" handler:handler, file, [bp line]];
231 }
232
233 /**
234 * Removes a breakpoint
235 */
236 - (void)removeBreakpoint:(Breakpoint*)bp {
237 if (!_active)
238 return;
239
240 [_client sendCommandWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]];
241 }
242
243 /**
244 * Sends a string to be evaluated by the engine.
245 */
246 - (void)evalScript:(NSString*)str callback:(void (^)(NSString*))callback {
247 if (!_active)
248 return;
249
250 char* encodedString = malloc(modp_b64_encode_len([str length]));
251 modp_b64_encode(encodedString, [str UTF8String], [str length]);
252 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
253 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
254 NSString* value = [parent base64DecodedValue];
255 callback(value);
256 };
257 [_client sendCustomCommandWithFormat:@"eval -i {txn} -- %s" handler:handler, encodedString];
258 free(encodedString);
259 }
260
261 // Protocol Client Delegate ////////////////////////////////////////////////////
262 #pragma mark Protocol Client Delegate
263
264 - (void)debuggerEngineConnected:(ProtocolClient*)client {
265 _active = YES;
266 [_model onNewConnection];
267 }
268
269 /**
270 * Called when the connection is finally closed. This will reopen the listening
271 * socket if the debugger remains attached.
272 */
273 - (void)debuggerEngineDisconnected:(ProtocolClient*)client {
274 _active = NO;
275
276 if ([self.delegate respondsToSelector:@selector(debuggerDisconnected)])
277 [self.delegate debuggerDisconnected];
278
279 if (self.autoAttach)
280 [_client connectOnPort:_port];
281 }
282
283 - (void)protocolClient:(ProtocolClient*)client receivedInitialMessage:(NSXMLDocument*)message {
284 [self handleInitialResponse:message];
285 }
286
287 - (void)protocolClient:(ProtocolClient*)client receivedErrorMessage:(NSXMLDocument*)message {
288 NSArray* error = [[message rootElement] elementsForName:@"error"];
289 if ([error count] > 0) {
290 NSLog(@"Xdebug error: %@", error);
291 NSString* errorMessage = [[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue];
292 [self errorEncountered:errorMessage];
293 }
294 }
295
296 // Specific Response Handlers //////////////////////////////////////////////////
297 #pragma mark Response Handlers
298
299 - (void)errorEncountered:(NSString*)error {
300 [self.delegate errorEncountered:error];
301 }
302
303 /**
304 * Initial packet received. We've started a brand-new connection to the engine.
305 */
306 - (void)handleInitialResponse:(NSXMLDocument*)response {
307 if (!self.autoAttach) {
308 [_client sendCommandWithFormat:@"detach"];
309 return;
310 }
311
312 _active = YES;
313
314 // Register any breakpoints that exist offline.
315 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
316 [self addBreakpoint:bp];
317
318 // Load the debugger to make it look active.
319 [self.delegate debuggerConnected];
320
321 // TODO: update the status.
322 }
323
324 /**
325 * Receiver for status updates. This just freshens up the UI.
326 */
327 - (void)updateStatus:(NSXMLDocument*)response {
328 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
329 _active = YES;
330 if (!_status || [_status isEqualToString:@"Stopped"]) {
331 [_delegate debuggerDisconnected];
332 _active = NO;
333 } else if ([_status isEqualToString:@"Stopping"]) {
334 [_client sendCommandWithFormat:@"stop"];
335 _active = NO;
336 }
337 self.model.status = self.status;
338 }
339
340 /**
341 * Step in/out/over and run all take this path. We first get the status of the
342 * debugger and then request fresh stack information.
343 */
344 - (void)debuggerStep:(NSXMLDocument*)response {
345 [self updateStatus:response];
346 if (![self isConnected])
347 return;
348
349 [_client sendCommandWithFormat:@"stack_depth" handler:^(NSXMLDocument* message) {
350 [self rebuildStack:message];
351 }];
352 }
353
354 /**
355 * We ask for the stack_depth and now we clobber the stack and start rebuilding
356 * it.
357 */
358 - (void)rebuildStack:(NSXMLDocument*)response {
359 NSUInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
360
361 // Start with frame 0. If this is a shifted frame, then only it needs to be
362 // re-loaded. If it is not shifted, see if another frame on the stack is equal
363 // to it; if so, then the frames up to that must be discarded. If not, this is
364 // a new stack frame that should be inserted at the top of the stack. Finally,
365 // the sice of the stack is trimmed to |depth| from the bottom.
366
367 __block NSMutableArray* tempStack = [[NSMutableArray alloc] init];
368
369 for (NSUInteger i = 0; i < depth; ++i) {
370 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
371 [tempStack addObject:[self transformXMLToStackFrame:message]];
372 if (i == depth - 1) {
373 [self.model updateStack:[tempStack autorelease]];
374 }
375 };
376 [_client sendCommandWithFormat:@"stack_get -d %d" handler:handler, i];
377 }
378 }
379
380 /**
381 * Creates a StackFrame object from an NSXMLDocument response from the "stack_get"
382 * command.
383 */
384 - (StackFrame*)transformXMLToStackFrame:(NSXMLDocument*)response {
385 NSXMLElement* xmlframe = (NSXMLElement*)[[[response rootElement] children] objectAtIndex:0];
386 StackFrame* frame = [[[StackFrame alloc] init] autorelease];
387 frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue];
388 frame.filename = [[xmlframe attributeForName:@"filename"] stringValue];
389 frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue];
390 frame.function = [[xmlframe attributeForName:@"where"] stringValue];
391 return frame;
392 }
393
394 /**
395 * Enumerates all the contexts of a given stack frame. We then in turn get the
396 * contents of each one of these contexts.
397 */
398 - (void)loadContexts:(NSXMLDocument*)response forFrame:(StackFrame*)frame {
399 NSXMLElement* contextNames = [response rootElement];
400 for (NSXMLElement* context in [contextNames children]) {
401 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
402
403 // Fetch each context's variables.
404 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
405 NSMutableArray* variables = [NSMutableArray array];
406
407 // Merge the frame's existing variables.
408 if (frame.variables)
409 [variables addObjectsFromArray:frame.variables];
410
411 // Add these new variables.
412 NSArray* addVariables = [[message rootElement] children];
413 if (addVariables) {
414 for (NSXMLElement* elm in addVariables) {
415 VariableNode* node = [[VariableNode alloc] initWithXMLNode:elm];
416 [variables addObject:[node autorelease]];
417 }
418 }
419
420 frame.variables = variables;
421 };
422 [_client sendCommandWithFormat:@"context_get -d %d -c %d" handler:handler, frame.index, cid];
423 }
424 }
425
426 @end