Convert the remaining DebuggerController IBOutlets to properties.
[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 [_client sendCommandWithFormat:@"breakpoint_set -t call -m %@" handler:handler, bp.functionName];
212 }
213 }
214
215 /**
216 * Removes a breakpoint
217 */
218 - (void)removeBreakpoint:(Breakpoint*)bp {
219 if (!self.model.connected)
220 return;
221
222 [_client sendCommandWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]];
223 }
224
225 /**
226 * Sends a string to be evaluated by the engine.
227 */
228 - (void)evalScript:(NSString*)str callback:(void (^)(NSString*))callback {
229 if (!self.model.connected)
230 return;
231
232 NSData* stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
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} -- %@"
239 handler:handler, [stringData base64EncodedStringWithOptions:0]];
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 self.model.breakpointManager.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