Force a re-serialization of Breakpoints on decode.
[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
130 // Get the source code of the file. Escape % in URL chars.
131 if ([frame.filename length]) {
132 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
133 frame.source = [[message rootElement] base64DecodedValue];
134 };
135 [_client sendCommandWithFormat:@"source -f %@" handler:handler, frame.filename];
136 }
137
138 // Get the names of all the contexts.
139 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
140 [self loadContexts:message forFrame:frame];
141 };
142 [_client sendCommandWithFormat:@"context_names -d %d" handler:handler, frame.index];
143
144 // This frame will be fully loaded.
145 frame.loaded = YES;
146 }
147
148 - (void)loadVariableNode:(VariableNode*)variable
149 forStackFrame:(StackFrame*)frame {
150 if (variable.children.count == variable.childCount)
151 return;
152
153 [self loadVariableNode:variable forStackFrame:frame dataPage:0 loadedData:@[]];
154 }
155
156 - (void)loadVariableNode:(VariableNode*)variable
157 forStackFrame:(StackFrame*)frame
158 dataPage:(unsigned int)dataPage
159 loadedData:(NSArray*)loadedData {
160 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
161 /*
162 <response>
163 <property> <!-- this is the one we requested -->
164 <property ... /> <!-- these are what we want -->
165 </property>
166 </repsonse>
167 */
168
169 // Detach all the children so we can insert them into another document.
170 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
171 NSArray* children = [parent children];
172 [parent setChildren:nil];
173
174 // Check to see if there are more children to load.
175 NSArray* newLoadedData = [loadedData arrayByAddingObjectsFromArray:children];
176
177 NSInteger totalChildren = [[[parent attributeForName:@"numchildren"] stringValue] integerValue];
178 if ([newLoadedData count] < (NSUInteger)totalChildren) {
179 [self loadVariableNode:variable
180 forStackFrame:frame
181 dataPage:dataPage + 1
182 loadedData:newLoadedData];
183 } else {
184 [variable setChildrenFromXMLChildren:newLoadedData];
185 }
186 };
187 [_client sendCommandWithFormat:@"property_get -d %d -n %@ -p %u" handler:handler, frame.index, variable.fullName, dataPage];
188 }
189
190 // Breakpoint Management ///////////////////////////////////////////////////////
191 #pragma mark Breakpoints
192
193 /**
194 * Send an add breakpoint command
195 */
196 - (void)addBreakpoint:(Breakpoint*)bp {
197 if (!self.model.connected)
198 return;
199
200 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
201 [bp setDebuggerId:[[[[message rootElement] attributeForName:@"id"] stringValue] intValue]];
202 };
203 if (bp.type == kBreakpointTypeFile) {
204 NSString* file = [ProtocolClient escapedFilePathURI:[bp transformedPath]];
205 [_client sendCommandWithFormat:@"breakpoint_set -t line -f %@ -n %i" handler:handler, file, [bp line]];
206 } else if (bp.type == kBreakpointTypeFunctionEntry) {
207 [_client sendCommandWithFormat:@"breakpoint_set -t call -m %@" handler:handler, bp.functionName];
208 }
209 }
210
211 /**
212 * Removes a breakpoint
213 */
214 - (void)removeBreakpoint:(Breakpoint*)bp {
215 if (!self.model.connected)
216 return;
217
218 [_client sendCommandWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]];
219 }
220
221 /**
222 * Sends a string to be evaluated by the engine.
223 */
224 - (void)evalScript:(NSString*)str callback:(void (^)(NSString*))callback {
225 if (!self.model.connected)
226 return;
227
228 NSData* stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
229 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
230 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
231 NSString* value = [parent base64DecodedValue];
232 callback(value);
233 };
234 [_client sendCustomCommandWithFormat:@"eval -i {txn} -- %@"
235 handler:handler, [stringData base64EncodedStringWithOptions:0]];
236 }
237
238 // Protocol Client Delegate ////////////////////////////////////////////////////
239 #pragma mark Protocol Client Delegate
240
241 - (void)debuggerEngineConnected:(ProtocolClient*)client {
242 [_model onNewConnection];
243 }
244
245 /**
246 * Called when the connection is finally closed. This will reopen the listening
247 * socket if the debugger remains attached.
248 */
249 - (void)debuggerEngineDisconnected:(ProtocolClient*)client {
250 [_model onDisconnect];
251
252 if (self.autoAttach)
253 [self doConnect];
254 }
255
256 - (void)protocolClient:(ProtocolClient*)client receivedInitialMessage:(NSXMLDocument*)message {
257 [self handleInitialResponse:message];
258 }
259
260 - (void)protocolClient:(ProtocolClient*)client receivedErrorMessage:(NSXMLDocument*)message {
261 NSArray* error = [[message rootElement] elementsForName:@"error"];
262 if ([error count] > 0) {
263 NSLog(@"Xdebug error: %@", error);
264 NSString* errorMessage = [[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue];
265 _model.lastError = errorMessage;
266 }
267 }
268
269 // Specific Response Handlers //////////////////////////////////////////////////
270 #pragma mark Response Handlers
271
272 /**
273 * Initial packet received. We've started a brand-new connection to the engine.
274 */
275 - (void)handleInitialResponse:(NSXMLDocument*)response {
276 if (!self.autoAttach) {
277 [_client sendCommandWithFormat:@"detach"];
278 return;
279 }
280
281 // Register any breakpoints that exist offline.
282 for (Breakpoint* bp in self.model.breakpointManager.breakpoints)
283 [self addBreakpoint:bp];
284
285 // TODO: update the status.
286 }
287
288 /**
289 * Receiver for status updates. This just freshens up the UI.
290 */
291 - (void)updateStatus:(NSXMLDocument*)response {
292 NSString* status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
293 self.model.status = status;
294 if (!status || [status isEqualToString:@"Stopped"]) {
295 [_model onDisconnect];
296 } else if ([status isEqualToString:@"Stopping"]) {
297 [_client sendCommandWithFormat:@"stop"];
298 }
299 }
300
301 /**
302 * Step in/out/over and run all take this path. We first get the status of the
303 * debugger and then request fresh stack information.
304 */
305 - (void)debuggerStep:(NSXMLDocument*)response {
306 [self updateStatus:response];
307 if (!self.model.connected)
308 return;
309
310 [_client sendCommandWithFormat:@"stack_depth" handler:^(NSXMLDocument* message) {
311 [self rebuildStack:message];
312 }];
313 }
314
315 /**
316 * We ask for the stack_depth and now we clobber the stack and start rebuilding
317 * it.
318 */
319 - (void)rebuildStack:(NSXMLDocument*)response {
320 NSUInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
321
322 // Send a request to get each frame of the stack, which will be added to this
323 // array. When the final frame arrives, the |tempStack| is released.
324 __block NSMutableArray* tempStack = [[NSMutableArray alloc] init];
325
326 for (NSUInteger i = 0; i < depth; ++i) {
327 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
328 [tempStack addObject:[self transformXMLToStackFrame:message]];
329 if (i == depth - 1) {
330 [self.model updateStack:tempStack];
331 }
332 };
333 [_client sendCommandWithFormat:@"stack_get -d %d" handler:handler, i];
334 }
335 }
336
337 /**
338 * Creates a StackFrame object from an NSXMLDocument response from the "stack_get"
339 * command.
340 */
341 - (StackFrame*)transformXMLToStackFrame:(NSXMLDocument*)response {
342 NSXMLElement* xmlframe = (NSXMLElement*)[[[response rootElement] children] objectAtIndex:0];
343 StackFrame* frame = [[StackFrame alloc] init];
344 frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue];
345 frame.filename = [[xmlframe attributeForName:@"filename"] stringValue];
346 frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue];
347 frame.function = [[xmlframe attributeForName:@"where"] stringValue];
348 return frame;
349 }
350
351 /**
352 * Enumerates all the contexts of a given stack frame. We then in turn get the
353 * contents of each one of these contexts.
354 */
355 - (void)loadContexts:(NSXMLDocument*)response forFrame:(StackFrame*)frame {
356 NSXMLElement* contextNames = [response rootElement];
357 for (NSXMLElement* context in [contextNames children]) {
358 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
359
360 // Fetch each context's variables.
361 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
362 NSMutableArray* variables = [NSMutableArray array];
363
364 // Merge the frame's existing variables.
365 if (frame.variables)
366 [variables addObjectsFromArray:frame.variables];
367
368 // Add these new variables.
369 NSArray* addVariables = [[message rootElement] children];
370 if (addVariables) {
371 for (NSXMLElement* elm in addVariables) {
372 VariableNode* node = [[VariableNode alloc] initWithXMLNode:elm];
373 [variables addObject:node];
374 }
375 }
376
377 frame.variables = variables;
378 };
379 [_client sendCommandWithFormat:@"context_get -d %d -c %d" handler:handler, frame.index, cid];
380 }
381 }
382
383 // Private /////////////////////////////////////////////////////////////////////
384 #pragma mark Private
385
386 - (void)doConnect {
387 [_client connectOnPort:_port];
388 [_model onListeningOnPort:_port];
389 }
390
391 @end