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