Finished enough of the basic refactoring to get things working.
[macgdbp.git] / Source / DebuggerProcessor.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2007 - 2010, 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 "DebuggerProcessor.h"
18
19 #import "AppDelegate.h"
20
21 // GDBpConnection (Private) ////////////////////////////////////////////////////
22
23 @interface DebuggerProcessor ()
24 @property (readwrite, copy) NSString* status;
25
26 - (void)updateStatus:(NSXMLDocument*)response;
27 - (void)debuggerStep:(NSXMLDocument*)response;
28 - (void)rebuildStack:(NSXMLDocument*)response;
29 - (void)getStackFrame:(NSXMLDocument*)response;
30 - (void)setSource:(NSXMLDocument*)response;
31 - (void)contextsReceived:(NSXMLDocument*)response;
32 - (void)variablesReceived:(NSXMLDocument*)response;
33 - (void)propertiesReceived:(NSXMLDocument*)response;
34
35 @end
36
37 // GDBpConnection //////////////////////////////////////////////////////////////
38
39 @implementation DebuggerProcessor
40
41 @synthesize status;
42 @synthesize delegate;
43
44 /**
45 * Creates a new DebuggerConnection and initializes the socket from the given connection
46 * paramters.
47 */
48 - (id)initWithPort:(NSUInteger)aPort
49 {
50 if (self = [super init])
51 {
52 stackFrames_ = [[NSMutableDictionary alloc] init];
53 callbackContext_ = [NSMutableDictionary new];
54
55 [[BreakpointManager sharedManager] setConnection:self];
56 connection_ = [[DebuggerConnection alloc] initWithPort:aPort];
57 connection_.delegate = self;
58 [connection_ connect];
59 }
60 return self;
61 }
62
63 /**
64 * Deallocates the object
65 */
66 - (void)dealloc
67 {
68 [connection_ close];
69 [stackFrames_ release];
70 [callbackContext_ release];
71 [super dealloc];
72 }
73
74
75 // Getters /////////////////////////////////////////////////////////////////////
76 #pragma mark Getters
77
78 /**
79 * Gets the port number
80 */
81 - (NSUInteger)port
82 {
83 return [connection_ port];
84 }
85
86 /**
87 * Returns the name of the remote host
88 */
89 - (NSString*)remoteHost
90 {
91 if (![connection_ connected])
92 return @"(DISCONNECTED)";
93
94 // TODO: Either impl or remove.
95 return @"";
96 }
97
98 /**
99 * Returns whether or not we have an active connection
100 */
101 - (BOOL)isConnected
102 {
103 return [connection_ connected];
104 }
105
106 // Commands ////////////////////////////////////////////////////////////////////
107 #pragma mark Commands
108
109 /**
110 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
111 * created every time you want to debug a page
112 */
113 - (void)reconnect
114 {
115 [connection_ close];
116 self.status = @"Connecting";
117 [connection_ connect];
118 }
119
120 /**
121 * Tells the debugger to continue running the script. Returns the current stack frame.
122 */
123 - (void)run
124 {
125 [connection_ sendCommandWithCallback:@selector(debuggerStep:) format:@"run"];
126 }
127
128 /**
129 * Tells the debugger to step into the current command.
130 */
131 - (void)stepIn
132 {
133 [connection_ sendCommandWithCallback:@selector(debuggerStep:) format:@"step_into"];
134 }
135
136 /**
137 * Tells the debugger to step out of the current context
138 */
139 - (void)stepOut
140 {
141 [connection_ sendCommandWithCallback:@selector(debuggerStep:) format:@"step_out"];
142 }
143
144 /**
145 * Tells the debugger to step over the current function
146 */
147 - (void)stepOver
148 {
149 [connection_ sendCommandWithCallback:@selector(debuggerStep:) format:@"step_over"];
150 }
151
152 /**
153 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
154 * that requested it so that the child can be attached.
155 */
156 - (NSInteger)getProperty:(NSString*)property
157 {
158 [connection_ sendCommandWithCallback:@selector(propertiesReceived:) format:@"property_get -n \"%@\"", property];
159 }
160
161 // Breakpoint Management ///////////////////////////////////////////////////////
162 #pragma mark Breakpoints
163
164 /**
165 * Send an add breakpoint command
166 */
167 - (void)addBreakpoint:(Breakpoint*)bp
168 {
169 if (![connection_ connected])
170 return;
171
172 NSString* file = [connection_ escapedURIPath:[bp transformedPath]];
173 NSNumber* transaction = [connection_ sendCommandWithCallback:@selector(breakpointReceived:)
174 format:@"breakpoint_set -t line -f %@ -n %i", file, [bp line]];
175 [callbackContext_ setObject:bp forKey:transaction];
176 }
177
178 /**
179 * Removes a breakpoint
180 */
181 - (void)removeBreakpoint:(Breakpoint*)bp
182 {
183 if (![connection_ connected])
184 return;
185
186 [connection_ sendCommandWithCallback:nil format:@"breakpoint_remove -d %i", [bp debuggerId]];
187 }
188
189 // Specific Response Handlers //////////////////////////////////////////////////
190 #pragma mark Response Handlers
191
192 /**
193 * Initial packet received. We've started a brand-new connection to the engine.
194 */
195 - (void)handleInitialResponse:(NSXMLDocument*)response
196 {
197 // Register any breakpoints that exist offline.
198 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
199 [self addBreakpoint:bp];
200
201 // Load the debugger to make it look active.
202 [delegate debuggerConnected];
203
204 // TODO: update the status.
205 }
206
207 /**
208 * Receiver for status updates. This just freshens up the UI.
209 */
210 - (void)updateStatus:(NSXMLDocument*)response
211 {
212 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
213 if (status == nil || [status isEqualToString:@"Stopped"] || [status isEqualToString:@"Stopping"])
214 {
215 [connection_ close];
216 [delegate debuggerDisconnected];
217
218 self.status = @"Stopped";
219 }
220 }
221
222 /**
223 * Step in/out/over and run all take this path. We first get the status of the
224 * debugger and then request fresh stack information.
225 */
226 - (void)debuggerStep:(NSXMLDocument*)response
227 {
228 [self updateStatus:response];
229 if (![connection_ connected])
230 return;
231
232 // If this is the run command, tell the delegate that a bunch of updates
233 // are coming. Also remove all existing stack routes and request a new stack.
234 // TODO: figure out if we can not clobber the stack every time.
235 NSString* command = [[[response rootElement] attributeForName:@"command"] stringValue];
236 if (YES || [command isEqualToString:@"run"])
237 {
238 if ([delegate respondsToSelector:@selector(clobberStack)])
239 [delegate clobberStack];
240 [stackFrames_ removeAllObjects];
241 stackFirstTransactionID_ = [[connection_ sendCommandWithCallback:@selector(rebuildStack:) format:@"stack_depth"] intValue];
242 }
243 }
244
245 /**
246 * We ask for the stack_depth and now we clobber the stack and start rebuilding
247 * it.
248 */
249 - (void)rebuildStack:(NSXMLDocument*)response
250 {
251 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
252
253 if (stackFirstTransactionID_ == [connection_ transactionIDFromResponse:response])
254 stackDepth_ = depth;
255
256 // We now need to alloc a bunch of stack frames and get the basic information
257 // for them.
258 for (NSInteger i = 0; i < depth; i++)
259 {
260 // Use the transaction ID to create a routing path.
261 NSNumber* routingID = [connection_ sendCommandWithCallback:@selector(getStackFrame:) format:@"stack_get -d %d", i];
262 [stackFrames_ setObject:[StackFrame alloc] forKey:routingID];
263 }
264 }
265
266 /**
267 * The initial rebuild of the stack frame. We now have enough to initialize
268 * a StackFrame object.
269 */
270 - (void)getStackFrame:(NSXMLDocument*)response
271 {
272 // Get the routing information.
273 NSInteger routingID = [connection_ transactionIDFromResponse:response];
274 if (routingID < stackFirstTransactionID_)
275 return;
276 NSNumber* routingNumber = [NSNumber numberWithInt:routingID];
277
278 // Make sure we initialized this frame in our last |-rebuildStack:|.
279 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
280 if (!frame)
281 return;
282
283 NSXMLElement* xmlframe = [[[response rootElement] children] objectAtIndex:0];
284
285 // Initialize the stack frame.
286 [frame initWithIndex:[[[xmlframe attributeForName:@"level"] stringValue] intValue]
287 withFilename:[[xmlframe attributeForName:@"filename"] stringValue]
288 withSource:nil
289 atLine:[[[xmlframe attributeForName:@"lineno"] stringValue] intValue]
290 inFunction:[[xmlframe attributeForName:@"where"] stringValue]
291 withVariables:nil];
292
293 // Get the source code of the file. Escape % in URL chars.
294 NSString* escapedFilename = [frame.filename stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
295 NSNumber* transaction = [connection_ sendCommandWithCallback:@selector(setSource:) format:@"source -f %@", escapedFilename];
296 [callbackContext_ setObject:routingNumber forKey:transaction];
297
298 // Get the names of all the contexts.
299 transaction = [connection_ sendCommandWithCallback:@selector(contextsReceived:) format:@"context_names -d %d", frame.index];
300 [callbackContext_ setObject:routingNumber forKey:transaction];
301
302 if ([delegate respondsToSelector:@selector(newStackFrame:)])
303 [delegate newStackFrame:frame];
304 }
305
306 /**
307 * Callback for setting the source of a file while rebuilding a specific stack
308 * frame.
309 */
310 - (void)setSource:(NSXMLDocument*)response
311 {
312 NSNumber* transaction = [NSNumber numberWithInt:[connection_ transactionIDFromResponse:response]];
313 if ([transaction intValue] < stackFirstTransactionID_)
314 return;
315 NSNumber* routingNumber = [callbackContext_ objectForKey:transaction];
316 if (!routingNumber)
317 return;
318
319 [callbackContext_ removeObjectForKey:transaction];
320 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
321 if (!frame)
322 return;
323
324 frame.source = [[response rootElement] value];
325
326 if ([delegate respondsToSelector:@selector(sourceUpdated:)])
327 [delegate sourceUpdated:frame];
328 }
329
330 /**
331 * Enumerates all the contexts of a given stack frame. We then in turn get the
332 * contents of each one of these contexts.
333 */
334 - (void)contextsReceived:(NSXMLDocument*)response
335 {
336 // Get the stack frame's routing ID and use it again.
337 NSNumber* receivedTransaction = [NSNumber numberWithInt:[connection_ transactionIDFromResponse:response]];
338 if ([receivedTransaction intValue] < stackFirstTransactionID_)
339 return;
340 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
341 if (!routingID)
342 return;
343
344 // Get the stack frame by the |routingID|.
345 StackFrame* frame = [stackFrames_ objectForKey:routingID];
346
347 NSXMLElement* contextNames = [response rootElement];
348 for (NSXMLElement* context in [contextNames children])
349 {
350 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
351
352 // Fetch each context's variables.
353 NSNumber* transaction = [connection_ sendCommandWithCallback:@selector(variablesReceived:)
354 format:@"context_get -d %d -c %d", frame.index, cid];
355 [callbackContext_ setObject:routingID forKey:transaction];
356 }
357 }
358
359 /**
360 * Receives the variables from the context and attaches them to the stack frame.
361 */
362 - (void)variablesReceived:(NSXMLDocument*)response
363 {
364 // Get the stack frame's routing ID and use it again.
365 NSInteger transaction = [connection_ transactionIDFromResponse:response];
366 if (transaction < stackFirstTransactionID_)
367 return;
368 NSNumber* receivedTransaction = [NSNumber numberWithInt:transaction];
369 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
370 if (!routingID)
371 return;
372
373 // Get the stack frame by the |routingID|.
374 StackFrame* frame = [stackFrames_ objectForKey:routingID];
375
376 NSMutableArray* variables = [NSMutableArray array];
377
378 // Merge the frame's existing variables.
379 if (frame.variables)
380 [variables addObjectsFromArray:frame.variables];
381
382 // Add these new variables.
383 NSArray* addVariables = [[response rootElement] children];
384 if (addVariables)
385 [variables addObjectsFromArray:addVariables];
386
387 frame.variables = variables;
388 }
389
390 /**
391 * Callback from a |-getProperty:| request.
392 */
393 - (void)propertiesReceived:(NSXMLDocument*)response
394 {
395 NSInteger transaction = [connection_ transactionIDFromResponse:response];
396
397 /*
398 <response>
399 <property> <!-- this is the one we requested -->
400 <property ... /> <!-- these are what we want -->
401 </property>
402 </repsonse>
403 */
404
405 // Detach all the children so we can insert them into another document.
406 NSXMLElement* parent = (NSXMLElement*)[[response rootElement] childAtIndex:0];
407 NSArray* children = [parent children];
408 [parent setChildren:nil];
409
410 [delegate receivedProperties:children forTransaction:transaction];
411 }
412
413 /**
414 * Callback for setting a breakpoint.
415 */
416 - (void)breakpointReceived:(NSXMLDocument*)response
417 {
418 NSNumber* transaction = [NSNumber numberWithInt:[connection_ transactionIDFromResponse:response]];
419 Breakpoint* bp = [callbackContext_ objectForKey:transaction];
420 if (!bp)
421 return;
422
423 [callbackContext_ removeObjectForKey:callbackContext_];
424 [bp setDebuggerId:[[[[response rootElement] attributeForName:@"id"] stringValue] intValue]];
425 }
426
427 @end