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