Move the Eval window into a tabbed section.
[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 [[BreakpointManager sharedManager] setConnection:self];
35 _port = aPort;
36 _client = [[ProtocolClient alloc] initWithDelegate:self];
37
38 _autoAttach = doAttach;
39 if (doAttach)
40 [_client connectOnPort:_port];
41 }
42 return self;
43 }
44
45 - (void)dealloc {
46 [_client release];
47 [super dealloc];
48 }
49
50 // Getters /////////////////////////////////////////////////////////////////////
51 #pragma mark Getters
52
53 /**
54 * Gets the port number
55 */
56 - (uint16_t)port {
57 return _port;
58 }
59
60 /**
61 * Sets the attached state of the debugger. This will open and close the
62 * connection as appropriate.
63 */
64 - (void)setAutoAttach:(BOOL)flag {
65 if (flag == _autoAttach)
66 return;
67
68 if (_autoAttach)
69 [_client disconnect];
70 else
71 [_client connectOnPort:_port];
72
73 _autoAttach = flag;
74 }
75
76 // Commands ////////////////////////////////////////////////////////////////////
77 #pragma mark Commands
78
79 /**
80 * Tells the debugger to continue running the script. Returns the current stack frame.
81 */
82 - (void)run {
83 [_client sendCommandWithFormat:@"run" handler:^(NSXMLDocument* message) {
84 [self debuggerStep:message];
85 }];
86 }
87
88 /**
89 * Tells the debugger to step into the current command.
90 */
91 - (void)stepIn {
92 [_client sendCommandWithFormat:@"step_into" handler:^(NSXMLDocument* message) {
93 [self debuggerStep:message];
94 }];
95 }
96
97 /**
98 * Tells the debugger to step out of the current context
99 */
100 - (void)stepOut {
101 [_client sendCommandWithFormat:@"step_out" handler:^(NSXMLDocument* message) {
102 [self debuggerStep:message];
103 }];
104 }
105
106 /**
107 * Tells the debugger to step over the current function
108 */
109 - (void)stepOver {
110 [_client sendCommandWithFormat:@"step_over" handler:^(NSXMLDocument* message) {
111 [self debuggerStep:message];
112 }];
113 }
114
115 /**
116 * Halts execution of the script.
117 */
118 - (void)stop {
119 [_client disconnect];
120 self.model.status = @"Stopped";
121 }
122
123 /**
124 * Ends the current debugging session.
125 */
126 - (void)detach {
127 [_client sendCommandWithFormat:@"detach"];
128 self.model.status = @"Stopped";
129 }
130
131 - (void)loadStackFrame:(StackFrame*)frame {
132 if (frame.loaded)
133 return;
134
135 // Get the source code of the file. Escape % in URL chars.
136 if ([frame.filename length]) {
137 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
138 frame.source = [[message rootElement] base64DecodedValue];
139 };
140 [_client sendCommandWithFormat:@"source -f %@" handler:handler, frame.filename];
141 }
142
143 // Get the names of all the contexts.
144 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
145 [self loadContexts:message forFrame:frame];
146 };
147 [_client sendCommandWithFormat:@"context_names -d %d" handler:handler, frame.index];
148
149 // This frame will be fully loaded.
150 frame.loaded = YES;
151 }
152
153 - (void)loadVariableNode:(VariableNode*)variable
154 forStackFrame:(StackFrame*)frame {
155 if (variable.children.count == variable.childCount)
156 return;
157
158 [self loadVariableNode:variable forStackFrame:frame dataPage:0 loadedData:@[]];
159 }
160
161 - (void)loadVariableNode:(VariableNode*)variable
162 forStackFrame:(StackFrame*)frame
163 dataPage:(unsigned int)dataPage
164 loadedData:(NSArray*)loadedData {
165 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
166 /*
167 <response>
168 <property> <!-- this is the one we requested -->
169 <property ... /> <!-- these are what we want -->
170 </property>
171 </repsonse>
172 */
173
174 // Detach all the children so we can insert them into another document.
175 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
176 NSArray* children = [parent children];
177 [parent setChildren:nil];
178
179 // Check to see if there are more children to load.
180 NSArray* newLoadedData = [loadedData arrayByAddingObjectsFromArray:children];
181
182 unsigned int totalChildren = [[[parent attributeForName:@"numchildren"] stringValue] integerValue];
183 if ([newLoadedData count] < totalChildren) {
184 [self loadVariableNode:variable
185 forStackFrame:frame
186 dataPage:dataPage + 1
187 loadedData:newLoadedData];
188 } else {
189 [variable setChildrenFromXMLChildren:newLoadedData];
190 }
191 };
192 [_client sendCommandWithFormat:@"property_get -d %d -n %@ -p %u" handler:handler, frame.index, variable.fullName, dataPage];
193 }
194
195 // Breakpoint Management ///////////////////////////////////////////////////////
196 #pragma mark Breakpoints
197
198 /**
199 * Send an add breakpoint command
200 */
201 - (void)addBreakpoint:(Breakpoint*)bp {
202 if (!self.model.connected)
203 return;
204
205 NSString* file = [ProtocolClient escapedFilePathURI:[bp transformedPath]];
206 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
207 [bp setDebuggerId:[[[[message rootElement] attributeForName:@"id"] stringValue] intValue]];
208 };
209 [_client sendCommandWithFormat:@"breakpoint_set -t line -f %@ -n %i" handler:handler, file, [bp line]];
210 }
211
212 /**
213 * Removes a breakpoint
214 */
215 - (void)removeBreakpoint:(Breakpoint*)bp {
216 if (!self.model.connected)
217 return;
218
219 [_client sendCommandWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]];
220 }
221
222 /**
223 * Sends a string to be evaluated by the engine.
224 */
225 - (void)evalScript:(NSString*)str callback:(void (^)(NSString*))callback {
226 if (!self.model.connected)
227 return;
228
229 NSData* stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
230 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
231 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
232 NSString* value = [parent base64DecodedValue];
233 callback(value);
234 };
235 [_client sendCustomCommandWithFormat:@"eval -i {txn} -- %@" handler:handler, [stringData base64Encoding]];
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 [_client connectOnPort:_port];
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 [[BreakpointManager sharedManager] 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 autorelease]];
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] autorelease];
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 autorelease]];
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 @end