Implemented a signal handler for the Python debug clients.

Sun, 16 Aug 2015 13:47:44 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 16 Aug 2015 13:47:44 +0200
changeset 4366
9445c7cb991f
parent 4365
d97f2e05ae1b
child 4367
af2d7ceeb019

Implemented a signal handler for the Python debug clients.

DebugClients/Python/DebugClientBase.py file | annotate | diff | comparison | revisions
DebugClients/Python/DebugProtocol.py file | annotate | diff | comparison | revisions
DebugClients/Python3/DebugClientBase.py file | annotate | diff | comparison | revisions
DebugClients/Python3/DebugProtocol.py file | annotate | diff | comparison | revisions
Debugger/DebugProtocol.py file | annotate | diff | comparison | revisions
Debugger/DebugServer.py file | annotate | diff | comparison | revisions
Debugger/DebugUI.py file | annotate | diff | comparison | revisions
Debugger/DebuggerInterfacePython.py file | annotate | diff | comparison | revisions
Debugger/DebuggerInterfacePython3.py file | annotate | diff | comparison | revisions
QScintilla/Shell.py file | annotate | diff | comparison | revisions
--- a/DebugClients/Python/DebugClientBase.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/DebugClients/Python/DebugClientBase.py	Sun Aug 16 13:47:44 2015 +0200
@@ -17,6 +17,8 @@
 import imp
 import re
 import atexit
+import signal
+import inspect
 
 
 import DebugProtocol
@@ -521,6 +523,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 # clear all old breakpoints, they'll get set after we
                 # have started
@@ -566,6 +569,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 self.mainThread.tracePython = 0
                 
@@ -578,7 +582,7 @@
                     res = exc.code
                     atexit._run_exitfuncs()
                 self.writestream.flush()
-                self.progTerminated(res)
+                self.progTerminated(res, exit=True)
                 return
 
             if cmd == DebugProtocol.RequestCoverage:
@@ -597,6 +601,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 # generate a coverage object
                 self.cover = coverage(
@@ -619,7 +624,7 @@
                 self.cover.stop()
                 self.cover.save()
                 self.writestream.flush()
-                self.progTerminated(res)
+                self.progTerminated(res, exit=True)
                 return
             
             if cmd == DebugProtocol.RequestProfile:
@@ -639,6 +644,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 # generate a profile object
                 self.prof = PyProfile.PyProfile(sys.argv[0])
@@ -656,7 +662,7 @@
                     atexit._run_exitfuncs()
                 self.prof.save()
                 self.writestream.flush()
-                self.progTerminated(res)
+                self.progTerminated(res, exit=True)
                 return
 
             if cmd == DebugProtocol.RequestShutdown:
@@ -849,6 +855,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 try:
                     import unittest
@@ -1158,6 +1165,61 @@
         @param exctb traceback for the exception
         """
         self.mainThread.user_exception(None, (exctype, excval, exctb), 1)
+    
+    def __interceptSignals(self):
+        """
+        Private method to intercept common signals.
+        """
+        for signum in [
+            signal.SIGABRT,                 # abnormal termination
+            signal.SIGFPE,                  # floating point exception
+            signal.SIGILL,                  # illegal instruction
+            signal.SIGSEGV,                 # segmentation violation
+        ]:
+            signal.signal(signum, self.__signalHandler)
+    
+    def __signalHandler(self, signalNumber, stackFrame):
+        """
+        Private method to handle signals.
+        
+        @param signalNumber number of the signal to be handled
+        @type int
+        @param stack frame current stack frame
+        @type frame object
+        """
+        if signalNumber == signal.SIGABRT:
+            message = "Abnormal Termination"
+        elif signalNumber == signal.SIGFPE:
+            message = "Floating Point Exception"
+        elif signalNumber == signal.SIGILL:
+            message = "Illegal Instruction"
+        elif signalNumber == signal.SIGSEGV:
+            message = "Segmentation Violation"
+        else:
+            message = "Unknown Signal '%d'" % signalNumber
+        
+        filename = self.absPath(stackFrame)
+        
+        linenr = stackFrame.f_lineno
+        ffunc = stackFrame.f_code.co_name
+        
+        if ffunc == '?':
+            ffunc = ''
+        
+        if ffunc and not ffunc.startswith("<"):
+            argInfo = inspect.getargvalues(stackFrame)
+            try:
+                fargs = inspect.formatargvalues(
+                    argInfo.args, argInfo.varargs,
+                    argInfo.keywords, argInfo.locals)
+            except Exception:
+                fargs = ""
+        else:
+            fargs = ""
+        
+        siglist = [message, [filename, linenr, ffunc, fargs]]
+        
+        self.write("%s%s" % (DebugProtocol.ResponseSignal, str(siglist)))
         
     def absPath(self, fn):
         """
@@ -1232,11 +1294,13 @@
         """
         return self.running
 
-    def progTerminated(self, status):
+    def progTerminated(self, status, exit=False):
         """
         Public method to tell the debugger that the program has terminated.
         
-        @param status the return status
+        @param status return status
+        @param exit flag indicating to perform a sys.exit()
+        @type bool
         """
         if status is None:
             status = 0
@@ -1250,6 +1314,8 @@
             self.set_quit()
             self.running = None
             self.write('%s%d\n' % (DebugProtocol.ResponseExit, status))
+            if exit:
+                sys.exit(status)
         
         # reset coding
         self.__coding = self.defaultCoding
@@ -1891,6 +1957,7 @@
         # set the system exception handling function to ensure, that
         # we report on all unhandled exceptions
         sys.excepthook = self.__unhandled_exception
+        self.__interceptSignals()
         
         # now start debugging
         if enableTrace:
@@ -1948,6 +2015,7 @@
         # set the system exception handling function to ensure, that
         # we report on all unhandled exceptions
         sys.excepthook = self.__unhandled_exception
+        self.__interceptSignals()
         
         # This will eventually enter a local event loop.
         # Note the use of backquotes to cause a repr of self.running. The
--- a/DebugClients/Python/DebugProtocol.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/DebugClients/Python/DebugProtocol.py	Sun Aug 16 13:47:44 2015 +0200
@@ -50,6 +50,7 @@
 ResponseContinue = '>Continue<'
 ResponseException = '>Exception<'
 ResponseSyntax = '>SyntaxError<'
+ResponseSignal = '>Signal<'
 ResponseExit = '>Exit<'
 ResponseLine = '>Line<'
 ResponseRaw = '>Raw<'
--- a/DebugClients/Python3/DebugClientBase.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/DebugClients/Python3/DebugClientBase.py	Sun Aug 16 13:47:44 2015 +0200
@@ -17,6 +17,8 @@
 import imp
 import re
 import atexit
+import signal
+import inspect
 
 
 import DebugProtocol
@@ -512,6 +514,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 # clear all old breakpoints, they'll get set after we have
                 # started
@@ -557,6 +560,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 self.mainThread.tracePython = False
                 
@@ -571,7 +575,7 @@
                         res = exc.code
                         atexit._run_exitfuncs()
                     self.writestream.flush()
-                    self.progTerminated(res)
+                    self.progTerminated(res, exit=True)
                 return
 
             if cmd == DebugProtocol.RequestProfile:
@@ -591,6 +595,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 # generate a profile object
                 self.prof = PyProfile.PyProfile(sys.argv[0])
@@ -616,7 +621,7 @@
                         atexit._run_exitfuncs()
                     self.prof.save()
                     self.writestream.flush()
-                    self.progTerminated(res)
+                    self.progTerminated(res, exit=True)
                 return
 
             if cmd == DebugProtocol.RequestCoverage:
@@ -635,6 +640,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 # generate a coverage object
                 self.cover = coverage(
@@ -667,7 +673,7 @@
                     self.cover.stop()
                     self.cover.save()
                     self.writestream.flush()
-                    self.progTerminated(res)
+                    self.progTerminated(res, exit=True)
                 return
 
             if cmd == DebugProtocol.RequestShutdown:
@@ -859,6 +865,7 @@
                 # set the system exception handling function to ensure, that
                 # we report on all unhandled exceptions
                 sys.excepthook = self.__unhandled_exception
+                self.__interceptSignals()
                 
                 try:
                     import unittest
@@ -1170,6 +1177,61 @@
         """
         self.mainThread.user_exception(None, (exctype, excval, exctb), True)
     
+    def __interceptSignals(self):
+        """
+        Private method to intercept common signals.
+        """
+        for signum in [
+            signal.SIGABRT,                 # abnormal termination
+            signal.SIGFPE,                  # floating point exception
+            signal.SIGILL,                  # illegal instruction
+            signal.SIGSEGV,                 # segmentation violation
+        ]:
+            signal.signal(signum, self.__signalHandler)
+    
+    def __signalHandler(self, signalNumber, stackFrame):
+        """
+        Private method to handle signals.
+        
+        @param signalNumber number of the signal to be handled
+        @type int
+        @param stack frame current stack frame
+        @type frame object
+        """
+        if signalNumber == signal.SIGABRT:
+            message = "Abnormal Termination"
+        elif signalNumber == signal.SIGFPE:
+            message = "Floating Point Exception"
+        elif signalNumber == signal.SIGILL:
+            message = "Illegal Instruction"
+        elif signalNumber == signal.SIGSEGV:
+            message = "Segmentation Violation"
+        else:
+            message = "Unknown Signal '{0}'".format(signalNumber)
+        
+        filename = self.absPath(stackFrame)
+        
+        linenr = stackFrame.f_lineno
+        ffunc = stackFrame.f_code.co_name
+        
+        if ffunc == '?':
+            ffunc = ''
+        
+        if ffunc and not ffunc.startswith("<"):
+            argInfo = inspect.getargvalues(stackFrame)
+            try:
+                fargs = inspect.formatargvalues(
+                    argInfo.args, argInfo.varargs,
+                    argInfo.keywords, argInfo.locals)
+            except Exception:
+                fargs = ""
+        else:
+            fargs = ""
+        
+        siglist = [message, [filename, linenr, ffunc, fargs]]
+        
+        self.write("{0}{1}".format(DebugProtocol.ResponseSignal, str(siglist)))
+    
     def absPath(self, fn):
         """
         Public method to convert a filename to an absolute name.
@@ -1243,11 +1305,13 @@
         """
         return self.running
 
-    def progTerminated(self, status):
+    def progTerminated(self, status, exit=False):
         """
         Public method to tell the debugger that the program has terminated.
         
-        @param status the return status
+        @param status return status
+        @param exit flag indicating to perform a sys.exit()
+        @type bool
         """
         if status is None:
             status = 0
@@ -1261,6 +1325,8 @@
             self.set_quit()
             self.running = None
             self.write('{0}{1:d}\n'.format(DebugProtocol.ResponseExit, status))
+            if exit:
+                sys.exit(status)
         
         # reset coding
         self.__coding = self.defaultCoding
@@ -1950,6 +2016,7 @@
         # set the system exception handling function to ensure, that
         # we report on all unhandled exceptions
         sys.excepthook = self.__unhandled_exception
+        self.__interceptSignals()
         
         # now start debugging
         if enableTrace:
@@ -2007,6 +2074,7 @@
         # set the system exception handling function to ensure, that
         # we report on all unhandled exceptions
         sys.excepthook = self.__unhandled_exception
+        self.__interceptSignals()
         
         # This will eventually enter a local event loop.
         # Note the use of backquotes to cause a repr of self.running. The
--- a/DebugClients/Python3/DebugProtocol.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/DebugClients/Python3/DebugProtocol.py	Sun Aug 16 13:47:44 2015 +0200
@@ -50,6 +50,7 @@
 ResponseContinue = '>Continue<'
 ResponseException = '>Exception<'
 ResponseSyntax = '>SyntaxError<'
+ResponseSignal = '>Signal<'
 ResponseExit = '>Exit<'
 ResponseLine = '>Line<'
 ResponseRaw = '>Raw<'
--- a/Debugger/DebugProtocol.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/Debugger/DebugProtocol.py	Sun Aug 16 13:47:44 2015 +0200
@@ -49,6 +49,7 @@
 ResponseContinue = '>Continue<'
 ResponseException = '>Exception<'
 ResponseSyntax = '>SyntaxError<'
+ResponseSignal = '>Signal<'
 ResponseExit = '>Exit<'
 ResponseLine = '>Line<'
 ResponseRaw = '>Raw<'
--- a/Debugger/DebugServer.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/Debugger/DebugServer.py	Sun Aug 16 13:47:44 2015 +0200
@@ -68,6 +68,8 @@
         the client side
     @signal clientSyntaxError(exception) emitted after a syntax error has been
         detected on the client side
+    @signal clientSignal(signal) emitted after a signal has been generated on
+        the client side
     @signal clientExit(int) emitted with the exit status after the client has
         exited
     @signal clientClearBreak(filename, lineno) emitted after the debug client
@@ -131,6 +133,7 @@
     clientStatement = pyqtSignal(bool)
     clientException = pyqtSignal(str, str, list)
     clientSyntaxError = pyqtSignal(str, str, int, int)
+    clientSignal = pyqtSignal(str, str, int, str, str)
     clientExit = pyqtSignal(int)
     clientBreakConditionError = pyqtSignal(str, int)
     clientWatchConditionError = pyqtSignal(str)
@@ -1263,6 +1266,26 @@
         if self.running:
             self.clientSyntaxError.emit(message, filename, lineNo, characterNo)
         
+    def signalClientSignal(self, message, filename, lineNo,
+                           funcName, funcArgs):
+        """
+        Public method to process a signal generated on the client side.
+        
+        @param message message of the syntax error
+        @type str
+        @param filename translated filename of the syntax error position
+        @type str
+        @param lineNo line number of the syntax error position
+        @type int
+        @param funcName name of the function causing the signal
+        @type str
+        @param funcArgs function arguments
+        @type str
+        """
+        if self.running:
+            self.clientSignal.emit(message, filename, lineNo,
+                                   funcName, funcArgs)
+        
     def signalClientExit(self, status):
         """
         Public method to process the client exit status.
--- a/Debugger/DebugUI.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/Debugger/DebugUI.py	Sun Aug 16 13:47:44 2015 +0200
@@ -125,6 +125,7 @@
         debugServer.clientExit.connect(self.__clientExit)
         debugServer.clientSyntaxError.connect(self.__clientSyntaxError)
         debugServer.clientException.connect(self.__clientException)
+        debugServer.clientSignal.connect(self.__clientSignal)
         debugServer.clientVariables.connect(self.__clientVariables)
         debugServer.clientVariable.connect(self.__clientVariable)
         debugServer.clientBreakConditionError.connect(
@@ -1190,6 +1191,31 @@
         else:
             self.__continue()
         
+    def __clientSignal(self, message, filename, lineNo, funcName, funcArgs):
+        """
+        Private method to handle a signal generated on the client side.
+        
+        @param message message of the syntax error
+        @type str
+        @param filename translated filename of the syntax error position
+        @type str
+        @param lineNo line number of the syntax error position
+        @type int
+        @param funcName name of the function causing the signal
+        @type str
+        @param funcArgs function arguments
+        @type str
+        """
+        self.ui.raise_()
+        self.ui.activateWindow()
+        QApplication.processEvents()
+        self.viewmanager.setFileLine(filename, lineNo, True)
+        E5MessageBox.critical(
+            self.ui, Program, 
+            self.tr("""<p>The program generate the signal "{0}".<br/>"""
+                    """File: <b>{2}</b>, Line: <b>{3}</b></p>""").format(
+                    message, filename, lineNo))
+        
     def __clientGone(self, unplanned):
         """
         Private method to handle the disconnection of the debugger client.
--- a/Debugger/DebuggerInterfacePython.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/Debugger/DebuggerInterfacePython.py	Sun Aug 16 13:47:44 2015 +0200
@@ -958,6 +958,13 @@
                         message, fn, ln, cn)
                     continue
                 
+                if resp == DebugProtocol.ResponseSignal:
+                    sig = self.translate(evalArg, True)
+                    message, (fn, ln, func, args) = eval(sig)
+                    self.debugServer.signalClientSignal(
+                        message, fn, ln, func, args)
+                    continue
+                
                 if resp == DebugProtocol.ResponseExit:
                     self.__scriptName = ""
                     self.debugServer.signalClientExit(evalArg)
--- a/Debugger/DebuggerInterfacePython3.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/Debugger/DebuggerInterfacePython3.py	Sun Aug 16 13:47:44 2015 +0200
@@ -955,6 +955,14 @@
                         message, fn, ln, cn)
                     continue
                 
+                if resp == DebugProtocol.ResponseSignal:
+                    sig = line[eoc:-1]
+                    sig = self.translate(sig, True)
+                    message, (fn, ln, func, args) = eval(sig)
+                    self.debugServer.signalClientSignal(
+                        message, fn, ln, func, args)
+                    continue
+                
                 if resp == DebugProtocol.ResponseExit:
                     self.__scriptName = ""
                     self.debugServer.signalClientExit(line[eoc:-1])
--- a/QScintilla/Shell.py	Sun Aug 16 12:24:28 2015 +0200
+++ b/QScintilla/Shell.py	Sun Aug 16 13:47:44 2015 +0200
@@ -160,6 +160,7 @@
         dbs.clientCapabilities.connect(self.__clientCapabilities)
         dbs.clientException.connect(self.__clientException)
         dbs.clientSyntaxError.connect(self.__clientSyntaxError)
+        dbs.clientSignal.connect(self.__clientSignal)
         self.dbs = dbs
         
         # Initialize instance variables.
@@ -710,6 +711,29 @@
                         .format(filename, message, lineNo, characterNo)
                 )
         
+    def __clientSignal(self, message, filename, lineNo, funcName, funcArgs):
+        """
+        Private method to handle a signal generated on the client side.
+        
+        @param message message of the syntax error
+        @type str
+        @param filename translated filename of the syntax error position
+        @type str
+        @param lineNo line number of the syntax error position
+        @type int
+        @param funcName name of the function causing the signal
+        @type str
+        @param funcArgs function arguments
+        @type str
+        """
+        self.__clientError()
+        
+        self.__write(
+            self.tr("""Signal "{0}" generated in file {1} at line {2}.\n"""
+                    """Function: {3}({4})""")
+                .format(message, filename, lineNo, funcName, funcArgs)
+        )
+        
     def __clientError(self):
         """
         Private method to handle an error in the client.

eric ide

mercurial