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