1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing the debug base class which based originally on bdb. 8""" 9 10import sys 11import os 12import types 13import atexit 14import inspect 15import ctypes 16import time 17import dis 18import contextlib 19 20from BreakpointWatch import Breakpoint, Watch 21 22import _thread 23from DebugUtilities import getargvalues, formatargvalues 24 25gRecursionLimit = 64 26 27try: 28 GENERATOR_AND_COROUTINE_FLAGS = ( 29 inspect.CO_GENERATOR | inspect.CO_COROUTINE | 30 inspect.CO_ASYNC_GENERATOR 31 ) 32except AttributeError: 33 # Python < 3.7 34 GENERATOR_AND_COROUTINE_FLAGS = inspect.CO_GENERATOR 35 36 37def printerr(s): 38 """ 39 Module function used for debugging the debug client. 40 41 @param s data to be printed 42 """ 43 sys.__stderr__.write('{0!s}\n'.format(s)) 44 sys.__stderr__.flush() 45 46 47def setRecursionLimit(limit): 48 """ 49 Module function to set the recursion limit. 50 51 @param limit recursion limit (integer) 52 """ 53 global gRecursionLimit 54 gRecursionLimit = limit 55 56 57class DebugBase: 58 """ 59 Class implementing base class of the debugger. 60 61 Provides methods for the 'owning' client to call to step etc. 62 """ 63 # Don't thrust distutils.sysconfig.get_python_lib: possible case mismatch 64 # on Windows 65 lib = os.path.dirname(inspect.__file__) 66 # tuple required because it's accessed a lot of times by startswith method 67 pathsToSkip = ('<', os.path.dirname(__file__), inspect.__file__[:-1]) 68 filesToSkip = {} 69 70 # cache for fixed file names 71 _fnCache = {} 72 73 # Stop all timers, when greenlets are used 74 pollTimerEnabled = True 75 76 def __init__(self, dbgClient): 77 """ 78 Constructor 79 80 @param dbgClient the owning client 81 """ 82 self._dbgClient = dbgClient 83 84 # Some informations about the thread 85 self.isMainThread = False 86 self.quitting = False 87 self.id = -1 88 self.name = '' 89 90 self.tracePythonLibs(0) 91 92 # Special handling of a recursion error 93 self.skipFrames = 0 94 95 self.isBroken = False 96 self.isException = False 97 self.cFrame = None 98 99 # current frame we are at 100 self.currentFrame = None 101 102 # frames, where we want to stop or release debugger 103 self.stopframe = None 104 self.returnframe = None 105 self.stop_everywhere = False 106 107 self.__recursionDepth = -1 108 self.setRecursionDepth(inspect.currentframe()) 109 110 # background task to periodicaly check for client interactions 111 self.eventPollFlag = False 112 self.timer = _thread.start_new_thread(self.__eventPollTimer, ()) 113 114 # provide a hook to perform a hard breakpoint 115 # Use it like this: 116 # if hasattr(sys, 'breakpoint): sys.breakpoint() 117 sys.breakpoint = self.set_trace 118 if sys.version_info >= (3, 7): 119 sys.breakpointhook = self.set_trace 120 121 def __eventPollTimer(self): 122 """ 123 Private method to set a flag every 0.5 s to check for new messages. 124 """ 125 while DebugBase.pollTimerEnabled: 126 time.sleep(0.5) 127 self.eventPollFlag = True 128 129 self.eventPollFlag = False 130 131 def getCurrentFrame(self): 132 """ 133 Public method to return the current frame. 134 135 @return the current frame 136 @rtype frame object 137 """ 138 # Don't show any local frames after the program was stopped 139 if self.quitting: 140 return None 141 142 return self.currentFrame 143 144 def getFrameLocals(self, frmnr=0): 145 """ 146 Public method to return the locals dictionary of the current frame 147 or a frame below. 148 149 @param frmnr distance of frame to get locals dictionary of. 0 is 150 the current frame (int) 151 @return locals dictionary of the frame 152 """ 153 f = self.currentFrame 154 while f is not None and frmnr > 0: 155 f = f.f_back 156 frmnr -= 1 157 return f.f_locals 158 159 def storeFrameLocals(self, frmnr=0): 160 """ 161 Public method to store the locals into the frame, so an access to 162 frame.f_locals returns the last data. 163 164 @param frmnr distance of frame to store locals dictionary to. 0 is 165 the current frame (int) 166 """ 167 cf = self.currentFrame 168 while cf is not None and frmnr > 0: 169 cf = cf.f_back 170 frmnr -= 1 171 172 with contextlib.suppress(Exception): 173 if "__pypy__" in sys.builtin_module_names: 174 import __pypy__ 175 __pypy__.locals_to_fast(cf) 176 return 177 178 ctypes.pythonapi.PyFrame_LocalsToFast( 179 ctypes.py_object(cf), 180 ctypes.c_int(0)) 181 182 def step(self, traceMode): 183 """ 184 Public method to perform a step operation in this thread. 185 186 @param traceMode If it is True, then the step is a step into, 187 otherwise it is a step over. 188 """ 189 if traceMode: 190 self.set_step() 191 else: 192 self.set_next(self.currentFrame) 193 194 def stepOut(self): 195 """ 196 Public method to perform a step out of the current call. 197 """ 198 self.set_return(self.currentFrame) 199 200 def go(self, special): 201 """ 202 Public method to resume the thread. 203 204 It resumes the thread stopping only at breakpoints or exceptions. 205 206 @param special flag indicating a special continue operation 207 """ 208 self.set_continue(special) 209 210 def setRecursionDepth(self, frame): 211 """ 212 Public method to determine the current recursion depth. 213 214 @param frame The current stack frame. 215 """ 216 self.__recursionDepth = 0 217 while frame is not None: 218 self.__recursionDepth += 1 219 frame = frame.f_back 220 221 def profileWithRecursion(self, frame, event, arg): 222 """ 223 Public method used to trace some stuff independent of the debugger 224 trace function. 225 226 @param frame current stack frame 227 @type frame object 228 @param event trace event 229 @type str 230 @param arg arguments 231 @type depends on the previous event parameter 232 @exception RuntimeError raised to indicate too many recursions 233 """ 234 if event == 'return': 235 self.cFrame = frame.f_back 236 self.__recursionDepth -= 1 237 if self._dbgClient.callTraceEnabled: 238 self.__sendCallTrace(event, frame, self.cFrame) 239 elif event == 'call': 240 if self._dbgClient.callTraceEnabled: 241 self.__sendCallTrace(event, self.cFrame, frame) 242 self.cFrame = frame 243 self.__recursionDepth += 1 244 if self.__recursionDepth > gRecursionLimit: 245 raise RuntimeError( 246 'maximum recursion depth exceeded\n' 247 '(offending frame is two down the stack)') 248 249 def profile(self, frame, event, arg): 250 """ 251 Public method used to trace some stuff independent of the debugger 252 trace function. 253 254 @param frame current stack frame 255 @type frame object 256 @param event trace event 257 @type str 258 @param arg arguments 259 @type depends on the previous event parameter 260 """ 261 if event == 'return': 262 self.__sendCallTrace(event, frame, frame.f_back) 263 elif event == 'call': 264 self.__sendCallTrace(event, frame.f_back, frame) 265 266 def __sendCallTrace(self, event, fromFrame, toFrame): 267 """ 268 Private method to send a call/return trace. 269 270 @param event trace event 271 @type str 272 @param fromFrame originating frame 273 @type frame object 274 @param toFrame destination frame 275 @type frame object 276 """ 277 if not self.__skipFrame(fromFrame) and not self.__skipFrame(toFrame): 278 fromInfo = { 279 "filename": self._dbgClient.absPath( 280 self.fix_frame_filename(fromFrame)), 281 "linenumber": fromFrame.f_lineno, 282 "codename": fromFrame.f_code.co_name, 283 } 284 toInfo = { 285 "filename": self._dbgClient.absPath( 286 self.fix_frame_filename(toFrame)), 287 "linenumber": toFrame.f_lineno, 288 "codename": toFrame.f_code.co_name, 289 } 290 self._dbgClient.sendCallTrace(event, fromInfo, toInfo) 291 292 def trace_dispatch(self, frame, event, arg): 293 """ 294 Public method reimplemented from bdb.py to do some special things. 295 296 This specialty is to check the connection to the debug server 297 for new events (i.e. new breakpoints) while we are going through 298 the code. 299 300 @param frame The current stack frame 301 @type frame object 302 @param event The trace event 303 @type str 304 @param arg The arguments 305 @type depends on the previous event parameter 306 @return local trace function 307 @rtype trace function or None 308 @exception SystemExit 309 """ 310 # give the client a chance to push through new break points. 311 if self.eventPollFlag: 312 self._dbgClient.eventPoll() 313 self.eventPollFlag = False 314 315 if self.quitting: 316 raise SystemExit 317 318 if event == 'line': 319 if self.stop_here(frame) or self.break_here(frame): 320 if ( 321 self.stop_everywhere and 322 frame.f_back and 323 frame.f_back.f_code.co_name == "prepareJsonCommand" 324 ): 325 # Just stepped into print statement, so skip these frames 326 self._set_stopinfo(None, frame.f_back) 327 else: 328 self.user_line(frame) 329 return self.trace_dispatch 330 331 if event == 'call': 332 if ( 333 self.stop_here(frame) or 334 self.__checkBreakInFrame(frame) or 335 Watch.watches != [] 336 ) or ( 337 self.stopframe and 338 frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS 339 ): 340 return self.trace_dispatch 341 else: 342 # No need to trace this function 343 return None 344 345 if event == 'return': 346 if self.stop_here(frame) or frame == self.returnframe: 347 # Ignore return events in generator except when stepping. 348 if ( 349 self.stopframe and 350 frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS 351 ): 352 return self.trace_dispatch 353 # Only true if we didn't stop in this frame, because it's 354 # belonging to the eric debugger. 355 if self.stopframe is frame and self.stoplineno != -1: 356 self._set_stopinfo(None, frame.f_back) 357 return None 358 359 if event == 'exception': 360 if not self.__skipFrame(frame): 361 # When stepping with next/until/return in a generator frame, 362 # skip the internal StopIteration exception (with no traceback) 363 # triggered by a subiterator run with the 'yield from' 364 # statement. 365 if not ( 366 frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS and 367 arg[0] is StopIteration and 368 arg[2] is None 369 ): 370 self.user_exception(arg) 371 # Stop at the StopIteration or GeneratorExit exception when the 372 # user has set stopframe in a generator by issuing a return 373 # command, or a next/until command at the last statement in the 374 # generator before the exception. 375 elif ( 376 self.stopframe and 377 frame is not self.stopframe and 378 (self.stopframe.f_code.co_flags & 379 GENERATOR_AND_COROUTINE_FLAGS) and 380 arg[0] in (StopIteration, GeneratorExit) 381 ): 382 self.user_exception(arg) 383 return None 384 385 if event == 'c_call': 386 return None 387 if event == 'c_exception': 388 return None 389 if event == 'c_return': 390 return None 391 392 print('DebugBase.trace_dispatch:' # __IGNORE_WARNING_M801__ 393 ' unknown debugging event: ', 394 repr(event)) 395 396 return self.trace_dispatch 397 398 def set_trace(self, frame=None): 399 """ 400 Public method to start debugging from 'frame'. 401 402 If frame is not specified, debugging starts from caller's frame. 403 Because of jump optimizations it's not possible to use sys.breakpoint() 404 as last instruction in a function or method. 405 406 @param frame frame to start debugging from 407 @type frame object 408 """ 409 if frame is None: 410 frame = sys._getframe().f_back # Skip set_trace method 411 412 stopOnHandleCommand = self._dbgClient.handleJsonCommand.__code__ 413 414 frame.f_trace = self.trace_dispatch 415 while frame.f_back is not None: 416 # stop at eric's debugger frame or a threading bootstrap 417 if frame.f_back.f_code == stopOnHandleCommand: 418 frame.f_trace = self.trace_dispatch 419 break 420 421 frame = frame.f_back 422 423 self.stop_everywhere = True 424 sys.settrace(self.trace_dispatch) 425 sys.setprofile(self._dbgClient.callTraceEnabled) 426 427 def bootstrap(self, target, args, kwargs): 428 """ 429 Public method to bootstrap a thread. 430 431 It wraps the call to the user function to enable tracing 432 before hand. 433 434 @param target function which is called in the new created thread 435 @type function pointer 436 @param args arguments to pass to target 437 @type tuple 438 @param kwargs keyword arguments to pass to target 439 @type dict 440 """ 441 try: 442 # Because in the initial run method the "base debug" function is 443 # set up, it's also valid for the threads afterwards. 444 sys.settrace(self.trace_dispatch) 445 446 target(*args, **kwargs) 447 except Exception: 448 excinfo = sys.exc_info() 449 self.user_exception(excinfo, True) 450 finally: 451 sys.settrace(None) 452 sys.setprofile(None) 453 454 def run(self, cmd, globalsDict=None, localsDict=None, debug=True, 455 closeSession=True): 456 """ 457 Public method to start a given command under debugger control. 458 459 @param cmd command / code to execute under debugger control 460 @type str or CodeType 461 @param globalsDict dictionary of global variables for cmd 462 @type dict 463 @param localsDict dictionary of local variables for cmd 464 @type dict 465 @param debug flag if command should run under debugger control 466 @type bool 467 @return exit code of the program 468 @rtype int 469 @param closeSession flag indicating to close the debugger session 470 at exit 471 @type bool 472 """ 473 if globalsDict is None: 474 import __main__ 475 globalsDict = __main__.__dict__ 476 477 if localsDict is None: 478 localsDict = globalsDict 479 480 if not isinstance(cmd, types.CodeType): 481 cmd = compile(cmd, "<string>", "exec") 482 483 if debug: 484 # First time the trace_dispatch function is called, a "base debug" 485 # function has to be returned, which is called at every user code 486 # function call. This is ensured by setting stop_everywhere. 487 self.stop_everywhere = True 488 sys.settrace(self.trace_dispatch) 489 490 try: 491 exec(cmd, globalsDict, localsDict) # secok 492 atexit._run_exitfuncs() 493 self._dbgClient.progTerminated(0, closeSession=closeSession) 494 exitcode = 0 495 except SystemExit: 496 atexit._run_exitfuncs() 497 excinfo = sys.exc_info() 498 exitcode, message = self.__extractSystemExitMessage(excinfo) 499 self._dbgClient.progTerminated(exitcode, message=message, 500 closeSession=closeSession) 501 except Exception: 502 excinfo = sys.exc_info() 503 self.user_exception(excinfo, True) 504 exitcode = 242 505 finally: 506 self.quitting = True 507 sys.settrace(None) 508 return exitcode 509 510 def _set_stopinfo(self, stopframe, returnframe, stoplineno=0): 511 """ 512 Protected method to update the frame pointers. 513 514 @param stopframe the frame object where to stop 515 @type frame object 516 @param returnframe the frame object where to stop on a function return 517 @type frame object 518 @param stoplineno line number to stop at. If stoplineno is greater than 519 or equal to 0, then stop at line greater than or equal to the 520 stopline. If stoplineno is -1, then don't stop at all. 521 @type int 522 """ 523 self.stopframe = stopframe 524 self.returnframe = returnframe 525 # stoplineno >= 0 means: stop at line >= the stoplineno 526 # stoplineno -1 means: don't stop at all 527 self.stoplineno = stoplineno 528 529 if returnframe is not None: 530 # Ensure to be able to stop on the return frame 531 returnframe.f_trace = self.trace_dispatch 532 self.stop_everywhere = False 533 534 def set_continue(self, special): 535 """ 536 Public method to stop only on next breakpoint. 537 538 @param special flag indicating a special continue operation 539 @type bool 540 """ 541 # Here we only set a new stop frame if it is a normal continue. 542 if not special: 543 self._set_stopinfo(None, None, -1) 544 545 # Disable tracing if not started in debug mode 546 if not self._dbgClient.debugging: 547 sys.settrace(None) 548 sys.setprofile(None) 549 550 def set_until(self, frame=None, lineno=None): 551 """ 552 Public method to stop when the line with the lineno greater than the 553 current one is reached or when returning from current frame. 554 555 @param frame reference to the frame object 556 @type frame object 557 @param lineno line number to continue to 558 @type int 559 """ 560 # the name "until" is borrowed from gdb 561 if frame is None: 562 frame = self.currentFrame 563 if lineno is None: 564 lineno = frame.f_lineno + 1 565 self._set_stopinfo(frame, frame, lineno) 566 567 def set_step(self): 568 """ 569 Public method to stop after one line of code. 570 """ 571 self._set_stopinfo(None, None) 572 self.stop_everywhere = True 573 574 def set_next(self, frame): 575 """ 576 Public method to stop on the next line in or below the given frame. 577 578 @param frame the frame object 579 @type frame object 580 """ 581 self._set_stopinfo(frame, frame.f_back) 582 frame.f_trace = self.trace_dispatch 583 584 def set_return(self, frame): 585 """ 586 Public method to stop when returning from the given frame. 587 588 @param frame the frame object 589 @type frame object 590 """ 591 self._set_stopinfo(None, frame.f_back) 592 593 def move_instruction_pointer(self, lineno): 594 """ 595 Public methode to move the instruction pointer to another line. 596 597 @param lineno new line number 598 @type int 599 """ 600 try: 601 self.currentFrame.f_lineno = lineno 602 stack = self.getStack(self.currentFrame) 603 self._dbgClient.sendResponseLine(stack, self.name) 604 except Exception as e: 605 printerr(e) 606 607 def set_quit(self): 608 """ 609 Public method to quit. 610 611 Disables the trace functions and resets all frame pointer. 612 """ 613 sys.setprofile(None) 614 self.stopframe = None 615 self.returnframe = None 616 for debugThread in self._dbgClient.threads.values(): 617 debugThread.quitting = True 618 619 def fix_frame_filename(self, frame): 620 """ 621 Public method used to fixup the filename for a given frame. 622 623 The logic employed here is that if a module was loaded 624 from a .pyc file, then the correct .py to operate with 625 should be in the same path as the .pyc. The reason this 626 logic is needed is that when a .pyc file is generated, the 627 filename embedded and thus what is readable in the code object 628 of the frame object is the fully qualified filepath when the 629 pyc is generated. If files are moved from machine to machine 630 this can break debugging as the .pyc will refer to the .py 631 on the original machine. Another case might be sharing 632 code over a network... This logic deals with that. 633 634 @param frame the frame object 635 @type frame object 636 @return fixed up file name 637 @rtype str 638 """ 639 # get module name from __file__ 640 fn = frame.f_globals.get('__file__') 641 try: 642 return self._fnCache[fn] 643 except KeyError: 644 if fn is None: 645 return frame.f_code.co_filename 646 647 absFilename = os.path.abspath(fn) 648 if absFilename.endswith(('.pyc', '.pyo', '.pyd')): 649 fixedName = absFilename[:-1] 650 if not os.path.exists(fixedName): 651 fixedName = absFilename 652 else: 653 fixedName = absFilename 654 # update cache 655 self._fnCache[fn] = fixedName 656 return fixedName 657 658 def __checkBreakInFrame(self, frame): 659 """ 660 Private method to check if the function / method has a line number 661 which is a breakpoint. 662 663 @param frame the frame object 664 @type frame object 665 @return Flag indicating a function / method with breakpoint 666 @rtype bool 667 """ 668 try: 669 return Breakpoint.breakInFrameCache[ 670 frame.f_globals.get('__file__'), 671 frame.f_code.co_firstlineno] 672 except KeyError: 673 filename = self.fix_frame_filename(frame) 674 if filename not in Breakpoint.breakInFile: 675 Breakpoint.breakInFrameCache[ 676 frame.f_globals.get('__file__'), 677 frame.f_code.co_firstlineno] = False 678 return False 679 lineNo = frame.f_code.co_firstlineno 680 lineNumbers = [lineNo] 681 682 co_lnotab = frame.f_code.co_lnotab[1::2] 683 684 # No need to handle special case if a lot of lines between 685 # (e.g. closure), because the additional lines won't cause a bp 686 for co_lno in co_lnotab: 687 if co_lno >= 0x80: 688 lineNo -= 0x100 689 lineNo += co_lno 690 lineNumbers.append(lineNo) 691 692 for bp in Breakpoint.breakInFile[filename]: 693 if bp in lineNumbers: 694 Breakpoint.breakInFrameCache[ 695 frame.f_globals.get('__file__'), 696 frame.f_code.co_firstlineno] = True 697 return True 698 Breakpoint.breakInFrameCache[ 699 frame.f_globals.get('__file__'), 700 frame.f_code.co_firstlineno] = False 701 return False 702 703 def break_here(self, frame): 704 """ 705 Public method reimplemented from bdb.py to fix the filename from the 706 frame. 707 708 See fix_frame_filename for more info. 709 710 @param frame the frame object 711 @type frame object 712 @return flag indicating the break status 713 @rtype bool 714 """ 715 filename = self.fix_frame_filename(frame) 716 if (filename, frame.f_lineno) in Breakpoint.breaks: 717 bp, flag = Breakpoint.effectiveBreak( 718 filename, frame.f_lineno, frame) 719 if bp: 720 # flag says ok to delete temp. bp 721 if flag and bp.temporary: 722 self.__do_clearBreak(filename, frame.f_lineno) 723 return True 724 725 if Watch.watches != []: 726 bp, flag = Watch.effectiveWatch(frame) 727 if bp: 728 # flag says ok to delete temp. watch 729 if flag and bp.temporary: 730 self.__do_clearWatch(bp.cond) 731 return True 732 733 return False 734 735 def __do_clearBreak(self, filename, lineno): 736 """ 737 Private method called to clear a temporary breakpoint. 738 739 @param filename name of the file the bp belongs to 740 @type str 741 @param lineno linenumber of the bp 742 @type int 743 """ 744 Breakpoint.clear_break(filename, lineno) 745 self._dbgClient.sendClearTemporaryBreakpoint(filename, lineno) 746 747 def __do_clearWatch(self, cond): 748 """ 749 Private method called to clear a temporary watch expression. 750 751 @param cond expression of the watch expression to be cleared 752 @type str 753 """ 754 Watch.clear_watch(cond) 755 self._dbgClient.sendClearTemporaryWatch(cond) 756 757 def getStack(self, frame=None, applyTrace=False): 758 """ 759 Public method to get the stack. 760 761 @param frame frame object to inspect 762 @type frame object or list 763 @param applyTrace flag to assign trace function to fr.f_trace 764 @type bool 765 @return list of lists with file name (string), line number (integer) 766 and function name (string) 767 """ 768 tb_lineno = None 769 if frame is None: 770 fr = self.getCurrentFrame() 771 elif type(frame) == list: 772 fr, tb_lineno = frame.pop(0) 773 else: 774 fr = frame 775 776 stack = [] 777 while fr is not None: 778 if applyTrace: 779 # Reset the trace function so we can be sure 780 # to trace all functions up the stack... This gets around 781 # problems where an exception/breakpoint has occurred 782 # but we had disabled tracing along the way via a None 783 # return from dispatch_call 784 fr.f_trace = self.trace_dispatch 785 786 fname = self._dbgClient.absPath(self.fix_frame_filename(fr)) 787 # Always show at least one stack frame, even if it's from eric. 788 if stack and os.path.basename(fname).startswith( 789 ("DebugBase.py", "DebugClientBase.py", 790 "ThreadExtension.py", "threading.py") 791 ): 792 break 793 794 fline = tb_lineno or fr.f_lineno 795 ffunc = fr.f_code.co_name 796 797 if ffunc == '?': 798 ffunc = '' 799 800 if ffunc and not ffunc.startswith("<"): 801 argInfo = getargvalues(fr) 802 try: 803 fargs = formatargvalues( 804 argInfo.args, argInfo.varargs, 805 argInfo.keywords, argInfo.locals) 806 except Exception: 807 fargs = "" 808 else: 809 fargs = "" 810 811 stack.append([fname, fline, ffunc, fargs]) 812 813 # is it a stack frame or exception list? 814 if type(frame) == list: 815 if frame != []: 816 fr, tb_lineno = frame.pop(0) 817 else: 818 fr = None 819 else: 820 fr = fr.f_back 821 822 return stack 823 824 def user_line(self, frame): 825 """ 826 Public method reimplemented to handle the program about to execute a 827 particular line. 828 829 @param frame the frame object 830 """ 831 # We never stop on line 0. 832 if frame.f_lineno == 0: 833 return 834 835 self.isBroken = True 836 self.currentFrame = frame 837 stack = self.getStack(frame, applyTrace=True) 838 839 self._dbgClient.lockClient() 840 self._dbgClient.currentThread = self 841 self._dbgClient.currentThreadExec = self 842 843 self._dbgClient.sendResponseLine(stack, self.name) 844 self._dbgClient.eventLoop() 845 846 self.isBroken = False 847 self._dbgClient.unlockClient() 848 849 self._dbgClient.dumpThreadList() 850 851 def user_exception(self, excinfo, unhandled=False): 852 """ 853 Public method reimplemented to report an exception to the debug server. 854 855 @param excinfo details about the exception 856 @type tuple(Exception, excval object, traceback frame object) 857 @param unhandled flag indicating an uncaught exception 858 @type bool 859 """ 860 exctype, excval, exctb = excinfo 861 862 if ((exctype in [GeneratorExit, StopIteration] and 863 unhandled is False) or 864 exctype == SystemExit): 865 # ignore these 866 return 867 868 if exctype in [SyntaxError, IndentationError]: 869 try: 870 if type(excval) == tuple: 871 message, details = excval 872 filename, lineno, charno, text = details 873 else: 874 message = excval.msg 875 filename = excval.filename 876 lineno = excval.lineno 877 charno = excval.offset 878 879 if filename is None: 880 realSyntaxError = False 881 else: 882 if charno is None: 883 charno = 0 884 885 filename = os.path.abspath(filename) 886 realSyntaxError = os.path.exists(filename) 887 888 except (AttributeError, ValueError): 889 message = "" 890 filename = "" 891 lineno = 0 892 charno = 0 893 realSyntaxError = True 894 895 if realSyntaxError: 896 self._dbgClient.sendSyntaxError( 897 message, filename, lineno, charno, self.name) 898 self._dbgClient.eventLoop() 899 return 900 901 self.skipFrames = 0 902 if (exctype == RuntimeError and 903 str(excval).startswith('maximum recursion depth exceeded') or 904 sys.version_info >= (3, 5) and 905 exctype == RecursionError): # __IGNORE_WARNING__ 906 excval = 'maximum recursion depth exceeded' 907 depth = 0 908 tb = exctb 909 while tb: 910 tb = tb.tb_next 911 912 if (tb and tb.tb_frame.f_code.co_name == 'trace_dispatch' and 913 __file__.startswith(tb.tb_frame.f_code.co_filename)): 914 depth = 1 915 self.skipFrames += depth 916 917 # always 1 if running without debugger 918 self.skipFrames = max(1, self.skipFrames) 919 920 exctype = self.__extractExceptionName(exctype) 921 922 if excval is None: 923 excval = '' 924 925 exctypetxt = ( 926 "unhandled {0!s}".format(str(exctype)) 927 if unhandled else 928 str(exctype) 929 ) 930 excvaltxt = str(excval) 931 932 # Don't step into libraries, which are used by our debugger methods 933 if exctb is not None: 934 self.stop_everywhere = False 935 936 self.isBroken = True 937 self.isException = True 938 939 disassembly = None 940 stack = [] 941 if exctb: 942 frlist = self.__extract_stack(exctb) 943 frlist.reverse() 944 disassembly = self.__disassemble(frlist[0][0]) 945 946 self.currentFrame = frlist[0][0] 947 stack = self.getStack(frlist[self.skipFrames:]) 948 949 self._dbgClient.lockClient() 950 self._dbgClient.currentThread = self 951 self._dbgClient.currentThreadExec = self 952 self._dbgClient.sendException(exctypetxt, excvaltxt, stack, self.name) 953 self._dbgClient.setDisassembly(disassembly) 954 self._dbgClient.dumpThreadList() 955 956 if exctb is not None: 957 # When polling kept enabled, it isn't possible to resume after an 958 # unhandled exception without further user interaction. 959 self._dbgClient.eventLoop(True) 960 961 self.skipFrames = 0 962 963 self.isBroken = False 964 self.isException = False 965 stop_everywhere = self.stop_everywhere 966 self.stop_everywhere = False 967 self.eventPollFlag = False 968 self._dbgClient.unlockClient() 969 self.stop_everywhere = stop_everywhere 970 971 self._dbgClient.dumpThreadList() 972 973 def __extractExceptionName(self, exctype): 974 """ 975 Private method to extract the exception name given the exception 976 type object. 977 978 @param exctype type of the exception 979 @return exception name (string) 980 """ 981 return str(exctype).replace("<class '", "").replace("'>", "") 982 983 def __extract_stack(self, exctb): 984 """ 985 Private member to return a list of stack frames. 986 987 @param exctb exception traceback 988 @return list of stack frames 989 """ 990 tb = exctb 991 stack = [] 992 while tb is not None: 993 stack.append((tb.tb_frame, tb.tb_lineno)) 994 tb = tb.tb_next 995 tb = None 996 return stack 997 998 def __disassemble(self, frame): 999 """ 1000 Private method to generate a disassembly of the given code object. 1001 1002 @param frame frame object to be disassembled 1003 @type code 1004 @return dictionary containing the disassembly information 1005 @rtype dict 1006 """ 1007 co = frame.f_code 1008 disDict = { 1009 "lasti": frame.f_lasti, 1010 "firstlineno": co.co_firstlineno, 1011 "instructions": [], 1012 } 1013 1014 # 1. disassembly info 1015 for instr in dis.get_instructions(co): 1016 instrDict = { 1017 "lineno": 1018 0 if instr.starts_line is None else instr.starts_line, 1019 "isJumpTarget": instr.is_jump_target, 1020 "offset": instr.offset, 1021 "opname": instr.opname, 1022 "arg": instr.arg, 1023 "argrepr": instr.argrepr, 1024 } 1025 disDict["instructions"].append(instrDict) 1026 1027 # 2. code info 1028 # Note: keep in sync with PythonDisViewer.__createCodeInfo() 1029 disDict["codeinfo"] = { 1030 "name": co.co_name, 1031 "filename": co.co_filename, 1032 "firstlineno": co.co_firstlineno, 1033 "argcount": co.co_argcount, 1034 "kwonlyargcount": co.co_kwonlyargcount, 1035 "nlocals": co.co_nlocals, 1036 "stacksize": co.co_stacksize, 1037 "flags": dis.pretty_flags(co.co_flags), 1038 "consts": [str(const) for const in co.co_consts], 1039 "names": [str(name) for name in co.co_names], 1040 "varnames": [str(name) for name in co.co_varnames], 1041 "freevars": [str(var) for var in co.co_freevars], 1042 "cellvars": [str(var) for var in co.co_cellvars], 1043 } 1044 try: 1045 disDict["codeinfo"]["posonlyargcount"] = co.co_posonlyargcount 1046 except AttributeError: 1047 # does not exist prior to 3.8.0 1048 disDict["codeinfo"]["posonlyargcount"] = 0 1049 1050 return disDict 1051 1052 def __extractSystemExitMessage(self, excinfo): 1053 """ 1054 Private method to get the SystemExit code and message. 1055 1056 @param excinfo details about the SystemExit exception 1057 @type tuple(Exception, excval object, traceback frame object) 1058 @return SystemExit code and message 1059 @rtype int, str 1060 """ 1061 exctype, excval, exctb = excinfo 1062 if excval is None: 1063 exitcode = 0 1064 message = "" 1065 elif isinstance(excval, str): 1066 exitcode = 1 1067 message = excval 1068 elif isinstance(excval, bytes): 1069 exitcode = 1 1070 message = excval.decode() 1071 elif isinstance(excval, int): 1072 exitcode = excval 1073 message = "" 1074 elif isinstance(excval, SystemExit): 1075 code = excval.code 1076 if isinstance(code, str): 1077 exitcode = 1 1078 message = code 1079 elif isinstance(code, bytes): 1080 exitcode = 1 1081 message = code.decode() 1082 elif isinstance(code, int): 1083 exitcode = code 1084 message = "" 1085 elif code is None: 1086 exitcode = 0 1087 message = "" 1088 else: 1089 exitcode = 1 1090 message = str(code) 1091 else: 1092 exitcode = 1 1093 message = str(excval) 1094 1095 return exitcode, message 1096 1097 def stop_here(self, frame): 1098 """ 1099 Public method reimplemented to filter out debugger files. 1100 1101 Tracing is turned off for files that are part of the 1102 debugger that are called from the application being debugged. 1103 1104 @param frame the frame object 1105 @type frame object 1106 @return flag indicating whether the debugger should stop here 1107 @rtype bool 1108 """ 1109 if self.__skipFrame(frame): 1110 return False 1111 1112 if frame is self.stopframe: 1113 if self.stoplineno == -1: 1114 return False 1115 return frame.f_lineno >= self.stoplineno 1116 return self.stop_everywhere or frame is self.returnframe 1117 1118 def tracePythonLibs(self, enable): 1119 """ 1120 Public method to update the settings to trace into Python libraries. 1121 1122 @param enable flag to debug into Python libraries 1123 @type bool 1124 """ 1125 pathsToSkip = list(self.pathsToSkip) 1126 # don't trace into Python library? 1127 if enable: 1128 pathsToSkip = [x for x in pathsToSkip if not x.endswith( 1129 ("site-packages", "dist-packages", self.lib))] 1130 else: 1131 pathsToSkip.append(self.lib) 1132 localLib = [x for x in sys.path if x.endswith(("site-packages", 1133 "dist-packages")) and not x.startswith(self.lib)] 1134 pathsToSkip.extend(localLib) 1135 1136 self.pathsToSkip = tuple(set(pathsToSkip)) 1137 1138 def __skipFrame(self, frame): 1139 """ 1140 Private method to filter out debugger files. 1141 1142 Tracing is turned off for files that are part of the 1143 debugger that are called from the application being debugged. 1144 1145 @param frame the frame object 1146 @type frame object 1147 @return flag indicating whether the debugger should skip this frame 1148 @rtype bool 1149 """ 1150 try: 1151 return self.filesToSkip[frame.f_code.co_filename] 1152 except KeyError: 1153 ret = frame.f_code.co_filename.startswith(self.pathsToSkip) 1154 self.filesToSkip[frame.f_code.co_filename] = ret 1155 return ret 1156 except AttributeError: 1157 # if frame is None 1158 return True 1159