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