Put the listening port information into the status display.
[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)initWithModel:(DebuggerModel*)model
32 port:(NSUInteger)aPort
33 autoAttach:(BOOL)doAttach
34 {
35 if (self = [super init]) {
36 _model = model;
37 _port = aPort;
38 _client = [[ProtocolClient alloc] initWithDelegate:self];
39
40 [self setAutoAttach:doAttach];
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 [self doConnect];
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 NSInteger totalChildren = [[[parent attributeForName:@"numchildren"] stringValue] integerValue];
183 if ([newLoadedData count] < (NSUInteger)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 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
206 [bp setDebuggerId:[[[[message rootElement] attributeForName:@"id"] stringValue] intValue]];
207 };
208 if (bp.type == kBreakpointTypeFile) {
209 NSString* file = [ProtocolClient escapedFilePathURI:[bp transformedPath]];
210 [_client sendCommandWithFormat:@"breakpoint_set -t line -f %@ -n %i" handler:handler, file, [bp line]];
211 } else if (bp.type == kBreakpointTypeFunctionEntry) {
212 [_client sendCommandWithFormat:@"breakpoint_set -t call -m %@" handler:handler, bp.functionName];
213 }
214 }
215
216 /**
217 * Removes a breakpoint
218 */
219 - (void)removeBreakpoint:(Breakpoint*)bp {
220 if (!self.model.connected)
221 return;
222
223 [_client sendCommandWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]];
224 }
225
226 /**
227 * Sends a string to be evaluated by the engine.
228 */
229 - (void)evalScript:(NSString*)str callback:(void (^)(NSString*))callback {
230 if (!self.model.connected)
231 return;
232
233 NSData* stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
234 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
235 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
236 NSString* value = [parent base64DecodedValue];
237 callback(value);
238 };
239 [_client sendCustomCommandWithFormat:@"eval -i {txn} -- %@"
240 handler:handler, [stringData base64EncodedStringWithOptions:0]];
241 }
242
243 // Protocol Client Delegate ////////////////////////////////////////////////////
244 #pragma mark Protocol Client Delegate
245
246 - (void)debuggerEngineConnected:(ProtocolClient*)client {
247 [_model onNewConnection];
248 }
249
250 /**
251 * Called when the connection is finally closed. This will reopen the listening
252 * socket if the debugger remains attached.
253 */
254 - (void)debuggerEngineDisconnected:(ProtocolClient*)client {
255 [_model onDisconnect];
256
257 if (self.autoAttach)
258 [self doConnect];
259 }
260
261 - (void)protocolClient:(ProtocolClient*)client receivedInitialMessage:(NSXMLDocument*)message {
262 [self handleInitialResponse:message];
263 }
264
265 - (void)protocolClient:(ProtocolClient*)client receivedErrorMessage:(NSXMLDocument*)message {
266 NSArray* error = [[message rootElement] elementsForName:@"error"];
267 if ([error count] > 0) {
268 NSLog(@"Xdebug error: %@", error);
269 NSString* errorMessage = [[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue];
270 _model.lastError = errorMessage;
271 }
272 }
273
274 // Specific Response Handlers //////////////////////////////////////////////////
275 #pragma mark Response Handlers
276
277 /**
278 * Initial packet received. We've started a brand-new connection to the engine.
279 */
280 - (void)handleInitialResponse:(NSXMLDocument*)response {
281 if (!self.autoAttach) {
282 [_client sendCommandWithFormat:@"detach"];
283 return;
284 }
285
286 // Register any breakpoints that exist offline.
287 for (Breakpoint* bp in self.model.breakpointManager.breakpoints)
288 [self addBreakpoint:bp];
289
290 // TODO: update the status.
291 }
292
293 /**
294 * Receiver for status updates. This just freshens up the UI.
295 */
296 - (void)updateStatus:(NSXMLDocument*)response {
297 NSString* status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
298 self.model.status = status;
299 if (!status || [status isEqualToString:@"Stopped"]) {
300 [_model onDisconnect];
301 } else if ([status isEqualToString:@"Stopping"]) {
302 [_client sendCommandWithFormat:@"stop"];
303 }
304 }
305
306 /**
307 * Step in/out/over and run all take this path. We first get the status of the
308 * debugger and then request fresh stack information.
309 */
310 - (void)debuggerStep:(NSXMLDocument*)response {
311 [self updateStatus:response];
312 if (!self.model.connected)
313 return;
314
315 [_client sendCommandWithFormat:@"stack_depth" handler:^(NSXMLDocument* message) {
316 [self rebuildStack:message];
317 }];
318 }
319
320 /**
321 * We ask for the stack_depth and now we clobber the stack and start rebuilding
322 * it.
323 */
324 - (void)rebuildStack:(NSXMLDocument*)response {
325 NSUInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
326
327 // Send a request to get each frame of the stack, which will be added to this
328 // array. When the final frame arrives, the |tempStack| is released.
329 __block NSMutableArray* tempStack = [[NSMutableArray alloc] init];
330
331 for (NSUInteger i = 0; i < depth; ++i) {
332 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
333 [tempStack addObject:[self transformXMLToStackFrame:message]];
334 if (i == depth - 1) {
335 [self.model updateStack:[tempStack autorelease]];
336 }
337 };
338 [_client sendCommandWithFormat:@"stack_get -d %d" handler:handler, i];
339 }
340 }
341
342 /**
343 * Creates a StackFrame object from an NSXMLDocument response from the "stack_get"
344 * command.
345 */
346 - (StackFrame*)transformXMLToStackFrame:(NSXMLDocument*)response {
347 NSXMLElement* xmlframe = (NSXMLElement*)[[[response rootElement] children] objectAtIndex:0];
348 StackFrame* frame = [[[StackFrame alloc] init] autorelease];
349 frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue];
350 frame.filename = [[xmlframe attributeForName:@"filename"] stringValue];
351 frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue];
352 frame.function = [[xmlframe attributeForName:@"where"] stringValue];
353 return frame;
354 }
355
356 /**
357 * Enumerates all the contexts of a given stack frame. We then in turn get the
358 * contents of each one of these contexts.
359 */
360 - (void)loadContexts:(NSXMLDocument*)response forFrame:(StackFrame*)frame {
361 NSXMLElement* contextNames = [response rootElement];
362 for (NSXMLElement* context in [contextNames children]) {
363 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
364
365 // Fetch each context's variables.
366 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
367 NSMutableArray* variables = [NSMutableArray array];
368
369 // Merge the frame's existing variables.
370 if (frame.variables)
371 [variables addObjectsFromArray:frame.variables];
372
373 // Add these new variables.
374 NSArray* addVariables = [[message rootElement] children];
375 if (addVariables) {
376 for (NSXMLElement* elm in addVariables) {
377 VariableNode* node = [[VariableNode alloc] initWithXMLNode:elm];
378 [variables addObject:[node autorelease]];
379 }
380 }
381
382 frame.variables = variables;
383 };
384 [_client sendCommandWithFormat:@"context_get -d %d -c %d" handler:handler, frame.index, cid];
385 }
386 }
387
388 // Private /////////////////////////////////////////////////////////////////////
389 #pragma mark Private
390
391 - (void)doConnect {
392 [_client connectOnPort:_port];
393 [_model onListeningOnPort:_port];
394 }
395
396 @end