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