1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a debug client base class. |
|
8 """ |
|
9 |
|
10 import sys |
|
11 import socket |
|
12 import select |
|
13 import codeop |
|
14 import traceback |
|
15 import os |
|
16 import time |
|
17 import imp |
|
18 import re |
|
19 import atexit |
|
20 import signal |
|
21 |
|
22 |
|
23 import DebugClientCapabilities |
|
24 from DebugBase import setRecursionLimit, printerr # __IGNORE_WARNING__ |
|
25 from AsyncFile import AsyncFile, AsyncPendingWrite |
|
26 from DebugConfig import ConfigVarTypeStrings |
|
27 from FlexCompleter import Completer |
|
28 from DebugUtilities import getargvalues, formatargvalues, prepareJsonCommand |
|
29 import DebugVariables |
|
30 |
|
31 |
|
32 DebugClientInstance = None |
|
33 |
|
34 ############################################################################### |
|
35 |
|
36 |
|
37 def DebugClientInput(prompt="", echo=True): |
|
38 """ |
|
39 Replacement for the standard input builtin. |
|
40 |
|
41 This function works with the split debugger. |
|
42 |
|
43 @param prompt prompt to be shown (string) |
|
44 @param echo flag indicating to echo the output (boolean) |
|
45 @return result of the input() call |
|
46 """ |
|
47 if DebugClientInstance is None or not DebugClientInstance.redirect: |
|
48 return DebugClientOrigInput(prompt) |
|
49 |
|
50 return DebugClientInstance.input(prompt, echo) |
|
51 |
|
52 # Use our own input(). |
|
53 try: |
|
54 DebugClientOrigInput = __builtins__.__dict__['input'] |
|
55 __builtins__.__dict__['input'] = DebugClientInput |
|
56 except (AttributeError, KeyError): |
|
57 import __main__ |
|
58 DebugClientOrigInput = __main__.__builtins__.__dict__['input'] |
|
59 __main__.__builtins__.__dict__['input'] = DebugClientInput |
|
60 |
|
61 ############################################################################### |
|
62 |
|
63 |
|
64 def DebugClientFork(): |
|
65 """ |
|
66 Replacement for the standard os.fork(). |
|
67 |
|
68 @return result of the fork() call |
|
69 """ |
|
70 if DebugClientInstance is None: |
|
71 return DebugClientOrigFork() |
|
72 |
|
73 return DebugClientInstance.fork() |
|
74 |
|
75 # use our own fork(). |
|
76 if 'fork' in dir(os): |
|
77 DebugClientOrigFork = os.fork |
|
78 os.fork = DebugClientFork |
|
79 |
|
80 ############################################################################### |
|
81 |
|
82 |
|
83 def DebugClientClose(fd): |
|
84 """ |
|
85 Replacement for the standard os.close(fd). |
|
86 |
|
87 @param fd open file descriptor to be closed (integer) |
|
88 """ |
|
89 if DebugClientInstance is None: |
|
90 DebugClientOrigClose(fd) |
|
91 |
|
92 DebugClientInstance.close(fd) |
|
93 |
|
94 # use our own close(). |
|
95 if 'close' in dir(os): |
|
96 DebugClientOrigClose = os.close |
|
97 os.close = DebugClientClose |
|
98 |
|
99 ############################################################################### |
|
100 |
|
101 |
|
102 def DebugClientSetRecursionLimit(limit): |
|
103 """ |
|
104 Replacement for the standard sys.setrecursionlimit(limit). |
|
105 |
|
106 @param limit recursion limit (integer) |
|
107 """ |
|
108 rl = max(limit, 64) |
|
109 setRecursionLimit(rl) |
|
110 DebugClientOrigSetRecursionLimit(rl + 64) |
|
111 |
|
112 # use our own setrecursionlimit(). |
|
113 if 'setrecursionlimit' in dir(sys): |
|
114 DebugClientOrigSetRecursionLimit = sys.setrecursionlimit |
|
115 sys.setrecursionlimit = DebugClientSetRecursionLimit |
|
116 DebugClientSetRecursionLimit(sys.getrecursionlimit()) |
|
117 |
|
118 ############################################################################### |
|
119 |
|
120 |
|
121 class DebugClientBase(object): |
|
122 """ |
|
123 Class implementing the client side of the debugger. |
|
124 |
|
125 It provides access to the Python interpeter from a debugger running in |
|
126 another process. |
|
127 |
|
128 The protocol between the debugger and the client is based on JSONRPC 2.0 |
|
129 PDUs. Each one is sent on a single line, i.e. commands or responses are |
|
130 separated by a linefeed character. |
|
131 |
|
132 If the debugger closes the session there is no response from the client. |
|
133 The client may close the session at any time as a result of the script |
|
134 being debugged closing or crashing. |
|
135 |
|
136 <b>Note</b>: This class is meant to be subclassed by individual |
|
137 DebugClient classes. Do not instantiate it directly. |
|
138 """ |
|
139 clientCapabilities = DebugClientCapabilities.HasAll |
|
140 |
|
141 # keep these in sync with VariablesViewer.VariableItem.Indicators |
|
142 Indicators = ("()", "[]", "{:}", "{}") # __IGNORE_WARNING__ |
|
143 |
|
144 def __init__(self): |
|
145 """ |
|
146 Constructor |
|
147 """ |
|
148 self.breakpoints = {} |
|
149 self.redirect = True |
|
150 self.__receiveBuffer = "" |
|
151 |
|
152 # The next couple of members are needed for the threaded version. |
|
153 # For this base class they contain static values for the non threaded |
|
154 # debugger |
|
155 |
|
156 # dictionary of all threads running |
|
157 self.threads = {} |
|
158 |
|
159 # the "current" thread, basically the thread we are at a breakpoint |
|
160 # for. |
|
161 self.currentThread = self |
|
162 |
|
163 # special objects representing the main scripts thread and frame |
|
164 self.mainThread = self |
|
165 self.mainFrame = None |
|
166 self.framenr = 0 |
|
167 |
|
168 # The context to run the debugged program in. |
|
169 self.debugMod = imp.new_module('__main__') |
|
170 self.debugMod.__dict__['__builtins__'] = __builtins__ |
|
171 |
|
172 # The list of complete lines to execute. |
|
173 self.buffer = '' |
|
174 |
|
175 # The list of regexp objects to filter variables against |
|
176 self.globalsFilterObjects = [] |
|
177 self.localsFilterObjects = [] |
|
178 |
|
179 self._fncache = {} |
|
180 self.dircache = [] |
|
181 self.passive = False # used to indicate the passive mode |
|
182 self.running = None |
|
183 self.test = None |
|
184 self.tracePython = False |
|
185 self.debugging = False |
|
186 |
|
187 self.fork_auto = False |
|
188 self.fork_child = False |
|
189 |
|
190 self.readstream = None |
|
191 self.writestream = None |
|
192 self.errorstream = None |
|
193 self.pollingDisabled = False |
|
194 |
|
195 self.callTraceEnabled = False |
|
196 self.__newCallTraceEnabled = False |
|
197 |
|
198 self.skipdirs = sys.path[:] |
|
199 |
|
200 self.variant = 'You should not see this' |
|
201 |
|
202 # commandline completion stuff |
|
203 self.complete = Completer(self.debugMod.__dict__).complete |
|
204 |
|
205 self.compile_command = codeop.CommandCompiler() |
|
206 |
|
207 self.coding_re = re.compile(r"coding[:=]\s*([-\w_.]+)") |
|
208 self.defaultCoding = 'utf-8' |
|
209 self.__coding = self.defaultCoding |
|
210 self.noencoding = False |
|
211 |
|
212 def getCoding(self): |
|
213 """ |
|
214 Public method to return the current coding. |
|
215 |
|
216 @return codec name (string) |
|
217 """ |
|
218 return self.__coding |
|
219 |
|
220 def __setCoding(self, filename): |
|
221 """ |
|
222 Private method to set the coding used by a python file. |
|
223 |
|
224 @param filename name of the file to inspect (string) |
|
225 """ |
|
226 if self.noencoding: |
|
227 self.__coding = sys.getdefaultencoding() |
|
228 else: |
|
229 default = 'utf-8' |
|
230 try: |
|
231 f = open(filename, 'rb') |
|
232 # read the first and second line |
|
233 text = f.readline() |
|
234 text = "{0}{1}".format(text, f.readline()) |
|
235 f.close() |
|
236 except IOError: |
|
237 self.__coding = default |
|
238 return |
|
239 |
|
240 for l in text.splitlines(): |
|
241 m = self.coding_re.search(l) |
|
242 if m: |
|
243 self.__coding = m.group(1) |
|
244 return |
|
245 self.__coding = default |
|
246 |
|
247 def attachThread(self, target=None, args=None, kwargs=None, |
|
248 mainThread=False): |
|
249 """ |
|
250 Public method to setup a thread for DebugClient to debug. |
|
251 |
|
252 If mainThread is True, then we are attaching to the already |
|
253 started mainthread of the app and the rest of the args are ignored. |
|
254 |
|
255 @param target the start function of the target thread (i.e. the |
|
256 user code) |
|
257 @param args arguments to pass to target |
|
258 @param kwargs keyword arguments to pass to target |
|
259 @param mainThread True, if we are attaching to the already |
|
260 started mainthread of the app |
|
261 """ |
|
262 if self.debugging: |
|
263 sys.setprofile(self.profile) |
|
264 |
|
265 def __dumpThreadList(self): |
|
266 """ |
|
267 Private method to send the list of threads. |
|
268 """ |
|
269 threadList = [] |
|
270 if self.threads and self.currentThread: |
|
271 # indication for the threaded debugger |
|
272 currentId = self.currentThread.get_ident() |
|
273 for t in self.threads.values(): |
|
274 d = {} |
|
275 d["id"] = t.get_ident() |
|
276 d["name"] = t.get_name() |
|
277 d["broken"] = t.isBroken() |
|
278 threadList.append(d) |
|
279 else: |
|
280 currentId = -1 |
|
281 d = {} |
|
282 d["id"] = -1 |
|
283 d["name"] = "MainThread" |
|
284 if hasattr(self, "isBroken"): |
|
285 d["broken"] = self.isBroken() |
|
286 else: |
|
287 d["broken"] = False |
|
288 threadList.append(d) |
|
289 |
|
290 self.sendJsonCommand("ResponseThreadList", { |
|
291 "currentID": currentId, |
|
292 "threadList": threadList, |
|
293 }) |
|
294 |
|
295 def input(self, prompt, echo=True): |
|
296 """ |
|
297 Public method to implement input() using the event loop. |
|
298 |
|
299 @param prompt the prompt to be shown (string) |
|
300 @param echo Flag indicating echoing of the input (boolean) |
|
301 @return the entered string |
|
302 """ |
|
303 self.sendJsonCommand("RequestRaw", { |
|
304 "prompt": prompt, |
|
305 "echo": echo, |
|
306 }) |
|
307 self.eventLoop(True) |
|
308 return self.rawLine |
|
309 |
|
310 def sessionClose(self, exit=True): |
|
311 """ |
|
312 Public method to close the session with the debugger and optionally |
|
313 terminate. |
|
314 |
|
315 @param exit flag indicating to terminate (boolean) |
|
316 """ |
|
317 try: |
|
318 self.set_quit() |
|
319 except Exception: |
|
320 pass |
|
321 |
|
322 self.debugging = False |
|
323 |
|
324 # make sure we close down our end of the socket |
|
325 # might be overkill as normally stdin, stdout and stderr |
|
326 # SHOULD be closed on exit, but it does not hurt to do it here |
|
327 self.readstream.close(True) |
|
328 self.writestream.close(True) |
|
329 self.errorstream.close(True) |
|
330 |
|
331 if exit: |
|
332 # Ok, go away. |
|
333 sys.exit() |
|
334 |
|
335 def __compileFileSource(self, filename, mode='exec'): |
|
336 """ |
|
337 Private method to compile source code read from a file. |
|
338 |
|
339 @param filename name of the source file (string) |
|
340 @param mode kind of code to be generated (string, exec or eval) |
|
341 @return compiled code object (None in case of errors) |
|
342 """ |
|
343 with open(filename, encoding=self.__coding) as fp: |
|
344 statement = fp.read() |
|
345 |
|
346 try: |
|
347 code = compile(statement + '\n', filename, mode) |
|
348 except SyntaxError: |
|
349 exctype, excval, exctb = sys.exc_info() |
|
350 try: |
|
351 message = str(excval) |
|
352 filename = excval.filename |
|
353 lineno = excval.lineno |
|
354 charno = excval.offset |
|
355 if charno is None: |
|
356 charno = 0 |
|
357 except (AttributeError, ValueError): |
|
358 message = "" |
|
359 filename = "" |
|
360 lineno = 0 |
|
361 charno = 0 |
|
362 self.sendSyntaxError(message, filename, lineno, charno) |
|
363 return None |
|
364 |
|
365 return code |
|
366 |
|
367 def handleLine(self, line): |
|
368 """ |
|
369 Public method to handle the receipt of a complete line. |
|
370 |
|
371 It first looks for a valid protocol token at the start of the line. |
|
372 Thereafter it trys to execute the lines accumulated so far. |
|
373 |
|
374 @param line the received line |
|
375 """ |
|
376 # Remove any newline. |
|
377 if line[-1] == '\n': |
|
378 line = line[:-1] |
|
379 |
|
380 ## printerr(line) ##debug |
|
381 |
|
382 self.handleJsonCommand(line) |
|
383 |
|
384 def handleJsonCommand(self, jsonStr): |
|
385 """ |
|
386 Public method to handle a command serialized as a JSON string. |
|
387 |
|
388 @param jsonStr string containing the command received from the IDE |
|
389 @type str |
|
390 """ |
|
391 import json |
|
392 |
|
393 try: |
|
394 commandDict = json.loads(jsonStr.strip()) |
|
395 except (TypeError, ValueError) as err: |
|
396 printerr(str(err)) |
|
397 return |
|
398 |
|
399 method = commandDict["method"] |
|
400 params = commandDict["params"] |
|
401 |
|
402 if method == "RequestVariables": |
|
403 self.__dumpVariables( |
|
404 params["frameNumber"], params["scope"], params["filters"]) |
|
405 |
|
406 elif method == "RequestVariable": |
|
407 self.__dumpVariable( |
|
408 params["variable"], params["frameNumber"], |
|
409 params["scope"], params["filters"]) |
|
410 |
|
411 elif method == "RequestThreadList": |
|
412 self.__dumpThreadList() |
|
413 |
|
414 elif method == "RequestThreadSet": |
|
415 if params["threadID"] in self.threads: |
|
416 self.setCurrentThread(params["threadID"]) |
|
417 self.sendJsonCommand("ResponseThreadSet", {}) |
|
418 stack = self.currentThread.getStack() |
|
419 self.sendJsonCommand("ResponseStack", { |
|
420 "stack": stack, |
|
421 }) |
|
422 |
|
423 elif method == "RequestCapabilities": |
|
424 self.sendJsonCommand("ResponseCapabilities", { |
|
425 "capabilities": self.__clientCapabilities(), |
|
426 "clientType": "Python3" |
|
427 }) |
|
428 |
|
429 elif method == "RequestBanner": |
|
430 self.sendJsonCommand("ResponseBanner", { |
|
431 "version": "Python {0}".format(sys.version), |
|
432 "platform": socket.gethostname(), |
|
433 "dbgclient": self.variant, |
|
434 }) |
|
435 |
|
436 elif method == "RequestSetFilter": |
|
437 self.__generateFilterObjects(params["scope"], params["filter"]) |
|
438 |
|
439 elif method == "RequestCallTrace": |
|
440 if self.debugging: |
|
441 self.callTraceEnabled = params["enable"] |
|
442 else: |
|
443 self.__newCallTraceEnabled = params["enable"] |
|
444 # remember for later |
|
445 |
|
446 elif method == "RequestEnvironment": |
|
447 for key, value in params["environment"].items(): |
|
448 if key.endswith("+"): |
|
449 if key[:-1] in os.environ: |
|
450 os.environ[key[:-1]] += value |
|
451 else: |
|
452 os.environ[key[:-1]] = value |
|
453 else: |
|
454 os.environ[key] = value |
|
455 |
|
456 elif method == "RequestLoad": |
|
457 self._fncache = {} |
|
458 self.dircache = [] |
|
459 sys.argv = [] |
|
460 self.__setCoding(params["filename"]) |
|
461 sys.argv.append(params["filename"]) |
|
462 sys.argv.extend(params["argv"]) |
|
463 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0])) |
|
464 if params["workdir"] == '': |
|
465 os.chdir(sys.path[1]) |
|
466 else: |
|
467 os.chdir(params["workdir"]) |
|
468 |
|
469 self.running = sys.argv[0] |
|
470 self.mainFrame = None |
|
471 self.debugging = True |
|
472 |
|
473 self.fork_auto = params["autofork"] |
|
474 self.fork_child = params["forkChild"] |
|
475 |
|
476 self.threads.clear() |
|
477 self.attachThread(mainThread=True) |
|
478 |
|
479 # set the system exception handling function to ensure, that |
|
480 # we report on all unhandled exceptions |
|
481 sys.excepthook = self.__unhandled_exception |
|
482 self.__interceptSignals() |
|
483 |
|
484 # clear all old breakpoints, they'll get set after we have |
|
485 # started |
|
486 self.mainThread.clear_all_breaks() |
|
487 |
|
488 self.mainThread.tracePython = params["traceInterpreter"] |
|
489 |
|
490 # This will eventually enter a local event loop. |
|
491 self.debugMod.__dict__['__file__'] = self.running |
|
492 sys.modules['__main__'] = self.debugMod |
|
493 code = self.__compileFileSource(self.running) |
|
494 if code: |
|
495 self.callTraceEnabled = self.__newCallTraceEnabled |
|
496 res = self.mainThread.run(code, self.debugMod.__dict__) |
|
497 self.progTerminated(res) |
|
498 |
|
499 elif method == "RequestRun": |
|
500 sys.argv = [] |
|
501 self.__setCoding(params["filename"]) |
|
502 sys.argv.append(params["filename"]) |
|
503 sys.argv.extend(params["argv"]) |
|
504 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0])) |
|
505 if params["workdir"] == '': |
|
506 os.chdir(sys.path[1]) |
|
507 else: |
|
508 os.chdir(params["workdir"]) |
|
509 |
|
510 self.running = sys.argv[0] |
|
511 self.mainFrame = None |
|
512 self.botframe = None |
|
513 |
|
514 self.fork_auto = params["autofork"] |
|
515 self.fork_child = params["forkChild"] |
|
516 |
|
517 self.threads.clear() |
|
518 self.attachThread(mainThread=True) |
|
519 |
|
520 # set the system exception handling function to ensure, that |
|
521 # we report on all unhandled exceptions |
|
522 sys.excepthook = self.__unhandled_exception |
|
523 self.__interceptSignals() |
|
524 |
|
525 self.mainThread.tracePython = False |
|
526 |
|
527 self.debugMod.__dict__['__file__'] = sys.argv[0] |
|
528 sys.modules['__main__'] = self.debugMod |
|
529 res = 0 |
|
530 code = self.__compileFileSource(self.running) |
|
531 if code: |
|
532 try: |
|
533 exec(code, self.debugMod.__dict__) |
|
534 except SystemExit as exc: |
|
535 res = exc.code |
|
536 atexit._run_exitfuncs() |
|
537 self.writestream.flush() |
|
538 self.progTerminated(res) |
|
539 |
|
540 elif method == "RequestCoverage": |
|
541 from coverage import coverage |
|
542 sys.argv = [] |
|
543 self.__setCoding(params["filename"]) |
|
544 sys.argv.append(params["filename"]) |
|
545 sys.argv.extend(params["argv"]) |
|
546 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0])) |
|
547 if params["workdir"] == '': |
|
548 os.chdir(sys.path[1]) |
|
549 else: |
|
550 os.chdir(params["workdir"]) |
|
551 |
|
552 # set the system exception handling function to ensure, that |
|
553 # we report on all unhandled exceptions |
|
554 sys.excepthook = self.__unhandled_exception |
|
555 self.__interceptSignals() |
|
556 |
|
557 # generate a coverage object |
|
558 self.cover = coverage( |
|
559 auto_data=True, |
|
560 data_file="{0}.coverage".format( |
|
561 os.path.splitext(sys.argv[0])[0])) |
|
562 |
|
563 if params["erase"]: |
|
564 self.cover.erase() |
|
565 sys.modules['__main__'] = self.debugMod |
|
566 self.debugMod.__dict__['__file__'] = sys.argv[0] |
|
567 fp = open(sys.argv[0], encoding=self.__coding) |
|
568 try: |
|
569 script = fp.read() |
|
570 finally: |
|
571 fp.close() |
|
572 if script: |
|
573 if not script.endswith('\n'): |
|
574 script += '\n' |
|
575 code = compile(script, sys.argv[0], 'exec') |
|
576 self.running = sys.argv[0] |
|
577 res = 0 |
|
578 self.cover.start() |
|
579 try: |
|
580 exec(code, self.debugMod.__dict__) |
|
581 except SystemExit as exc: |
|
582 res = exc.code |
|
583 atexit._run_exitfuncs() |
|
584 self.cover.stop() |
|
585 self.cover.save() |
|
586 self.writestream.flush() |
|
587 self.progTerminated(res) |
|
588 |
|
589 elif method == "RequestProfile": |
|
590 sys.setprofile(None) |
|
591 import PyProfile |
|
592 sys.argv = [] |
|
593 self.__setCoding(params["filename"]) |
|
594 sys.argv.append(params["filename"]) |
|
595 sys.argv.extend(params["argv"]) |
|
596 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0])) |
|
597 if params["workdir"] == '': |
|
598 os.chdir(sys.path[1]) |
|
599 else: |
|
600 os.chdir(params["workdir"]) |
|
601 |
|
602 # set the system exception handling function to ensure, that |
|
603 # we report on all unhandled exceptions |
|
604 sys.excepthook = self.__unhandled_exception |
|
605 self.__interceptSignals() |
|
606 |
|
607 # generate a profile object |
|
608 self.prof = PyProfile.PyProfile(sys.argv[0]) |
|
609 |
|
610 if params["erase"]: |
|
611 self.prof.erase() |
|
612 self.debugMod.__dict__['__file__'] = sys.argv[0] |
|
613 sys.modules['__main__'] = self.debugMod |
|
614 fp = open(sys.argv[0], encoding=self.__coding) |
|
615 try: |
|
616 script = fp.read() |
|
617 finally: |
|
618 fp.close() |
|
619 if script: |
|
620 if not script.endswith('\n'): |
|
621 script += '\n' |
|
622 self.running = sys.argv[0] |
|
623 res = 0 |
|
624 try: |
|
625 self.prof.run(script) |
|
626 except SystemExit as exc: |
|
627 res = exc.code |
|
628 atexit._run_exitfuncs() |
|
629 self.prof.save() |
|
630 self.writestream.flush() |
|
631 self.progTerminated(res) |
|
632 |
|
633 elif method == "ExecuteStatement": |
|
634 if self.buffer: |
|
635 self.buffer = self.buffer + '\n' + params["statement"] |
|
636 else: |
|
637 self.buffer = params["statement"] |
|
638 |
|
639 try: |
|
640 code = self.compile_command(self.buffer, self.readstream.name) |
|
641 except (OverflowError, SyntaxError, ValueError): |
|
642 # Report the exception |
|
643 sys.last_type, sys.last_value, sys.last_traceback = \ |
|
644 sys.exc_info() |
|
645 self.sendJsonCommand("ClientOutput", { |
|
646 "text": "".join(traceback.format_exception_only( |
|
647 sys.last_type, sys.last_value)) |
|
648 }) |
|
649 self.buffer = '' |
|
650 else: |
|
651 if code is None: |
|
652 self.sendJsonCommand("ResponseContinue", {}) |
|
653 return |
|
654 else: |
|
655 self.buffer = '' |
|
656 |
|
657 try: |
|
658 if self.running is None: |
|
659 exec(code, self.debugMod.__dict__) |
|
660 else: |
|
661 if self.currentThread is None: |
|
662 # program has terminated |
|
663 self.running = None |
|
664 _globals = self.debugMod.__dict__ |
|
665 _locals = _globals |
|
666 else: |
|
667 cf = self.currentThread.getCurrentFrame() |
|
668 # program has terminated |
|
669 if cf is None: |
|
670 self.running = None |
|
671 _globals = self.debugMod.__dict__ |
|
672 _locals = _globals |
|
673 else: |
|
674 frmnr = self.framenr |
|
675 while cf is not None and frmnr > 0: |
|
676 cf = cf.f_back |
|
677 frmnr -= 1 |
|
678 _globals = cf.f_globals |
|
679 _locals = \ |
|
680 self.currentThread.getFrameLocals( |
|
681 self.framenr) |
|
682 # reset sys.stdout to our redirector |
|
683 # (unconditionally) |
|
684 if "sys" in _globals: |
|
685 __stdout = _globals["sys"].stdout |
|
686 _globals["sys"].stdout = self.writestream |
|
687 exec(code, _globals, _locals) |
|
688 _globals["sys"].stdout = __stdout |
|
689 elif "sys" in _locals: |
|
690 __stdout = _locals["sys"].stdout |
|
691 _locals["sys"].stdout = self.writestream |
|
692 exec(code, _globals, _locals) |
|
693 _locals["sys"].stdout = __stdout |
|
694 else: |
|
695 exec(code, _globals, _locals) |
|
696 |
|
697 self.currentThread.storeFrameLocals(self.framenr) |
|
698 except SystemExit as exc: |
|
699 self.progTerminated(exc.code) |
|
700 except Exception: |
|
701 # Report the exception and the traceback |
|
702 tlist = [] |
|
703 try: |
|
704 exc_type, exc_value, exc_tb = sys.exc_info() |
|
705 sys.last_type = exc_type |
|
706 sys.last_value = exc_value |
|
707 sys.last_traceback = exc_tb |
|
708 tblist = traceback.extract_tb(exc_tb) |
|
709 del tblist[:1] |
|
710 tlist = traceback.format_list(tblist) |
|
711 if tlist: |
|
712 tlist.insert( |
|
713 0, "Traceback (innermost last):\n") |
|
714 tlist.extend(traceback.format_exception_only( |
|
715 exc_type, exc_value)) |
|
716 finally: |
|
717 tblist = exc_tb = None |
|
718 |
|
719 self.sendJsonCommand("ClientOutput", { |
|
720 "text": "".join(tlist) |
|
721 }) |
|
722 |
|
723 self.sendJsonCommand("ResponseOK", {}) |
|
724 |
|
725 elif method == "RequestStep": |
|
726 self.currentThread.step(True) |
|
727 self.eventExit = True |
|
728 |
|
729 elif method == "RequestStepOver": |
|
730 self.currentThread.step(False) |
|
731 self.eventExit = True |
|
732 |
|
733 elif method == "RequestStepOut": |
|
734 self.currentThread.stepOut() |
|
735 self.eventExit = True |
|
736 |
|
737 elif method == "RequestStepQuit": |
|
738 if self.passive: |
|
739 self.progTerminated(42) |
|
740 else: |
|
741 self.set_quit() |
|
742 self.eventExit = True |
|
743 |
|
744 elif method == "RequestContinue": |
|
745 self.currentThread.go(params["special"]) |
|
746 self.eventExit = True |
|
747 |
|
748 elif method == "RawInput": |
|
749 # If we are handling raw mode input then break out of the current |
|
750 # event loop. |
|
751 self.rawLine = params["input"] |
|
752 self.eventExit = True |
|
753 |
|
754 elif method == "RequestBreakpoint": |
|
755 if params["setBreakpoint"]: |
|
756 if params["condition"] in ['None', '']: |
|
757 params["condition"] = None |
|
758 elif params["condition"] is not None: |
|
759 try: |
|
760 compile(params["condition"], '<string>', 'eval') |
|
761 except SyntaxError: |
|
762 self.sendJsonCommand("ResponseBPConditionError", { |
|
763 "filename": params["filename"], |
|
764 "line": params["line"], |
|
765 }) |
|
766 return |
|
767 self.mainThread.set_break( |
|
768 params["filename"], params["line"], params["temporary"], |
|
769 params["condition"]) |
|
770 else: |
|
771 self.mainThread.clear_break(params["filename"], params["line"]) |
|
772 |
|
773 elif method == "RequestBreakpointEnable": |
|
774 bp = self.mainThread.get_break(params["filename"], params["line"]) |
|
775 if bp is not None: |
|
776 if params["enable"]: |
|
777 bp.enable() |
|
778 else: |
|
779 bp.disable() |
|
780 |
|
781 elif method == "RequestBreakpointIgnore": |
|
782 bp = self.mainThread.get_break(params["filename"], params["line"]) |
|
783 if bp is not None: |
|
784 bp.ignore = params["count"] |
|
785 |
|
786 elif method == "RequestWatch": |
|
787 if params["setWatch"]: |
|
788 if not params["condition"].endswith( |
|
789 ('??created??', '??changed??')): |
|
790 try: |
|
791 compile(params["condition"], '<string>', 'eval') |
|
792 except SyntaxError: |
|
793 self.sendJsonCommand("ResponseWatchConditionError", { |
|
794 "condition": params["condition"], |
|
795 }) |
|
796 return |
|
797 self.mainThread.set_watch( |
|
798 params["condition"], params["temporary"]) |
|
799 else: |
|
800 self.mainThread.clear_watch(params["condition"]) |
|
801 |
|
802 elif method == "RequestWatchEnable": |
|
803 wp = self.mainThread.get_watch(params["condition"]) |
|
804 if wp is not None: |
|
805 if params["enable"]: |
|
806 wp.enable() |
|
807 else: |
|
808 wp.disable() |
|
809 |
|
810 elif method == "RequestWatchIgnore": |
|
811 wp = self.mainThread.get_watch(params["condition"]) |
|
812 if wp is not None: |
|
813 wp.ignore = params["count"] |
|
814 |
|
815 elif method == "RequestShutdown": |
|
816 self.sessionClose() |
|
817 |
|
818 elif method == "RequestCompletion": |
|
819 self.__completionList(params["text"]) |
|
820 |
|
821 elif method == "RequestUTPrepare": |
|
822 sys.path.insert( |
|
823 0, os.path.dirname(os.path.abspath(params["filename"]))) |
|
824 os.chdir(sys.path[0]) |
|
825 |
|
826 # set the system exception handling function to ensure, that |
|
827 # we report on all unhandled exceptions |
|
828 sys.excepthook = self.__unhandled_exception |
|
829 self.__interceptSignals() |
|
830 |
|
831 try: |
|
832 import unittest |
|
833 utModule = imp.load_source( |
|
834 params["testname"], params["filename"]) |
|
835 try: |
|
836 if params["failed"]: |
|
837 self.test = unittest.defaultTestLoader\ |
|
838 .loadTestsFromNames(params["failed"], utModule) |
|
839 else: |
|
840 self.test = unittest.defaultTestLoader\ |
|
841 .loadTestsFromName(params["testfunctionname"], |
|
842 utModule) |
|
843 except AttributeError: |
|
844 self.test = unittest.defaultTestLoader\ |
|
845 .loadTestsFromModule(utModule) |
|
846 except Exception: |
|
847 exc_type, exc_value, exc_tb = sys.exc_info() |
|
848 self.sendJsonCommand("ResponseUTPrepared", { |
|
849 "count": 0, |
|
850 "exception": exc_type.__name__, |
|
851 "message": str(exc_value), |
|
852 }) |
|
853 return |
|
854 |
|
855 # generate a coverage object |
|
856 if params["coverage"]: |
|
857 from coverage import coverage |
|
858 self.cover = coverage( |
|
859 auto_data=True, |
|
860 data_file="{0}.coverage".format( |
|
861 os.path.splitext(params["coveragefile"])[0])) |
|
862 if params["coverageerase"]: |
|
863 self.cover.erase() |
|
864 else: |
|
865 self.cover = None |
|
866 |
|
867 self.sendJsonCommand("ResponseUTPrepared", { |
|
868 "count": self.test.countTestCases(), |
|
869 "exception": "", |
|
870 "message": "", |
|
871 }) |
|
872 |
|
873 elif method == "RequestUTRun": |
|
874 from DCTestResult import DCTestResult |
|
875 self.testResult = DCTestResult(self) |
|
876 if self.cover: |
|
877 self.cover.start() |
|
878 self.test.run(self.testResult) |
|
879 if self.cover: |
|
880 self.cover.stop() |
|
881 self.cover.save() |
|
882 self.sendJsonCommand("ResponseUTFinished", {}) |
|
883 |
|
884 elif method == "RequestUTStop": |
|
885 self.testResult.stop() |
|
886 |
|
887 elif method == "ResponseForkTo": |
|
888 # this results from a separate event loop |
|
889 self.fork_child = (params["target"] == 'child') |
|
890 self.eventExit = True |
|
891 |
|
892 def sendJsonCommand(self, method, params): |
|
893 """ |
|
894 Public method to send a single command or response to the IDE. |
|
895 |
|
896 @param method command or response command name to be sent |
|
897 @type str |
|
898 @param params dictionary of named parameters for the command or |
|
899 response |
|
900 @type dict |
|
901 """ |
|
902 cmd = prepareJsonCommand(method, params) |
|
903 |
|
904 self.writestream.write_p(cmd) |
|
905 self.writestream.flush() |
|
906 |
|
907 def sendClearTemporaryBreakpoint(self, filename, lineno): |
|
908 """ |
|
909 Public method to signal the deletion of a temporary breakpoint. |
|
910 |
|
911 @param filename name of the file the bp belongs to |
|
912 @type str |
|
913 @param lineno linenumber of the bp |
|
914 @type int |
|
915 """ |
|
916 self.sendJsonCommand("ResponseClearBreakpoint", { |
|
917 "filename": filename, |
|
918 "line": lineno |
|
919 }) |
|
920 |
|
921 def sendClearTemporaryWatch(self, condition): |
|
922 """ |
|
923 Public method to signal the deletion of a temporary watch expression. |
|
924 |
|
925 @param condition condition of the watch expression to be cleared |
|
926 @type str |
|
927 """ |
|
928 self.sendJsonCommand("ResponseClearWatch", { |
|
929 "condition": condition, |
|
930 }) |
|
931 |
|
932 def sendResponseLine(self, stack): |
|
933 """ |
|
934 Public method to send the current call stack. |
|
935 |
|
936 @param stack call stack |
|
937 @type list |
|
938 """ |
|
939 self.sendJsonCommand("ResponseLine", { |
|
940 "stack": stack, |
|
941 }) |
|
942 |
|
943 def sendCallTrace(self, event, fromInfo, toInfo): |
|
944 """ |
|
945 Public method to send a call trace entry. |
|
946 |
|
947 @param event trace event (call or return) |
|
948 @type str |
|
949 @param fromInfo dictionary containing the origin info |
|
950 @type dict with 'filename', 'linenumber' and 'codename' |
|
951 as keys |
|
952 @param toInfo dictionary containing the target info |
|
953 @type dict with 'filename', 'linenumber' and 'codename' |
|
954 as keys |
|
955 """ |
|
956 self.sendJsonCommand("CallTrace", { |
|
957 "event": event[0], |
|
958 "from": fromInfo, |
|
959 "to": toInfo, |
|
960 }) |
|
961 |
|
962 def sendException(self, exceptionType, exceptionMessage, stack): |
|
963 """ |
|
964 Public method to send information for an exception. |
|
965 |
|
966 @param exceptionType type of exception raised |
|
967 @type str |
|
968 @param exceptionMessage message of the exception |
|
969 @type str |
|
970 @param stack stack trace information |
|
971 @type list |
|
972 """ |
|
973 self.sendJsonCommand("ResponseException", { |
|
974 "type": exceptionType, |
|
975 "message": exceptionMessage, |
|
976 "stack": stack, |
|
977 }) |
|
978 |
|
979 def sendSyntaxError(self, message, filename, lineno, charno): |
|
980 """ |
|
981 Public method to send information for a syntax error. |
|
982 |
|
983 @param message syntax error message |
|
984 @type str |
|
985 @param filename name of the faulty file |
|
986 @type str |
|
987 @param lineno line number info |
|
988 @type int |
|
989 @param charno character number info |
|
990 @type int |
|
991 """ |
|
992 self.sendJsonCommand("ResponseSyntax", { |
|
993 "message": message, |
|
994 "filename": filename, |
|
995 "linenumber": lineno, |
|
996 "characternumber": charno, |
|
997 }) |
|
998 |
|
999 def sendPassiveStartup(self, filename, exceptions): |
|
1000 """ |
|
1001 Public method to send the passive start information. |
|
1002 |
|
1003 @param filename name of the script |
|
1004 @type str |
|
1005 @param exceptions flag to enable exception reporting of the IDE |
|
1006 @type bool |
|
1007 """ |
|
1008 self.sendJsonCommand("PassiveStartup", { |
|
1009 "filename": filename, |
|
1010 "exceptions": exceptions, |
|
1011 }) |
|
1012 |
|
1013 def __clientCapabilities(self): |
|
1014 """ |
|
1015 Private method to determine the clients capabilities. |
|
1016 |
|
1017 @return client capabilities (integer) |
|
1018 """ |
|
1019 try: |
|
1020 import PyProfile # __IGNORE_WARNING__ |
|
1021 try: |
|
1022 del sys.modules['PyProfile'] |
|
1023 except KeyError: |
|
1024 pass |
|
1025 return self.clientCapabilities |
|
1026 except ImportError: |
|
1027 return ( |
|
1028 self.clientCapabilities & ~DebugClientCapabilities.HasProfiler) |
|
1029 |
|
1030 def readReady(self, stream): |
|
1031 """ |
|
1032 Public method called when there is data ready to be read. |
|
1033 |
|
1034 @param stream file like object that has data to be written |
|
1035 """ |
|
1036 try: |
|
1037 got = stream.readline_p() |
|
1038 except Exception: |
|
1039 return |
|
1040 |
|
1041 if len(got) == 0: |
|
1042 self.sessionClose() |
|
1043 return |
|
1044 |
|
1045 self.__receiveBuffer = self.__receiveBuffer + got |
|
1046 |
|
1047 # Call handleLine for the line if it is complete. |
|
1048 eol = self.__receiveBuffer.find('\n') |
|
1049 while eol >= 0: |
|
1050 line = self.__receiveBuffer[:eol + 1] |
|
1051 self.__receiveBuffer = self.__receiveBuffer[eol + 1:] |
|
1052 self.handleLine(line) |
|
1053 eol = self.__receiveBuffer.find('\n') |
|
1054 |
|
1055 def writeReady(self, stream): |
|
1056 """ |
|
1057 Public method called when we are ready to write data. |
|
1058 |
|
1059 @param stream file like object that has data to be written |
|
1060 """ |
|
1061 stream.write_p("") |
|
1062 stream.flush() |
|
1063 |
|
1064 def __interact(self): |
|
1065 """ |
|
1066 Private method to interact with the debugger. |
|
1067 """ |
|
1068 global DebugClientInstance |
|
1069 |
|
1070 DebugClientInstance = self |
|
1071 self.__receiveBuffer = "" |
|
1072 |
|
1073 if not self.passive: |
|
1074 # At this point simulate an event loop. |
|
1075 self.eventLoop() |
|
1076 |
|
1077 def eventLoop(self, disablePolling=False): |
|
1078 """ |
|
1079 Public method implementing our event loop. |
|
1080 |
|
1081 @param disablePolling flag indicating to enter an event loop with |
|
1082 polling disabled (boolean) |
|
1083 """ |
|
1084 self.eventExit = None |
|
1085 self.pollingDisabled = disablePolling |
|
1086 |
|
1087 while self.eventExit is None: |
|
1088 wrdy = [] |
|
1089 |
|
1090 if self.writestream.nWriteErrors > self.writestream.maxtries: |
|
1091 break |
|
1092 |
|
1093 if AsyncPendingWrite(self.writestream): |
|
1094 wrdy.append(self.writestream) |
|
1095 |
|
1096 if AsyncPendingWrite(self.errorstream): |
|
1097 wrdy.append(self.errorstream) |
|
1098 |
|
1099 try: |
|
1100 rrdy, wrdy, xrdy = select.select([self.readstream], wrdy, []) |
|
1101 except (select.error, KeyboardInterrupt, socket.error): |
|
1102 # just carry on |
|
1103 continue |
|
1104 |
|
1105 if self.readstream in rrdy: |
|
1106 self.readReady(self.readstream) |
|
1107 |
|
1108 if self.writestream in wrdy: |
|
1109 self.writeReady(self.writestream) |
|
1110 |
|
1111 if self.errorstream in wrdy: |
|
1112 self.writeReady(self.errorstream) |
|
1113 |
|
1114 self.eventExit = None |
|
1115 self.pollingDisabled = False |
|
1116 |
|
1117 def eventPoll(self): |
|
1118 """ |
|
1119 Public method to poll for events like 'set break point'. |
|
1120 """ |
|
1121 if self.pollingDisabled: |
|
1122 return |
|
1123 |
|
1124 # the choice of a ~0.5 second poll interval is arbitrary. |
|
1125 lasteventpolltime = getattr(self, 'lasteventpolltime', time.time()) |
|
1126 now = time.time() |
|
1127 if now - lasteventpolltime < 0.5: |
|
1128 self.lasteventpolltime = lasteventpolltime |
|
1129 return |
|
1130 else: |
|
1131 self.lasteventpolltime = now |
|
1132 |
|
1133 wrdy = [] |
|
1134 if AsyncPendingWrite(self.writestream): |
|
1135 wrdy.append(self.writestream) |
|
1136 |
|
1137 if AsyncPendingWrite(self.errorstream): |
|
1138 wrdy.append(self.errorstream) |
|
1139 |
|
1140 # immediate return if nothing is ready. |
|
1141 try: |
|
1142 rrdy, wrdy, xrdy = select.select([self.readstream], wrdy, [], 0) |
|
1143 except (select.error, KeyboardInterrupt, socket.error): |
|
1144 return |
|
1145 |
|
1146 if self.readstream in rrdy: |
|
1147 self.readReady(self.readstream.fileno()) |
|
1148 |
|
1149 if self.writestream in wrdy: |
|
1150 self.writeReady(self.writestream) |
|
1151 |
|
1152 if self.errorstream in wrdy: |
|
1153 self.writeReady(self.errorstream) |
|
1154 |
|
1155 def connectDebugger(self, port, remoteAddress=None, redirect=True): |
|
1156 """ |
|
1157 Public method to establish a session with the debugger. |
|
1158 |
|
1159 It opens a network connection to the debugger, connects it to stdin, |
|
1160 stdout and stderr and saves these file objects in case the application |
|
1161 being debugged redirects them itself. |
|
1162 |
|
1163 @param port the port number to connect to (int) |
|
1164 @param remoteAddress the network address of the debug server host |
|
1165 (string) |
|
1166 @param redirect flag indicating redirection of stdin, stdout and |
|
1167 stderr (boolean) |
|
1168 """ |
|
1169 if remoteAddress is None: |
|
1170 remoteAddress = "127.0.0.1" |
|
1171 elif "@@i" in remoteAddress: |
|
1172 remoteAddress = remoteAddress.split("@@i")[0] |
|
1173 sock = socket.create_connection((remoteAddress, port)) |
|
1174 |
|
1175 self.readstream = AsyncFile(sock, sys.stdin.mode, sys.stdin.name) |
|
1176 self.writestream = AsyncFile(sock, sys.stdout.mode, sys.stdout.name) |
|
1177 self.errorstream = AsyncFile(sock, sys.stderr.mode, sys.stderr.name) |
|
1178 |
|
1179 if redirect: |
|
1180 sys.stdin = self.readstream |
|
1181 sys.stdout = self.writestream |
|
1182 sys.stderr = self.errorstream |
|
1183 self.redirect = redirect |
|
1184 |
|
1185 # attach to the main thread here |
|
1186 self.attachThread(mainThread=True) |
|
1187 |
|
1188 def __unhandled_exception(self, exctype, excval, exctb): |
|
1189 """ |
|
1190 Private method called to report an uncaught exception. |
|
1191 |
|
1192 @param exctype the type of the exception |
|
1193 @param excval data about the exception |
|
1194 @param exctb traceback for the exception |
|
1195 """ |
|
1196 self.mainThread.user_exception(None, (exctype, excval, exctb), True) |
|
1197 |
|
1198 def __interceptSignals(self): |
|
1199 """ |
|
1200 Private method to intercept common signals. |
|
1201 """ |
|
1202 for signum in [ |
|
1203 signal.SIGABRT, # abnormal termination |
|
1204 signal.SIGFPE, # floating point exception |
|
1205 signal.SIGILL, # illegal instruction |
|
1206 signal.SIGSEGV, # segmentation violation |
|
1207 ]: |
|
1208 signal.signal(signum, self.__signalHandler) |
|
1209 |
|
1210 def __signalHandler(self, signalNumber, stackFrame): |
|
1211 """ |
|
1212 Private method to handle signals. |
|
1213 |
|
1214 @param signalNumber number of the signal to be handled |
|
1215 @type int |
|
1216 @param stackFrame current stack frame |
|
1217 @type frame object |
|
1218 """ |
|
1219 if signalNumber == signal.SIGABRT: |
|
1220 message = "Abnormal Termination" |
|
1221 elif signalNumber == signal.SIGFPE: |
|
1222 message = "Floating Point Exception" |
|
1223 elif signalNumber == signal.SIGILL: |
|
1224 message = "Illegal Instruction" |
|
1225 elif signalNumber == signal.SIGSEGV: |
|
1226 message = "Segmentation Violation" |
|
1227 else: |
|
1228 message = "Unknown Signal '{0}'".format(signalNumber) |
|
1229 |
|
1230 filename = self.absPath(stackFrame) |
|
1231 |
|
1232 linenr = stackFrame.f_lineno |
|
1233 ffunc = stackFrame.f_code.co_name |
|
1234 |
|
1235 if ffunc == '?': |
|
1236 ffunc = '' |
|
1237 |
|
1238 if ffunc and not ffunc.startswith("<"): |
|
1239 argInfo = getargvalues(stackFrame) |
|
1240 try: |
|
1241 fargs = formatargvalues( |
|
1242 argInfo.args, argInfo.varargs, |
|
1243 argInfo.keywords, argInfo.locals) |
|
1244 except Exception: |
|
1245 fargs = "" |
|
1246 else: |
|
1247 fargs = "" |
|
1248 |
|
1249 self.sendJsonCommand("ResponseSignal", { |
|
1250 "message": message, |
|
1251 "filename": filename, |
|
1252 "linenumber": linenr, |
|
1253 "function": ffunc, |
|
1254 "arguments": fargs, |
|
1255 }) |
|
1256 |
|
1257 def absPath(self, fn): |
|
1258 """ |
|
1259 Public method to convert a filename to an absolute name. |
|
1260 |
|
1261 sys.path is used as a set of possible prefixes. The name stays |
|
1262 relative if a file could not be found. |
|
1263 |
|
1264 @param fn filename (string) |
|
1265 @return the converted filename (string) |
|
1266 """ |
|
1267 if os.path.isabs(fn): |
|
1268 return fn |
|
1269 |
|
1270 # Check the cache. |
|
1271 if fn in self._fncache: |
|
1272 return self._fncache[fn] |
|
1273 |
|
1274 # Search sys.path. |
|
1275 for p in sys.path: |
|
1276 afn = os.path.abspath(os.path.join(p, fn)) |
|
1277 nafn = os.path.normcase(afn) |
|
1278 |
|
1279 if os.path.exists(nafn): |
|
1280 self._fncache[fn] = afn |
|
1281 d = os.path.dirname(afn) |
|
1282 if (d not in sys.path) and (d not in self.dircache): |
|
1283 self.dircache.append(d) |
|
1284 return afn |
|
1285 |
|
1286 # Search the additional directory cache |
|
1287 for p in self.dircache: |
|
1288 afn = os.path.abspath(os.path.join(p, fn)) |
|
1289 nafn = os.path.normcase(afn) |
|
1290 |
|
1291 if os.path.exists(nafn): |
|
1292 self._fncache[fn] = afn |
|
1293 return afn |
|
1294 |
|
1295 # Nothing found. |
|
1296 return fn |
|
1297 |
|
1298 def shouldSkip(self, fn): |
|
1299 """ |
|
1300 Public method to check if a file should be skipped. |
|
1301 |
|
1302 @param fn filename to be checked |
|
1303 @return non-zero if fn represents a file we are 'skipping', |
|
1304 zero otherwise. |
|
1305 """ |
|
1306 if self.mainThread.tracePython: # trace into Python library |
|
1307 return False |
|
1308 |
|
1309 # Eliminate anything that is part of the Python installation. |
|
1310 afn = self.absPath(fn) |
|
1311 for d in self.skipdirs: |
|
1312 if afn.startswith(d): |
|
1313 return True |
|
1314 |
|
1315 # special treatment for paths containing site-packages or dist-packages |
|
1316 for part in ["site-packages", "dist-packages"]: |
|
1317 if part in afn: |
|
1318 return True |
|
1319 |
|
1320 return False |
|
1321 |
|
1322 def getRunning(self): |
|
1323 """ |
|
1324 Public method to return the main script we are currently running. |
|
1325 |
|
1326 @return flag indicating a running debug session (boolean) |
|
1327 """ |
|
1328 return self.running |
|
1329 |
|
1330 def progTerminated(self, status, message=""): |
|
1331 """ |
|
1332 Public method to tell the debugger that the program has terminated. |
|
1333 |
|
1334 @param status return status |
|
1335 @type int |
|
1336 @param message status message |
|
1337 @type str |
|
1338 """ |
|
1339 if status is None: |
|
1340 status = 0 |
|
1341 elif not isinstance(status, int): |
|
1342 message = str(status) |
|
1343 status = 1 |
|
1344 |
|
1345 if self.running: |
|
1346 self.set_quit() |
|
1347 self.running = None |
|
1348 self.sendJsonCommand("ResponseExit", { |
|
1349 "status": status, |
|
1350 "message": message, |
|
1351 }) |
|
1352 |
|
1353 # reset coding |
|
1354 self.__coding = self.defaultCoding |
|
1355 |
|
1356 def __dumpVariables(self, frmnr, scope, filter): |
|
1357 """ |
|
1358 Private method to return the variables of a frame to the debug server. |
|
1359 |
|
1360 @param frmnr distance of frame reported on. 0 is the current frame |
|
1361 (int) |
|
1362 @param scope 1 to report global variables, 0 for local variables (int) |
|
1363 @param filter the indices of variable types to be filtered |
|
1364 (list of int) |
|
1365 """ |
|
1366 if self.currentThread is None: |
|
1367 return |
|
1368 |
|
1369 if scope == 0: |
|
1370 self.framenr = frmnr |
|
1371 |
|
1372 f = self.currentThread.getCurrentFrame() |
|
1373 |
|
1374 while f is not None and frmnr > 0: |
|
1375 f = f.f_back |
|
1376 frmnr -= 1 |
|
1377 |
|
1378 if f is None: |
|
1379 if scope: |
|
1380 dict = self.debugMod.__dict__ |
|
1381 else: |
|
1382 scope = -1 |
|
1383 elif scope: |
|
1384 dict = f.f_globals |
|
1385 elif f.f_globals is f.f_locals: |
|
1386 scope = -1 |
|
1387 else: |
|
1388 dict = f.f_locals |
|
1389 |
|
1390 varlist = [] |
|
1391 |
|
1392 if scope != -1: |
|
1393 keylist = dict.keys() |
|
1394 |
|
1395 vlist = self.__formatVariablesList(keylist, dict, scope, filter) |
|
1396 varlist.extend(vlist) |
|
1397 |
|
1398 self.sendJsonCommand("ResponseVariables", { |
|
1399 "scope": scope, |
|
1400 "variables": varlist, |
|
1401 }) |
|
1402 |
|
1403 def __dumpVariable(self, var, frmnr, scope, filter): |
|
1404 """ |
|
1405 Private method to return the variables of a frame to the debug server. |
|
1406 |
|
1407 @param var list encoded name of the requested variable |
|
1408 (list of strings) |
|
1409 @param frmnr distance of frame reported on. 0 is the current frame |
|
1410 (int) |
|
1411 @param scope 1 to report global variables, 0 for local variables (int) |
|
1412 @param filter the indices of variable types to be filtered |
|
1413 (list of int) |
|
1414 """ |
|
1415 if self.currentThread is None: |
|
1416 return |
|
1417 |
|
1418 f = self.currentThread.getCurrentFrame() |
|
1419 |
|
1420 while f is not None and frmnr > 0: |
|
1421 f = f.f_back |
|
1422 frmnr -= 1 |
|
1423 |
|
1424 if f is None: |
|
1425 if scope: |
|
1426 dict = self.debugMod.__dict__ |
|
1427 else: |
|
1428 scope = -1 |
|
1429 elif scope: |
|
1430 dict = f.f_globals |
|
1431 elif f.f_globals is f.f_locals: |
|
1432 scope = -1 |
|
1433 else: |
|
1434 dict = f.f_locals |
|
1435 |
|
1436 varlist = [] |
|
1437 |
|
1438 if scope != -1: |
|
1439 variable = dict |
|
1440 for attribute in var: |
|
1441 attribute = self.__extractIndicators(attribute)[0] |
|
1442 typeObject, typeName, typeStr, resolver = \ |
|
1443 DebugVariables.getType(variable) |
|
1444 if resolver: |
|
1445 variable = resolver.resolve(variable, attribute) |
|
1446 if variable is None: |
|
1447 break |
|
1448 else: |
|
1449 break |
|
1450 if variable is not None: |
|
1451 typeObject, typeName, typeStr, resolver = \ |
|
1452 DebugVariables.getType(variable) |
|
1453 if typeStr.startswith(("PyQt5.", "PyQt4.")): |
|
1454 vlist = self.__formatQtVariable(variable, typeName) |
|
1455 varlist.extend(vlist) |
|
1456 elif resolver: |
|
1457 dict = resolver.getDictionary(variable) |
|
1458 vlist = self.__formatVariablesList( |
|
1459 list(dict.keys()), dict, scope, filter) |
|
1460 varlist.extend(vlist) |
|
1461 |
|
1462 self.sendJsonCommand("ResponseVariable", { |
|
1463 "scope": scope, |
|
1464 "variable": var, |
|
1465 "variables": varlist, |
|
1466 }) |
|
1467 |
|
1468 def __extractIndicators(self, var): |
|
1469 """ |
|
1470 Private method to extract the indicator string from a variable text. |
|
1471 |
|
1472 @param var variable text |
|
1473 @type str |
|
1474 @return tuple containing the variable text without indicators and the |
|
1475 indicator string |
|
1476 @rtype tuple of two str |
|
1477 """ |
|
1478 for indicator in DebugClientBase.Indicators: |
|
1479 if var.endswith(indicator): |
|
1480 return var[:-len(indicator)], indicator |
|
1481 |
|
1482 return var, "" |
|
1483 |
|
1484 def __formatQtVariable(self, value, qttype): |
|
1485 """ |
|
1486 Private method to produce a formatted output of a simple Qt4/Qt5 type. |
|
1487 |
|
1488 @param value variable to be formatted |
|
1489 @param qttype type of the Qt variable to be formatted (string) |
|
1490 @return A tuple consisting of a list of formatted variables. Each |
|
1491 variable entry is a tuple of three elements, the variable name, |
|
1492 its type and value. |
|
1493 """ |
|
1494 varlist = [] |
|
1495 if qttype == 'QChar': |
|
1496 varlist.append(("", "QChar", "{0}".format(chr(value.unicode())))) |
|
1497 varlist.append(("", "int", "{0:d}".format(value.unicode()))) |
|
1498 elif qttype == 'QByteArray': |
|
1499 varlist.append( |
|
1500 ("bytes", "QByteArray", "{0}".format(bytes(value))[2:-1])) |
|
1501 varlist.append( |
|
1502 ("hex", "QByteArray", "{0}".format(value.toHex())[2:-1])) |
|
1503 varlist.append( |
|
1504 ("base64", "QByteArray", "{0}".format(value.toBase64())[2:-1])) |
|
1505 varlist.append(("percent encoding", "QByteArray", |
|
1506 "{0}".format(value.toPercentEncoding())[2:-1])) |
|
1507 elif qttype == 'QPoint': |
|
1508 varlist.append(("x", "int", "{0:d}".format(value.x()))) |
|
1509 varlist.append(("y", "int", "{0:d}".format(value.y()))) |
|
1510 elif qttype == 'QPointF': |
|
1511 varlist.append(("x", "float", "{0:g}".format(value.x()))) |
|
1512 varlist.append(("y", "float", "{0:g}".format(value.y()))) |
|
1513 elif qttype == 'QRect': |
|
1514 varlist.append(("x", "int", "{0:d}".format(value.x()))) |
|
1515 varlist.append(("y", "int", "{0:d}".format(value.y()))) |
|
1516 varlist.append(("width", "int", "{0:d}".format(value.width()))) |
|
1517 varlist.append(("height", "int", "{0:d}".format(value.height()))) |
|
1518 elif qttype == 'QRectF': |
|
1519 varlist.append(("x", "float", "{0:g}".format(value.x()))) |
|
1520 varlist.append(("y", "float", "{0:g}".format(value.y()))) |
|
1521 varlist.append(("width", "float", "{0:g}".format(value.width()))) |
|
1522 varlist.append(("height", "float", "{0:g}".format(value.height()))) |
|
1523 elif qttype == 'QSize': |
|
1524 varlist.append(("width", "int", "{0:d}".format(value.width()))) |
|
1525 varlist.append(("height", "int", "{0:d}".format(value.height()))) |
|
1526 elif qttype == 'QSizeF': |
|
1527 varlist.append(("width", "float", "{0:g}".format(value.width()))) |
|
1528 varlist.append(("height", "float", "{0:g}".format(value.height()))) |
|
1529 elif qttype == 'QColor': |
|
1530 varlist.append(("name", "str", "{0}".format(value.name()))) |
|
1531 r, g, b, a = value.getRgb() |
|
1532 varlist.append( |
|
1533 ("rgba", "int", |
|
1534 "{0:d}, {1:d}, {2:d}, {3:d}".format(r, g, b, a))) |
|
1535 h, s, v, a = value.getHsv() |
|
1536 varlist.append( |
|
1537 ("hsva", "int", |
|
1538 "{0:d}, {1:d}, {2:d}, {3:d}".format(h, s, v, a))) |
|
1539 c, m, y, k, a = value.getCmyk() |
|
1540 varlist.append( |
|
1541 ("cmyka", "int", |
|
1542 "{0:d}, {1:d}, {2:d}, {3:d}, {4:d}".format(c, m, y, k, a))) |
|
1543 elif qttype == 'QDate': |
|
1544 varlist.append(("", "QDate", "{0}".format(value.toString()))) |
|
1545 elif qttype == 'QTime': |
|
1546 varlist.append(("", "QTime", "{0}".format(value.toString()))) |
|
1547 elif qttype == 'QDateTime': |
|
1548 varlist.append(("", "QDateTime", "{0}".format(value.toString()))) |
|
1549 elif qttype == 'QDir': |
|
1550 varlist.append(("path", "str", "{0}".format(value.path()))) |
|
1551 varlist.append(("absolutePath", "str", |
|
1552 "{0}".format(value.absolutePath()))) |
|
1553 varlist.append(("canonicalPath", "str", |
|
1554 "{0}".format(value.canonicalPath()))) |
|
1555 elif qttype == 'QFile': |
|
1556 varlist.append(("fileName", "str", "{0}".format(value.fileName()))) |
|
1557 elif qttype == 'QFont': |
|
1558 varlist.append(("family", "str", "{0}".format(value.family()))) |
|
1559 varlist.append( |
|
1560 ("pointSize", "int", "{0:d}".format(value.pointSize()))) |
|
1561 varlist.append(("weight", "int", "{0:d}".format(value.weight()))) |
|
1562 varlist.append(("bold", "bool", "{0}".format(value.bold()))) |
|
1563 varlist.append(("italic", "bool", "{0}".format(value.italic()))) |
|
1564 elif qttype == 'QUrl': |
|
1565 varlist.append(("url", "str", "{0}".format(value.toString()))) |
|
1566 varlist.append(("scheme", "str", "{0}".format(value.scheme()))) |
|
1567 varlist.append(("user", "str", "{0}".format(value.userName()))) |
|
1568 varlist.append(("password", "str", "{0}".format(value.password()))) |
|
1569 varlist.append(("host", "str", "{0}".format(value.host()))) |
|
1570 varlist.append(("port", "int", "{0:d}".format(value.port()))) |
|
1571 varlist.append(("path", "str", "{0}".format(value.path()))) |
|
1572 elif qttype == 'QModelIndex': |
|
1573 varlist.append(("valid", "bool", "{0}".format(value.isValid()))) |
|
1574 if value.isValid(): |
|
1575 varlist.append(("row", "int", "{0}".format(value.row()))) |
|
1576 varlist.append(("column", "int", "{0}".format(value.column()))) |
|
1577 varlist.append( |
|
1578 ("internalId", "int", "{0}".format(value.internalId()))) |
|
1579 varlist.append(("internalPointer", "void *", |
|
1580 "{0}".format(value.internalPointer()))) |
|
1581 elif qttype == 'QRegExp': |
|
1582 varlist.append(("pattern", "str", "{0}".format(value.pattern()))) |
|
1583 |
|
1584 # GUI stuff |
|
1585 elif qttype == 'QAction': |
|
1586 varlist.append(("name", "str", "{0}".format(value.objectName()))) |
|
1587 varlist.append(("text", "str", "{0}".format(value.text()))) |
|
1588 varlist.append( |
|
1589 ("icon text", "str", "{0}".format(value.iconText()))) |
|
1590 varlist.append(("tooltip", "str", "{0}".format(value.toolTip()))) |
|
1591 varlist.append( |
|
1592 ("whatsthis", "str", "{0}".format(value.whatsThis()))) |
|
1593 varlist.append( |
|
1594 ("shortcut", "str", |
|
1595 "{0}".format(value.shortcut().toString()))) |
|
1596 elif qttype == 'QKeySequence': |
|
1597 varlist.append(("value", "", "{0}".format(value.toString()))) |
|
1598 |
|
1599 # XML stuff |
|
1600 elif qttype == 'QDomAttr': |
|
1601 varlist.append(("name", "str", "{0}".format(value.name()))) |
|
1602 varlist.append(("value", "str", "{0}".format(value.value()))) |
|
1603 elif qttype == 'QDomCharacterData': |
|
1604 varlist.append(("data", "str", "{0}".format(value.data()))) |
|
1605 elif qttype == 'QDomComment': |
|
1606 varlist.append(("data", "str", "{0}".format(value.data()))) |
|
1607 elif qttype == "QDomDocument": |
|
1608 varlist.append(("text", "str", "{0}".format(value.toString()))) |
|
1609 elif qttype == 'QDomElement': |
|
1610 varlist.append(("tagName", "str", "{0}".format(value.tagName()))) |
|
1611 varlist.append(("text", "str", "{0}".format(value.text()))) |
|
1612 elif qttype == 'QDomText': |
|
1613 varlist.append(("data", "str", "{0}".format(value.data()))) |
|
1614 |
|
1615 # Networking stuff |
|
1616 elif qttype == 'QHostAddress': |
|
1617 varlist.append( |
|
1618 ("address", "QHostAddress", "{0}".format(value.toString()))) |
|
1619 |
|
1620 return varlist |
|
1621 |
|
1622 def __formatVariablesList(self, keylist, dict, scope, filter=[], |
|
1623 formatSequences=False): |
|
1624 """ |
|
1625 Private method to produce a formated variables list. |
|
1626 |
|
1627 The dictionary passed in to it is scanned. Variables are |
|
1628 only added to the list, if their type is not contained |
|
1629 in the filter list and their name doesn't match any of the filter |
|
1630 expressions. The formated variables list (a list of tuples of 3 |
|
1631 values) is returned. |
|
1632 |
|
1633 @param keylist keys of the dictionary |
|
1634 @param dict the dictionary to be scanned |
|
1635 @param scope 1 to filter using the globals filter, 0 using the locals |
|
1636 filter (int). |
|
1637 Variables are only added to the list, if their name do not match |
|
1638 any of the filter expressions. |
|
1639 @param filter the indices of variable types to be filtered. Variables |
|
1640 are only added to the list, if their type is not contained in the |
|
1641 filter list. |
|
1642 @param formatSequences flag indicating, that sequence or dictionary |
|
1643 variables should be formatted. If it is 0 (or false), just the |
|
1644 number of items contained in these variables is returned. (boolean) |
|
1645 @return A tuple consisting of a list of formatted variables. Each |
|
1646 variable entry is a tuple of three elements, the variable name, |
|
1647 its type and value. |
|
1648 """ |
|
1649 varlist = [] |
|
1650 if scope: |
|
1651 patternFilterObjects = self.globalsFilterObjects |
|
1652 else: |
|
1653 patternFilterObjects = self.localsFilterObjects |
|
1654 |
|
1655 for key in keylist: |
|
1656 # filter based on the filter pattern |
|
1657 matched = False |
|
1658 for pat in patternFilterObjects: |
|
1659 if pat.match(str(key)): |
|
1660 matched = True |
|
1661 break |
|
1662 if matched: |
|
1663 continue |
|
1664 |
|
1665 # filter hidden attributes (filter #0) |
|
1666 if 0 in filter and str(key)[:2] == '__' and not ( |
|
1667 key == "___len___" and |
|
1668 DebugVariables.TooLargeAttribute in keylist): |
|
1669 continue |
|
1670 |
|
1671 # special handling for '__builtins__' (it's way too big) |
|
1672 if key == '__builtins__': |
|
1673 rvalue = '<module __builtin__ (built-in)>' |
|
1674 valtype = 'module' |
|
1675 else: |
|
1676 value = dict[key] |
|
1677 valtypestr = str(type(value))[1:-1] |
|
1678 valtype = valtypestr[7:-1] |
|
1679 valtypename = type(value).__name__ |
|
1680 if valtype not in ConfigVarTypeStrings: |
|
1681 if valtype in ["numpy.ndarray", "array.array"]: |
|
1682 if ConfigVarTypeStrings.index('list') in filter: |
|
1683 continue |
|
1684 elif valtypename == "MultiValueDict": |
|
1685 if ConfigVarTypeStrings.index('dict') in filter: |
|
1686 continue |
|
1687 elif valtype == "sip.methoddescriptor": |
|
1688 if ConfigVarTypeStrings.index( |
|
1689 'method') in filter: |
|
1690 continue |
|
1691 elif valtype == "sip.enumtype": |
|
1692 if ConfigVarTypeStrings.index('class') in filter: |
|
1693 continue |
|
1694 elif ConfigVarTypeStrings.index('instance') in filter: |
|
1695 continue |
|
1696 |
|
1697 if valtypename not in ["ndarray", "MultiValueDict", |
|
1698 "array"]: |
|
1699 valtype = valtypestr |
|
1700 else: |
|
1701 try: |
|
1702 if ConfigVarTypeStrings.index(valtype) in filter: |
|
1703 continue |
|
1704 except ValueError: |
|
1705 if valtype == "classobj": |
|
1706 if ConfigVarTypeStrings.index( |
|
1707 'instance') in filter: |
|
1708 continue |
|
1709 elif valtype == "sip.methoddescriptor": |
|
1710 if ConfigVarTypeStrings.index( |
|
1711 'method') in filter: |
|
1712 continue |
|
1713 elif valtype == "sip.enumtype": |
|
1714 if ConfigVarTypeStrings.index('class') in filter: |
|
1715 continue |
|
1716 elif not valtype.startswith("PySide") and \ |
|
1717 ConfigVarTypeStrings.index('other') in filter: |
|
1718 continue |
|
1719 |
|
1720 try: |
|
1721 if valtype in ['list', 'tuple', 'dict', 'set', |
|
1722 'frozenset', 'array.array']: |
|
1723 if valtype == 'dict': |
|
1724 rvalue = "{0:d}".format(len(value.keys())) |
|
1725 else: |
|
1726 rvalue = "{0:d}".format(len(value)) |
|
1727 elif valtype == "numpy.ndarray": |
|
1728 rvalue = "{0:d}".format(value.size) |
|
1729 elif valtypename == "MultiValueDict": |
|
1730 rvalue = "{0:d}".format(len(value.keys())) |
|
1731 valtype = "django.MultiValueDict" # shortened type |
|
1732 else: |
|
1733 rvalue = repr(value) |
|
1734 if valtype.startswith('class') and \ |
|
1735 rvalue[0] in ['{', '(', '[']: |
|
1736 rvalue = "" |
|
1737 except Exception: |
|
1738 rvalue = '' |
|
1739 |
|
1740 if formatSequences: |
|
1741 if str(key) == key: |
|
1742 key = "'{0!s}'".format(key) |
|
1743 else: |
|
1744 key = str(key) |
|
1745 varlist.append((key, valtype, rvalue)) |
|
1746 |
|
1747 return varlist |
|
1748 |
|
1749 def __generateFilterObjects(self, scope, filterString): |
|
1750 """ |
|
1751 Private slot to convert a filter string to a list of filter objects. |
|
1752 |
|
1753 @param scope 1 to generate filter for global variables, 0 for local |
|
1754 variables (int) |
|
1755 @param filterString string of filter patterns separated by ';' |
|
1756 """ |
|
1757 patternFilterObjects = [] |
|
1758 for pattern in filterString.split(';'): |
|
1759 patternFilterObjects.append(re.compile('^{0}$'.format(pattern))) |
|
1760 if scope: |
|
1761 self.globalsFilterObjects = patternFilterObjects[:] |
|
1762 else: |
|
1763 self.localsFilterObjects = patternFilterObjects[:] |
|
1764 |
|
1765 def __completionList(self, text): |
|
1766 """ |
|
1767 Private slot to handle the request for a commandline completion list. |
|
1768 |
|
1769 @param text the text to be completed (string) |
|
1770 """ |
|
1771 completerDelims = ' \t\n`~!@#$%^&*()-=+[{]}\\|;:\'",<>/?' |
|
1772 |
|
1773 completions = set() |
|
1774 # find position of last delim character |
|
1775 pos = -1 |
|
1776 while pos >= -len(text): |
|
1777 if text[pos] in completerDelims: |
|
1778 if pos == -1: |
|
1779 text = '' |
|
1780 else: |
|
1781 text = text[pos + 1:] |
|
1782 break |
|
1783 pos -= 1 |
|
1784 |
|
1785 # Get local and global completions |
|
1786 try: |
|
1787 localdict = self.currentThread.getFrameLocals(self.framenr) |
|
1788 localCompleter = Completer(localdict).complete |
|
1789 self.__getCompletionList(text, localCompleter, completions) |
|
1790 except AttributeError: |
|
1791 pass |
|
1792 self.__getCompletionList(text, self.complete, completions) |
|
1793 |
|
1794 self.sendJsonCommand("ResponseCompletion", { |
|
1795 "completions": list(completions), |
|
1796 "text": text, |
|
1797 }) |
|
1798 |
|
1799 def __getCompletionList(self, text, completer, completions): |
|
1800 """ |
|
1801 Private method to create a completions list. |
|
1802 |
|
1803 @param text text to complete (string) |
|
1804 @param completer completer methode |
|
1805 @param completions set where to add new completions strings (set) |
|
1806 """ |
|
1807 state = 0 |
|
1808 try: |
|
1809 comp = completer(text, state) |
|
1810 except Exception: |
|
1811 comp = None |
|
1812 while comp is not None: |
|
1813 completions.add(comp) |
|
1814 state += 1 |
|
1815 try: |
|
1816 comp = completer(text, state) |
|
1817 except Exception: |
|
1818 comp = None |
|
1819 |
|
1820 def startDebugger(self, filename=None, host=None, port=None, |
|
1821 enableTrace=True, exceptions=True, tracePython=False, |
|
1822 redirect=True): |
|
1823 """ |
|
1824 Public method used to start the remote debugger. |
|
1825 |
|
1826 @param filename the program to be debugged (string) |
|
1827 @param host hostname of the debug server (string) |
|
1828 @param port portnumber of the debug server (int) |
|
1829 @param enableTrace flag to enable the tracing function (boolean) |
|
1830 @param exceptions flag to enable exception reporting of the IDE |
|
1831 (boolean) |
|
1832 @param tracePython flag to enable tracing into the Python library |
|
1833 (boolean) |
|
1834 @param redirect flag indicating redirection of stdin, stdout and |
|
1835 stderr (boolean) |
|
1836 """ |
|
1837 global debugClient |
|
1838 if host is None: |
|
1839 host = os.getenv('ERICHOST', 'localhost') |
|
1840 if port is None: |
|
1841 port = os.getenv('ERICPORT', 42424) |
|
1842 |
|
1843 remoteAddress = self.__resolveHost(host) |
|
1844 self.connectDebugger(port, remoteAddress, redirect) |
|
1845 if filename is not None: |
|
1846 self.running = os.path.abspath(filename) |
|
1847 else: |
|
1848 try: |
|
1849 self.running = os.path.abspath(sys.argv[0]) |
|
1850 except IndexError: |
|
1851 self.running = None |
|
1852 if self.running: |
|
1853 self.__setCoding(self.running) |
|
1854 self.passive = True |
|
1855 self.sendPassiveStartup(self.running, exceptions) |
|
1856 self.__interact() |
|
1857 |
|
1858 # setup the debugger variables |
|
1859 self._fncache = {} |
|
1860 self.dircache = [] |
|
1861 self.mainFrame = None |
|
1862 self.debugging = True |
|
1863 |
|
1864 self.attachThread(mainThread=True) |
|
1865 self.mainThread.tracePython = tracePython |
|
1866 |
|
1867 # set the system exception handling function to ensure, that |
|
1868 # we report on all unhandled exceptions |
|
1869 sys.excepthook = self.__unhandled_exception |
|
1870 self.__interceptSignals() |
|
1871 |
|
1872 # now start debugging |
|
1873 if enableTrace: |
|
1874 self.mainThread.set_trace() |
|
1875 |
|
1876 def startProgInDebugger(self, progargs, wd='', host=None, |
|
1877 port=None, exceptions=True, tracePython=False, |
|
1878 redirect=True): |
|
1879 """ |
|
1880 Public method used to start the remote debugger. |
|
1881 |
|
1882 @param progargs commandline for the program to be debugged |
|
1883 (list of strings) |
|
1884 @param wd working directory for the program execution (string) |
|
1885 @param host hostname of the debug server (string) |
|
1886 @param port portnumber of the debug server (int) |
|
1887 @param exceptions flag to enable exception reporting of the IDE |
|
1888 (boolean) |
|
1889 @param tracePython flag to enable tracing into the Python library |
|
1890 (boolean) |
|
1891 @param redirect flag indicating redirection of stdin, stdout and |
|
1892 stderr (boolean) |
|
1893 """ |
|
1894 if host is None: |
|
1895 host = os.getenv('ERICHOST', 'localhost') |
|
1896 if port is None: |
|
1897 port = os.getenv('ERICPORT', 42424) |
|
1898 |
|
1899 remoteAddress = self.__resolveHost(host) |
|
1900 self.connectDebugger(port, remoteAddress, redirect) |
|
1901 |
|
1902 self._fncache = {} |
|
1903 self.dircache = [] |
|
1904 sys.argv = progargs[:] |
|
1905 sys.argv[0] = os.path.abspath(sys.argv[0]) |
|
1906 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0])) |
|
1907 if wd == '': |
|
1908 os.chdir(sys.path[1]) |
|
1909 else: |
|
1910 os.chdir(wd) |
|
1911 self.running = sys.argv[0] |
|
1912 self.__setCoding(self.running) |
|
1913 self.mainFrame = None |
|
1914 self.debugging = True |
|
1915 |
|
1916 self.passive = True |
|
1917 self.sendPassiveStartup(self.running, exceptions) |
|
1918 self.__interact() |
|
1919 |
|
1920 self.attachThread(mainThread=True) |
|
1921 self.mainThread.tracePython = tracePython |
|
1922 |
|
1923 # set the system exception handling function to ensure, that |
|
1924 # we report on all unhandled exceptions |
|
1925 sys.excepthook = self.__unhandled_exception |
|
1926 self.__interceptSignals() |
|
1927 |
|
1928 # This will eventually enter a local event loop. |
|
1929 # Note the use of backquotes to cause a repr of self.running. The |
|
1930 # need for this is on Windows os where backslash is the path separator. |
|
1931 # They will get inadvertantly stripped away during the eval causing |
|
1932 # IOErrors if self.running is passed as a normal str. |
|
1933 self.debugMod.__dict__['__file__'] = self.running |
|
1934 sys.modules['__main__'] = self.debugMod |
|
1935 res = self.mainThread.run( |
|
1936 'exec(open(' + repr(self.running) + ').read())', |
|
1937 self.debugMod.__dict__) |
|
1938 self.progTerminated(res) |
|
1939 |
|
1940 def run_call(self, scriptname, func, *args): |
|
1941 """ |
|
1942 Public method used to start the remote debugger and call a function. |
|
1943 |
|
1944 @param scriptname name of the script to be debugged (string) |
|
1945 @param func function to be called |
|
1946 @param *args arguments being passed to func |
|
1947 @return result of the function call |
|
1948 """ |
|
1949 self.startDebugger(scriptname, enableTrace=False) |
|
1950 res = self.mainThread.runcall(func, *args) |
|
1951 self.progTerminated(res) |
|
1952 return res |
|
1953 |
|
1954 def __resolveHost(self, host): |
|
1955 """ |
|
1956 Private method to resolve a hostname to an IP address. |
|
1957 |
|
1958 @param host hostname of the debug server (string) |
|
1959 @return IP address (string) |
|
1960 """ |
|
1961 try: |
|
1962 host, version = host.split("@@") |
|
1963 except ValueError: |
|
1964 version = 'v4' |
|
1965 if version == 'v4': |
|
1966 family = socket.AF_INET |
|
1967 else: |
|
1968 family = socket.AF_INET6 |
|
1969 return socket.getaddrinfo(host, None, |
|
1970 family, socket.SOCK_STREAM)[0][4][0] |
|
1971 |
|
1972 def main(self): |
|
1973 """ |
|
1974 Public method implementing the main method. |
|
1975 """ |
|
1976 if '--' in sys.argv: |
|
1977 args = sys.argv[1:] |
|
1978 host = None |
|
1979 port = None |
|
1980 wd = '' |
|
1981 tracePython = False |
|
1982 exceptions = True |
|
1983 redirect = True |
|
1984 while args[0]: |
|
1985 if args[0] == '-h': |
|
1986 host = args[1] |
|
1987 del args[0] |
|
1988 del args[0] |
|
1989 elif args[0] == '-p': |
|
1990 port = int(args[1]) |
|
1991 del args[0] |
|
1992 del args[0] |
|
1993 elif args[0] == '-w': |
|
1994 wd = args[1] |
|
1995 del args[0] |
|
1996 del args[0] |
|
1997 elif args[0] == '-t': |
|
1998 tracePython = True |
|
1999 del args[0] |
|
2000 elif args[0] == '-e': |
|
2001 exceptions = False |
|
2002 del args[0] |
|
2003 elif args[0] == '-n': |
|
2004 redirect = False |
|
2005 del args[0] |
|
2006 elif args[0] == '--no-encoding': |
|
2007 self.noencoding = True |
|
2008 del args[0] |
|
2009 elif args[0] == '--fork-child': |
|
2010 self.fork_auto = True |
|
2011 self.fork_child = True |
|
2012 del args[0] |
|
2013 elif args[0] == '--fork-parent': |
|
2014 self.fork_auto = True |
|
2015 self.fork_child = False |
|
2016 del args[0] |
|
2017 elif args[0] == '--': |
|
2018 del args[0] |
|
2019 break |
|
2020 else: # unknown option |
|
2021 del args[0] |
|
2022 if not args: |
|
2023 print("No program given. Aborting!") # __IGNORE_WARNING__ |
|
2024 else: |
|
2025 if not self.noencoding: |
|
2026 self.__coding = self.defaultCoding |
|
2027 self.startProgInDebugger(args, wd, host, port, |
|
2028 exceptions=exceptions, |
|
2029 tracePython=tracePython, |
|
2030 redirect=redirect) |
|
2031 else: |
|
2032 if sys.argv[1] == '--no-encoding': |
|
2033 self.noencoding = True |
|
2034 del sys.argv[1] |
|
2035 if sys.argv[1] == '': |
|
2036 del sys.argv[1] |
|
2037 try: |
|
2038 port = int(sys.argv[1]) |
|
2039 except (ValueError, IndexError): |
|
2040 port = -1 |
|
2041 try: |
|
2042 redirect = int(sys.argv[2]) |
|
2043 except (ValueError, IndexError): |
|
2044 redirect = True |
|
2045 try: |
|
2046 ipOrHost = sys.argv[3] |
|
2047 if ':' in ipOrHost: |
|
2048 remoteAddress = ipOrHost |
|
2049 elif ipOrHost[0] in '0123456789': |
|
2050 remoteAddress = ipOrHost |
|
2051 else: |
|
2052 remoteAddress = self.__resolveHost(ipOrHost) |
|
2053 except Exception: |
|
2054 remoteAddress = None |
|
2055 sys.argv = [''] |
|
2056 if '' not in sys.path: |
|
2057 sys.path.insert(0, '') |
|
2058 if port >= 0: |
|
2059 if not self.noencoding: |
|
2060 self.__coding = self.defaultCoding |
|
2061 self.connectDebugger(port, remoteAddress, redirect) |
|
2062 self.__interact() |
|
2063 else: |
|
2064 print("No network port given. Aborting...") # __IGNORE_WARNING__ |
|
2065 |
|
2066 def fork(self): |
|
2067 """ |
|
2068 Public method implementing a fork routine deciding which branch |
|
2069 to follow. |
|
2070 |
|
2071 @return process ID (integer) |
|
2072 """ |
|
2073 if not self.fork_auto: |
|
2074 self.sendJsonCommand("RequestForkTo", {}) |
|
2075 self.eventLoop(True) |
|
2076 pid = DebugClientOrigFork() |
|
2077 if pid == 0: |
|
2078 # child |
|
2079 if not self.fork_child: |
|
2080 sys.settrace(None) |
|
2081 sys.setprofile(None) |
|
2082 self.sessionClose(0) |
|
2083 else: |
|
2084 # parent |
|
2085 if self.fork_child: |
|
2086 sys.settrace(None) |
|
2087 sys.setprofile(None) |
|
2088 self.sessionClose(0) |
|
2089 return pid |
|
2090 |
|
2091 def close(self, fd): |
|
2092 """ |
|
2093 Public method implementing a close method as a replacement for |
|
2094 os.close(). |
|
2095 |
|
2096 It prevents the debugger connections from being closed. |
|
2097 |
|
2098 @param fd file descriptor to be closed (integer) |
|
2099 """ |
|
2100 if fd in [self.readstream.fileno(), self.writestream.fileno(), |
|
2101 self.errorstream.fileno()]: |
|
2102 return |
|
2103 |
|
2104 DebugClientOrigClose(fd) |
|
2105 |
|
2106 def __getSysPath(self, firstEntry): |
|
2107 """ |
|
2108 Private slot to calculate a path list including the PYTHONPATH |
|
2109 environment variable. |
|
2110 |
|
2111 @param firstEntry entry to be put first in sys.path (string) |
|
2112 @return path list for use as sys.path (list of strings) |
|
2113 """ |
|
2114 sysPath = [path for path in os.environ.get("PYTHONPATH", "") |
|
2115 .split(os.pathsep) |
|
2116 if path not in sys.path] + sys.path[:] |
|
2117 if "" in sysPath: |
|
2118 sysPath.remove("") |
|
2119 sysPath.insert(0, firstEntry) |
|
2120 sysPath.insert(0, '') |
|
2121 return sysPath |
|
2122 |
|
2123 # |
|
2124 # eflag: noqa = M702 |
|