Remove -[Breakpoint hash] and fix -isEqual:.
[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 unsigned int totalChildren = [[[parent attributeForName:@"numchildren"] stringValue] integerValue];
182 if ([newLoadedData count] < 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 NSString* file = [ProtocolClient escapedFilePathURI:[bp transformedPath]];
205 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
206 [bp setDebuggerId:[[[[message rootElement] attributeForName:@"id"] stringValue] intValue]];
207 };
208 [_client sendCommandWithFormat:@"breakpoint_set -t line -f %@ -n %i" handler:handler, file, [bp line]];
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} -- %@" handler:handler, [stringData base64Encoding]];
235 }
236
237 // Protocol Client Delegate ////////////////////////////////////////////////////
238 #pragma mark Protocol Client Delegate
239
240 - (void)debuggerEngineConnected:(ProtocolClient*)client {
241 [_model onNewConnection];
242 }
243
244 /**
245 * Called when the connection is finally closed. This will reopen the listening
246 * socket if the debugger remains attached.
247 */
248 - (void)debuggerEngineDisconnected:(ProtocolClient*)client {
249 [_model onDisconnect];
250
251 if (self.autoAttach)
252 [_client connectOnPort:_port];
253 }
254
255 - (void)protocolClient:(ProtocolClient*)client receivedInitialMessage:(NSXMLDocument*)message {
256 [self handleInitialResponse:message];
257 }
258
259 - (void)protocolClient:(ProtocolClient*)client receivedErrorMessage:(NSXMLDocument*)message {
260 NSArray* error = [[message rootElement] elementsForName:@"error"];
261 if ([error count] > 0) {
262 NSLog(@"Xdebug error: %@", error);
263 NSString* errorMessage = [[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue];
264 _model.lastError = errorMessage;
265 }
266 }
267
268 // Specific Response Handlers //////////////////////////////////////////////////
269 #pragma mark Response Handlers
270
271 /**
272 * Initial packet received. We've started a brand-new connection to the engine.
273 */
274 - (void)handleInitialResponse:(NSXMLDocument*)response {
275 if (!self.autoAttach) {
276 [_client sendCommandWithFormat:@"detach"];
277 return;
278 }
279
280 // Register any breakpoints that exist offline.
281 for (Breakpoint* bp in self.model.breakpointManager.breakpoints)
282 [self addBreakpoint:bp];
283
284 // TODO: update the status.
285 }
286
287 /**
288 * Receiver for status updates. This just freshens up the UI.
289 */
290 - (void)updateStatus:(NSXMLDocument*)response {
291 NSString* status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
292 self.model.status = status;
293 if (!status || [status isEqualToString:@"Stopped"]) {
294 [_model onDisconnect];
295 } else if ([status isEqualToString:@"Stopping"]) {
296 [_client sendCommandWithFormat:@"stop"];
297 }
298 }
299
300 /**
301 * Step in/out/over and run all take this path. We first get the status of the
302 * debugger and then request fresh stack information.
303 */
304 - (void)debuggerStep:(NSXMLDocument*)response {
305 [self updateStatus:response];
306 if (!self.model.connected)
307 return;
308
309 [_client sendCommandWithFormat:@"stack_depth" handler:^(NSXMLDocument* message) {
310 [self rebuildStack:message];
311 }];
312 }
313
314 /**
315 * We ask for the stack_depth and now we clobber the stack and start rebuilding
316 * it.
317 */
318 - (void)rebuildStack:(NSXMLDocument*)response {
319 NSUInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
320
321 // Send a request to get each frame of the stack, which will be added to this
322 // array. When the final frame arrives, the |tempStack| is released.
323 __block NSMutableArray* tempStack = [[NSMutableArray alloc] init];
324
325 for (NSUInteger i = 0; i < depth; ++i) {
326 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
327 [tempStack addObject:[self transformXMLToStackFrame:message]];
328 if (i == depth - 1) {
329 [self.model updateStack:[tempStack autorelease]];
330 }
331 };
332 [_client sendCommandWithFormat:@"stack_get -d %d" handler:handler, i];
333 }
334 }
335
336 /**
337 * Creates a StackFrame object from an NSXMLDocument response from the "stack_get"
338 * command.
339 */
340 - (StackFrame*)transformXMLToStackFrame:(NSXMLDocument*)response {
341 NSXMLElement* xmlframe = (NSXMLElement*)[[[response rootElement] children] objectAtIndex:0];
342 StackFrame* frame = [[[StackFrame alloc] init] autorelease];
343 frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue];
344 frame.filename = [[xmlframe attributeForName:@"filename"] stringValue];
345 frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue];
346 frame.function = [[xmlframe attributeForName:@"where"] stringValue];
347 return frame;
348 }
349
350 /**
351 * Enumerates all the contexts of a given stack frame. We then in turn get the
352 * contents of each one of these contexts.
353 */
354 - (void)loadContexts:(NSXMLDocument*)response forFrame:(StackFrame*)frame {
355 NSXMLElement* contextNames = [response rootElement];
356 for (NSXMLElement* context in [contextNames children]) {
357 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
358
359 // Fetch each context's variables.
360 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
361 NSMutableArray* variables = [NSMutableArray array];
362
363 // Merge the frame's existing variables.
364 if (frame.variables)
365 [variables addObjectsFromArray:frame.variables];
366
367 // Add these new variables.
368 NSArray* addVariables = [[message rootElement] children];
369 if (addVariables) {
370 for (NSXMLElement* elm in addVariables) {
371 VariableNode* node = [[VariableNode alloc] initWithXMLNode:elm];
372 [variables addObject:[node autorelease]];
373 }
374 }
375
376 frame.variables = variables;
377 };
378 [_client sendCommandWithFormat:@"context_get -d %d -c %d" handler:handler, frame.index, cid];
379 }
380 }
381
382 @end