Wed, 22 Feb 2017 19:00:39 +0100
Merged with debugger changes done by Tobias.
--- a/DebugClients/Python/DebugBase.py Tue Feb 21 19:29:29 2017 +0100 +++ b/DebugClients/Python/DebugBase.py Wed Feb 22 19:00:39 2017 +0100 @@ -92,7 +92,6 @@ self.currentFrame = None # frames, where we want to stop or release debugger - self.botframe = None self.stopframe = None self.returnframe = None self.stop_everywhere = False @@ -312,19 +311,13 @@ return self.trace_dispatch if event == 'call': - if self.botframe is None and frame.f_lineno >= 1: - self.botframe = frame - frame.f_trace = self.trace_dispatch - - self.user_line(frame) - return self.trace_dispatch - - if not (self.stop_here(frame) or + if (self.stop_here(frame) or self.__checkBreakInFrame(frame) or Watch.watches != []): + return self.trace_dispatch + else: # No need to trace this function - return - return self.trace_dispatch + return if event == 'return': return @@ -384,7 +377,6 @@ # stop at erics debugger frame or a threading bootstrap if (frame.f_back.f_code == stopOnHandleLine): frame.f_trace = self.trace_dispatch - self.botframe = frame break frame = frame.f_back @@ -408,17 +400,9 @@ @type dict """ try: - frame = sys._getframe() - self.botframe = frame - # First time the dispach function is called, a "base debug" - # function has to be returned, which is called at every user code - # function call. Because of setting botframe manually, the - # specific branch returning the trace_dispatch itself is skipped. - # Because the next step is always in threading.py and we assume - # that there is no breakpoint in it, it's save to return - # trace_dispatch unconditionally. - sys.settrace(lambda frame, event, arg: self.trace_dispatch) - frame.f_trace = self.trace_dispatch + # Because in the initial run method the "base debug" function is + # set up, it's also valid for the threads afterwards. + sys.settrace(self.trace_dispatch) target(*args, **kwargs) except Exception: @@ -450,6 +434,10 @@ cmd = compile(cmd, "<string>", "exec") if debug: + # First time the trace_dispatch function is called, a "base debug" + # function has to be returned, which is called at every user code + # function call. This is ensured by setting stop_everywhere. + self.stop_everywhere = True sys.settrace(self.trace_dispatch) try:
--- a/DebugClients/Python/ThreadExtension.py Tue Feb 21 19:29:29 2017 +0100 +++ b/DebugClients/Python/ThreadExtension.py Wed Feb 22 19:00:39 2017 +0100 @@ -35,6 +35,7 @@ self.threadNumber = 1 self.enableImportHooks = True self._original_start_new_thread = None + self.threadingAttached = False self._qtThread = None self.clientLock = threading.RLock() @@ -250,7 +251,8 @@ return None if fullname in [self.threadModName, 'PyQt4.QtCore', 'PyQt5.QtCore', - 'PySide.QtCore'] and self.enableImportHooks: + 'PySide.QtCore', 'PySide2.QtCore', + 'threading'] and self.enableImportHooks: # Disable hook to be able to import original module self.enableImportHooks = False return self @@ -273,8 +275,40 @@ # make thread hooks available to system self._original_start_new_thread = module.start_new_thread module.start_new_thread = self.attachThread - elif fullname in ['PyQt4.QtCore', 'PyQt5.QtCore', - 'PySide.QtCore'] and self._qtThread is None: + + # Add hook for threading.run() + elif (fullname == "threading" and self.threadingAttached is False): + self.threadingAttached = True + + # _debugClient as a class attribute can't be accessed in following + # class. Therefore we need a global variable. + _debugClient = self + + def _bootstrap(self, run): + newThread = _debugClient.threads[self.ident] + newThread.name = self.name + # see DebugBase.bootstrap + sys.settrace(newThread.trace_dispatch) + try: + run() + except Exception: + excinfo = sys.exc_info() + newThread.user_exception(excinfo, True) + + class ThreadWrapper(module.Thread): + + def __init__(self, *args, **kwargs): + # Overwrite the provided run method with our own, to + # intercept the thread creation by threading.Thread + self.run = lambda s=self, run=self.run: _bootstrap(s, run) + + super(ThreadWrapper, self).__init__(*args, **kwargs) + + module.Thread = ThreadWrapper + + elif (fullname in ['PyQt4.QtCore', 'PyQt5.QtCore', + 'PySide.QtCore', 'PySide2.QtCore'] and + self._qtThread is None): self._qtThread = module.QThread # _debugClient as a class attribute can't be accessed in following # class. Therefore we need a global variable.