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