Remove stackFirstTransactionID_ tracking from DebuggerBackEnd.
[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 "modp_b64.h"
21 #import "NSXMLElementAdditions.h"
22
23 @interface DebuggerBackEnd ()
24 @property(readwrite, copy, nonatomic) NSString* status;
25 @end
26
27 @implementation DebuggerBackEnd {
28 // The connection to the debugger engine.
29 NSUInteger _port;
30 ProtocolClient* _client;
31
32 // Whether or not a debugging session is currently active.
33 BOOL _active;
34 }
35
36 @synthesize status = _status;
37 @synthesize autoAttach = _autoAttach;
38 @synthesize delegate = _delegate;
39
40 - (id)initWithPort:(NSUInteger)aPort
41 {
42 if (self = [super init]) {
43 [[BreakpointManager sharedManager] setConnection:self];
44 _port = aPort;
45 _client = [[ProtocolClient alloc] initWithDelegate:self];
46
47 _autoAttach = [[NSUserDefaults standardUserDefaults] boolForKey:@"DebuggerAttached"];
48
49 if (self.autoAttach)
50 [_client connectOnPort:_port];
51 }
52 return self;
53 }
54
55 - (void)dealloc {
56 [_client release];
57 [super dealloc];
58 }
59
60 // Getters /////////////////////////////////////////////////////////////////////
61 #pragma mark Getters
62
63 /**
64 * Gets the port number
65 */
66 - (NSUInteger)port {
67 return _port;
68 }
69
70 /**
71 * Returns whether or not we have an active connection
72 */
73 - (BOOL)isConnected {
74 return _active;
75 }
76
77 /**
78 * Sets the attached state of the debugger. This will open and close the
79 * connection as appropriate.
80 */
81 - (void)setAutoAttach:(BOOL)flag {
82 if (flag == _autoAttach)
83 return;
84
85 if (_autoAttach)
86 [_client disconnect];
87 else
88 [_client connectOnPort:_port];
89
90 _autoAttach = flag;
91 }
92
93 // Commands ////////////////////////////////////////////////////////////////////
94 #pragma mark Commands
95
96 /**
97 * Tells the debugger to continue running the script. Returns the current stack frame.
98 */
99 - (void)run {
100 [_client sendCommandWithFormat:@"run" handler:^(NSXMLDocument* message) {
101 [self debuggerStep:message];
102 }];
103 }
104
105 /**
106 * Tells the debugger to step into the current command.
107 */
108 - (void)stepIn {
109 [_client sendCommandWithFormat:@"step_into" handler:^(NSXMLDocument* message) {
110 [self debuggerStep:message];
111 }];
112 }
113
114 /**
115 * Tells the debugger to step out of the current context
116 */
117 - (void)stepOut {
118 [_client sendCommandWithFormat:@"step_out" handler:^(NSXMLDocument* message) {
119 [self debuggerStep:message];
120 }];
121 }
122
123 /**
124 * Tells the debugger to step over the current function
125 */
126 - (void)stepOver {
127 [_client sendCommandWithFormat:@"step_over" handler:^(NSXMLDocument* message) {
128 [self debuggerStep:message];
129 }];
130 }
131
132 /**
133 * Halts execution of the script.
134 */
135 - (void)stop {
136 [_client disconnect];
137 _active = NO;
138 self.status = @"Stopped";
139 }
140
141 /**
142 * Ends the current debugging session.
143 */
144 - (void)detach {
145 [_client sendCommandWithFormat:@"detach"];
146 _active = NO;
147 self.status = @"Stopped";
148 }
149
150 /**
151 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
152 * that requested it so that the child can be attached.
153 */
154 - (void)getChildrenOfProperty:(VariableNode*)property
155 atDepth:(NSInteger)depth
156 callback:(void (^)(NSArray*))callback {
157 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
158 /*
159 <response>
160 <property> <!-- this is the one we requested -->
161 <property ... /> <!-- these are what we want -->
162 </property>
163 </repsonse>
164 */
165
166 // Detach all the children so we can insert them into another document.
167 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
168 NSArray* children = [parent children];
169 [parent setChildren:nil];
170
171 callback(children);
172 };
173 [_client sendCommandWithFormat:@"property_get -d %d -n %@" handler:handler, depth, [property fullName]];
174 }
175
176 - (void)loadStackFrame:(StackFrame*)frame {
177 if (frame.loaded)
178 return;
179
180 // Get the source code of the file. Escape % in URL chars.
181 if ([frame.filename length]) {
182 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
183 frame.source = [[message rootElement] base64DecodedValue];
184 if ([self.delegate respondsToSelector:@selector(sourceUpdated:)])
185 [self.delegate sourceUpdated:frame];
186 };
187 [_client sendCommandWithFormat:@"source -f %@" handler:handler, frame.filename];
188 }
189
190 // Get the names of all the contexts.
191 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
192 [self loadContexts:message forFrame:frame];
193 };
194 [_client sendCommandWithFormat:@"context_names -d %d" handler:handler, frame.index];
195
196 // This frame will be fully loaded.
197 frame.loaded = YES;
198 }
199
200 // Breakpoint Management ///////////////////////////////////////////////////////
201 #pragma mark Breakpoints
202
203 /**
204 * Send an add breakpoint command
205 */
206 - (void)addBreakpoint:(Breakpoint*)bp {
207 if (!_active)
208 return;
209
210 NSString* file = [ProtocolClient escapedFilePathURI:[bp transformedPath]];
211 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
212 [bp setDebuggerId:[[[[message rootElement] attributeForName:@"id"] stringValue] intValue]];
213 };
214 [_client sendCommandWithFormat:@"breakpoint_set -t line -f %@ -n %i" handler:handler, file, [bp line]];
215 }
216
217 /**
218 * Removes a breakpoint
219 */
220 - (void)removeBreakpoint:(Breakpoint*)bp {
221 if (!_active)
222 return;
223
224 [_client sendCommandWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]];
225 }
226
227 /**
228 * Sends a string to be evaluated by the engine.
229 */
230 - (void)evalScript:(NSString*)str callback:(void (^)(NSString*))callback {
231 if (!_active)
232 return;
233
234 char* encodedString = malloc(modp_b64_encode_len([str length]));
235 modp_b64_encode(encodedString, [str UTF8String], [str length]);
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} -- %s" handler:handler, encodedString];
242 free(encodedString);
243 }
244
245 // Protocol Client Delegate ////////////////////////////////////////////////////
246 #pragma mark Protocol Client Delegate
247
248 - (void)debuggerEngineConnected:(ProtocolClient*)client {
249 _active = YES;
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 _active = NO;
258
259 if ([self.delegate respondsToSelector:@selector(debuggerDisconnected)])
260 [self.delegate debuggerDisconnected];
261
262 if (self.autoAttach)
263 [_client connectOnPort:_port];
264 }
265
266 - (void)protocolClient:(ProtocolClient*)client receivedInitialMessage:(NSXMLDocument*)message {
267 [self handleInitialResponse:message];
268 }
269
270 - (void)protocolClient:(ProtocolClient*)client receivedErrorMessage:(NSXMLDocument*)message {
271 NSArray* error = [[message rootElement] elementsForName:@"error"];
272 if ([error count] > 0) {
273 NSLog(@"Xdebug error: %@", error);
274 NSString* errorMessage = [[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue];
275 [self errorEncountered:errorMessage];
276 }
277 }
278
279 // Specific Response Handlers //////////////////////////////////////////////////
280 #pragma mark Response Handlers
281
282 - (void)errorEncountered:(NSString*)error {
283 [self.delegate errorEncountered:error];
284 }
285
286 /**
287 * Initial packet received. We've started a brand-new connection to the engine.
288 */
289 - (void)handleInitialResponse:(NSXMLDocument*)response {
290 if (!self.autoAttach) {
291 [_client sendCommandWithFormat:@"detach"];
292 return;
293 }
294
295 _active = YES;
296
297 // Register any breakpoints that exist offline.
298 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
299 [self addBreakpoint:bp];
300
301 // Load the debugger to make it look active.
302 [self.delegate debuggerConnected];
303
304 // TODO: update the status.
305 }
306
307 /**
308 * Receiver for status updates. This just freshens up the UI.
309 */
310 - (void)updateStatus:(NSXMLDocument*)response {
311 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
312 _active = YES;
313 if (!_status || [_status isEqualToString:@"Stopped"]) {
314 [_delegate debuggerDisconnected];
315 _active = NO;
316 } else if ([_status isEqualToString:@"Stopping"]) {
317 [_client sendCommandWithFormat:@"stop"];
318 _active = NO;
319 }
320 }
321
322 /**
323 * Step in/out/over and run all take this path. We first get the status of the
324 * debugger and then request fresh stack information.
325 */
326 - (void)debuggerStep:(NSXMLDocument*)response {
327 [self updateStatus:response];
328 if (![self isConnected])
329 return;
330
331 // If this is the run command, tell the delegate that a bunch of updates
332 // are coming. Also remove all existing stack routes and request a new stack.
333 if ([self.delegate respondsToSelector:@selector(clobberStack)])
334 [self.delegate clobberStack];
335
336 [_client sendCommandWithFormat:@"stack_depth" handler:^(NSXMLDocument* message) {
337 [self rebuildStack:message];
338 }];
339 }
340
341 /**
342 * We ask for the stack_depth and now we clobber the stack and start rebuilding
343 * it.
344 */
345 - (void)rebuildStack:(NSXMLDocument*)response {
346 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
347
348 // We now need to alloc a bunch of stack frames and get the basic information
349 // for them.
350 for (NSInteger i = 0; i < depth; i++) {
351 // Use the transaction ID to create a routing path.
352 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
353 StackFrame* frame = [[[StackFrame alloc] init] autorelease];
354 NSXMLElement* xmlframe = (NSXMLElement*)[[[message rootElement] children] objectAtIndex:0];
355
356 // Initialize the stack frame.
357 frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue];
358 frame.filename = [[xmlframe attributeForName:@"filename"] stringValue];
359 frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue];
360 frame.function = [[xmlframe attributeForName:@"where"] stringValue];
361
362 // Only get the complete frame for the first level. The other frames will get
363 // information loaded lazily when the user clicks on one.
364 if (frame.index == 0) {
365 [self loadStackFrame:frame];
366 }
367
368 if ([self.delegate respondsToSelector:@selector(newStackFrame:)])
369 [self.delegate newStackFrame:frame];
370 };
371 [_client sendCommandWithFormat:@"stack_get -d %d" handler:handler, i];
372 }
373 }
374
375 /**
376 * Enumerates all the contexts of a given stack frame. We then in turn get the
377 * contents of each one of these contexts.
378 */
379 - (void)loadContexts:(NSXMLDocument*)response forFrame:(StackFrame*)frame {
380 NSXMLElement* contextNames = [response rootElement];
381 for (NSXMLElement* context in [contextNames children]) {
382 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
383
384 // Fetch each context's variables.
385 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
386 NSMutableArray* variables = [NSMutableArray array];
387
388 // Merge the frame's existing variables.
389 if (frame.variables)
390 [variables addObjectsFromArray:frame.variables];
391
392 // Add these new variables.
393 NSArray* addVariables = [[message rootElement] children];
394 if (addVariables) {
395 for (NSXMLElement* elm in addVariables) {
396 VariableNode* node = [[VariableNode alloc] initWithXMLNode:elm];
397 [variables addObject:[node autorelease]];
398 }
399 }
400
401 frame.variables = variables;
402 };
403 [_client sendCommandWithFormat:@"context_get -d %d -c %d" handler:handler, frame.index, cid];
404 }
405 }
406
407 @end