Remove DebuggerBackEnd.status.
[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 @implementation DebuggerBackEnd {
25 // The connection to the debugger engine.
26 NSUInteger _port;
27 ProtocolClient* _client;
28
29 // Whether or not a debugging session is currently active.
30 BOOL _active;
31 }
32
33 @synthesize autoAttach = _autoAttach;
34 @synthesize model = _model;
35
36 - (id)initWithPort:(NSUInteger)aPort
37 {
38 if (self = [super init]) {
39 [[BreakpointManager sharedManager] setConnection:self];
40 _port = aPort;
41 _client = [[ProtocolClient alloc] initWithDelegate:self];
42
43 _autoAttach = [[NSUserDefaults standardUserDefaults] boolForKey:@"DebuggerAttached"];
44
45 if (self.autoAttach)
46 [_client connectOnPort:_port];
47 }
48 return self;
49 }
50
51 - (void)dealloc {
52 [_client release];
53 [super dealloc];
54 }
55
56 // Getters /////////////////////////////////////////////////////////////////////
57 #pragma mark Getters
58
59 /**
60 * Gets the port number
61 */
62 - (NSUInteger)port {
63 return _port;
64 }
65
66 /**
67 * Returns whether or not we have an active connection
68 */
69 - (BOOL)isConnected {
70 return _active;
71 }
72
73 /**
74 * Sets the attached state of the debugger. This will open and close the
75 * connection as appropriate.
76 */
77 - (void)setAutoAttach:(BOOL)flag {
78 if (flag == _autoAttach)
79 return;
80
81 if (_autoAttach)
82 [_client disconnect];
83 else
84 [_client connectOnPort:_port];
85
86 _autoAttach = flag;
87 }
88
89 // Commands ////////////////////////////////////////////////////////////////////
90 #pragma mark Commands
91
92 /**
93 * Tells the debugger to continue running the script. Returns the current stack frame.
94 */
95 - (void)run {
96 [_client sendCommandWithFormat:@"run" handler:^(NSXMLDocument* message) {
97 [self debuggerStep:message];
98 }];
99 }
100
101 /**
102 * Tells the debugger to step into the current command.
103 */
104 - (void)stepIn {
105 [_client sendCommandWithFormat:@"step_into" handler:^(NSXMLDocument* message) {
106 [self debuggerStep:message];
107 }];
108 }
109
110 /**
111 * Tells the debugger to step out of the current context
112 */
113 - (void)stepOut {
114 [_client sendCommandWithFormat:@"step_out" handler:^(NSXMLDocument* message) {
115 [self debuggerStep:message];
116 }];
117 }
118
119 /**
120 * Tells the debugger to step over the current function
121 */
122 - (void)stepOver {
123 [_client sendCommandWithFormat:@"step_over" handler:^(NSXMLDocument* message) {
124 [self debuggerStep:message];
125 }];
126 }
127
128 /**
129 * Halts execution of the script.
130 */
131 - (void)stop {
132 [_client disconnect];
133 _active = NO;
134 self.model.status = @"Stopped";
135 }
136
137 /**
138 * Ends the current debugging session.
139 */
140 - (void)detach {
141 [_client sendCommandWithFormat:@"detach"];
142 _active = NO;
143 self.model.status = @"Stopped";
144 }
145
146 - (void)loadStackFrame:(StackFrame*)frame {
147 if (frame.loaded)
148 return;
149
150 // Get the source code of the file. Escape % in URL chars.
151 if ([frame.filename length]) {
152 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
153 frame.source = [[message rootElement] base64DecodedValue];
154 };
155 [_client sendCommandWithFormat:@"source -f %@" handler:handler, frame.filename];
156 }
157
158 // Get the names of all the contexts.
159 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
160 [self loadContexts:message forFrame:frame];
161 };
162 [_client sendCommandWithFormat:@"context_names -d %d" handler:handler, frame.index];
163
164 // This frame will be fully loaded.
165 frame.loaded = YES;
166 }
167
168 - (void)loadVariableNode:(VariableNode*)variable
169 forStackFrame:(StackFrame*)frame {
170 if (variable.children.count == variable.childCount)
171 return;
172
173 [self loadVariableNode:variable forStackFrame:frame dataPage:0 loadedData:@[]];
174 }
175
176 - (void)loadVariableNode:(VariableNode*)variable
177 forStackFrame:(StackFrame*)frame
178 dataPage:(unsigned int)dataPage
179 loadedData:(NSArray*)loadedData {
180 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
181 /*
182 <response>
183 <property> <!-- this is the one we requested -->
184 <property ... /> <!-- these are what we want -->
185 </property>
186 </repsonse>
187 */
188
189 // Detach all the children so we can insert them into another document.
190 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
191 NSArray* children = [parent children];
192 [parent setChildren:nil];
193
194 // Check to see if there are more children to load.
195 NSArray* newLoadedData = [loadedData arrayByAddingObjectsFromArray:children];
196
197 unsigned int totalChildren = [[[parent attributeForName:@"numchildren"] stringValue] integerValue];
198 if ([newLoadedData count] < totalChildren) {
199 [self loadVariableNode:variable
200 forStackFrame:frame
201 dataPage:dataPage + 1
202 loadedData:newLoadedData];
203 } else {
204 [variable setChildrenFromXMLChildren:newLoadedData];
205 }
206 };
207 [_client sendCommandWithFormat:@"property_get -d %d -n %@ -p %u" handler:handler, frame.index, variable.fullName, dataPage];
208 }
209
210 // Breakpoint Management ///////////////////////////////////////////////////////
211 #pragma mark Breakpoints
212
213 /**
214 * Send an add breakpoint command
215 */
216 - (void)addBreakpoint:(Breakpoint*)bp {
217 if (!_active)
218 return;
219
220 NSString* file = [ProtocolClient escapedFilePathURI:[bp transformedPath]];
221 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
222 [bp setDebuggerId:[[[[message rootElement] attributeForName:@"id"] stringValue] intValue]];
223 };
224 [_client sendCommandWithFormat:@"breakpoint_set -t line -f %@ -n %i" handler:handler, file, [bp line]];
225 }
226
227 /**
228 * Removes a breakpoint
229 */
230 - (void)removeBreakpoint:(Breakpoint*)bp {
231 if (!_active)
232 return;
233
234 [_client sendCommandWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]];
235 }
236
237 /**
238 * Sends a string to be evaluated by the engine.
239 */
240 - (void)evalScript:(NSString*)str callback:(void (^)(NSString*))callback {
241 if (!_active)
242 return;
243
244 char* encodedString = malloc(modp_b64_encode_len([str length]));
245 modp_b64_encode(encodedString, [str UTF8String], [str length]);
246 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
247 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
248 NSString* value = [parent base64DecodedValue];
249 callback(value);
250 };
251 [_client sendCustomCommandWithFormat:@"eval -i {txn} -- %s" handler:handler, encodedString];
252 free(encodedString);
253 }
254
255 // Protocol Client Delegate ////////////////////////////////////////////////////
256 #pragma mark Protocol Client Delegate
257
258 - (void)debuggerEngineConnected:(ProtocolClient*)client {
259 _active = YES;
260 [_model onNewConnection];
261 }
262
263 /**
264 * Called when the connection is finally closed. This will reopen the listening
265 * socket if the debugger remains attached.
266 */
267 - (void)debuggerEngineDisconnected:(ProtocolClient*)client {
268 _active = NO;
269
270 [_model onDisconnect];
271
272 if (self.autoAttach)
273 [_client connectOnPort:_port];
274 }
275
276 - (void)protocolClient:(ProtocolClient*)client receivedInitialMessage:(NSXMLDocument*)message {
277 [self handleInitialResponse:message];
278 }
279
280 - (void)protocolClient:(ProtocolClient*)client receivedErrorMessage:(NSXMLDocument*)message {
281 NSArray* error = [[message rootElement] elementsForName:@"error"];
282 if ([error count] > 0) {
283 NSLog(@"Xdebug error: %@", error);
284 NSString* errorMessage = [[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue];
285 _model.lastError = errorMessage;
286 }
287 }
288
289 // Specific Response Handlers //////////////////////////////////////////////////
290 #pragma mark Response Handlers
291
292 /**
293 * Initial packet received. We've started a brand-new connection to the engine.
294 */
295 - (void)handleInitialResponse:(NSXMLDocument*)response {
296 if (!self.autoAttach) {
297 [_client sendCommandWithFormat:@"detach"];
298 return;
299 }
300
301 _active = YES;
302
303 // Register any breakpoints that exist offline.
304 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
305 [self addBreakpoint:bp];
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 NSString* status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
315 self.model.status = status;
316 _active = YES;
317 if (!status || [status isEqualToString:@"Stopped"]) {
318 [_model onDisconnect];
319 _active = NO;
320 } else if ([status isEqualToString:@"Stopping"]) {
321 [_client sendCommandWithFormat:@"stop"];
322 _active = NO;
323 }
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