[Python-checkins] cpython: faulthandler: add Windows exception handler

victor.stinner python-checkins at python.org
Wed Mar 23 06:23:56 EDT 2016


https://hg.python.org/cpython/rev/b114dbbe2d31
changeset:   100679:b114dbbe2d31
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Wed Mar 23 10:39:17 2016 +0100
summary:
  faulthandler: add Windows exception handler

Issue #23848: On Windows, faulthandler.enable() now also installs an exception
handler to dump the traceback of all Python threads on any Windows exception,
not only on UNIX signals (SIGSEGV, SIGFPE, SIGABRT).

files:
  Doc/library/faulthandler.rst  |    3 +
  Doc/whatsnew/3.6.rst          |    8 +
  Lib/test/test_faulthandler.py |   62 ++++-
  Misc/NEWS                     |    4 +
  Modules/faulthandler.c        |  253 ++++++++++++++++-----
  5 files changed, 253 insertions(+), 77 deletions(-)


diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst
--- a/Doc/library/faulthandler.rst
+++ b/Doc/library/faulthandler.rst
@@ -68,6 +68,9 @@
    .. versionchanged:: 3.5
       Added support for passing file descriptor to this function.
 
+   .. versionchanged:: 3.6
+      On Windows, a handler for Windows exception is also installed.
+
 .. function:: disable()
 
    Disable the fault handler: uninstall the signal handlers installed by
diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst
--- a/Doc/whatsnew/3.6.rst
+++ b/Doc/whatsnew/3.6.rst
@@ -199,6 +199,14 @@
 (Contributed by Ashley Anderson in :issue:`12006`.)
 
 
+faulthandler
+------------
+
+On Windows, the :mod:`faulthandler` module now installs an handler for Windows
+exceptions: see :func:`faulthandler.enable`. (Contributed by Victor Stinner in
+:issue:`23848`.)
+
+
 os
 --
 
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -23,6 +23,7 @@
     _testcapi = None
 
 TIMEOUT = 0.5
+MS_WINDOWS = (os.name == 'nt')
 
 def expected_traceback(lineno1, lineno2, header, min_count=1):
     regex = header
@@ -76,9 +77,9 @@
             output = output.decode('ascii', 'backslashreplace')
         return output.splitlines(), exitcode
 
-    def check_fatal_error(self, code, line_number, name_regex,
-                          filename=None, all_threads=True, other_regex=None,
-                          fd=None, know_current_thread=True):
+    def check_error(self, code, line_number, fatal_error, *,
+                    filename=None, all_threads=True, other_regex=None,
+                    fd=None, know_current_thread=True):
         """
         Check that the fault handler for fatal errors is enabled and check the
         traceback from the child process output.
@@ -93,14 +94,14 @@
         else:
             header = 'Stack'
         regex = """
-            ^Fatal Python error: {name}
+            ^{fatal_error}
 
             {header} \(most recent call first\):
               File "<string>", line {lineno} in <module>
             """
         regex = dedent(regex.format(
             lineno=line_number,
-            name=name_regex,
+            fatal_error=fatal_error,
             header=header)).strip()
         if other_regex:
             regex += '|' + other_regex
@@ -109,17 +110,36 @@
         self.assertRegex(output, regex)
         self.assertNotEqual(exitcode, 0)
 
+    def check_fatal_error(self, code, line_number, name_regex, **kw):
+        fatal_error = 'Fatal Python error: %s' % name_regex
+        self.check_error(code, line_number, fatal_error, **kw)
+
+    def check_windows_exception(self, code, line_number, name_regex, **kw):
+        fatal_error = 'Windows exception: %s' % name_regex
+        self.check_error(code, line_number, fatal_error, **kw)
+
     @unittest.skipIf(sys.platform.startswith('aix'),
                      "the first page of memory is a mapped read-only on AIX")
     def test_read_null(self):
-        self.check_fatal_error("""
-            import faulthandler
-            faulthandler.enable()
-            faulthandler._read_null()
-            """,
-            3,
-            # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion
-            '(?:Segmentation fault|Bus error|Illegal instruction)')
+        if not MS_WINDOWS:
+            self.check_fatal_error("""
+                import faulthandler
+                faulthandler.enable()
+                faulthandler._read_null()
+                """,
+                3,
+                # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion
+                '(?:Segmentation fault'
+                    '|Bus error'
+                    '|Illegal instruction)')
+        else:
+            self.check_windows_exception("""
+                import faulthandler
+                faulthandler.enable()
+                faulthandler._read_null()
+                """,
+                3,
+                'access violation')
 
     def test_sigsegv(self):
         self.check_fatal_error("""
@@ -708,6 +728,22 @@
             with self.check_stderr_none():
                 faulthandler.register(signal.SIGUSR1)
 
+    @unittest.skipUnless(MS_WINDOWS, 'specific to Windows')
+    def test_raise_exception(self):
+        for exc, name in (
+            ('EXCEPTION_ACCESS_VIOLATION', 'access violation'),
+            ('EXCEPTION_INT_DIVIDE_BY_ZERO', 'int divide by zero'),
+            ('EXCEPTION_STACK_OVERFLOW', 'stack overflow'),
+        ):
+            self.check_windows_exception(f"""
+                import faulthandler
+                faulthandler.enable()
+                faulthandler._raise_exception(faulthandler._{exc})
+                """,
+                3,
+                name)
+
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -232,6 +232,10 @@
 Library
 -------
 
+- Issue #23848: On Windows, faulthandler.enable() now also installs an
+  exception handler to dump the traceback of all Python threads on any Windows
+  exception, not only on UNIX signals (SIGSEGV, SIGFPE, SIGABRT).
+
 - Issue #26530: Add C functions :c:func:`_PyTraceMalloc_Track` and
   :c:func:`_PyTraceMalloc_Untrack` to track memory blocks using the
   :mod:`tracemalloc` module. Add :c:func:`_PyTraceMalloc_GetTraceback` to get
diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c
--- a/Modules/faulthandler.c
+++ b/Modules/faulthandler.c
@@ -119,7 +119,7 @@
        handler fails in faulthandler_fatal_error() */
     {SIGSEGV, 0, "Segmentation fault", }
 };
-static const unsigned char faulthandler_nsignals = \
+static const size_t faulthandler_nsignals = \
     Py_ARRAY_LENGTH(faulthandler_handlers);
 
 #ifdef HAVE_SIGALTSTACK
@@ -290,6 +290,19 @@
     Py_RETURN_NONE;
 }
 
+static void
+faulthandler_disable_fatal_handler(fault_handler_t *handler)
+{
+    if (!handler->enabled)
+        return;
+    handler->enabled = 0;
+#ifdef HAVE_SIGACTION
+    (void)sigaction(handler->signum, &handler->previous, NULL);
+#else
+    (void)signal(handler->signum, handler->previous);
+#endif
+}
+
 
 /* Handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals.
 
@@ -308,7 +321,7 @@
 faulthandler_fatal_error(int signum)
 {
     const int fd = fatal_error.fd;
-    unsigned int i;
+    size_t i;
     fault_handler_t *handler = NULL;
     int save_errno = errno;
 
@@ -326,12 +339,7 @@
     }
 
     /* restore the previous handler */
-#ifdef HAVE_SIGACTION
-    (void)sigaction(signum, &handler->previous, NULL);
-#else
-    (void)signal(signum, handler->previous);
-#endif
-    handler->enabled = 0;
+    faulthandler_disable_fatal_handler(handler);
 
     PUTS(fd, "Fatal Python error: ");
     PUTS(fd, handler->name);
@@ -353,20 +361,110 @@
     raise(signum);
 }
 
+#ifdef MS_WINDOWS
+static LONG WINAPI
+faulthandler_exc_handler(struct _EXCEPTION_POINTERS *exc_info)
+{
+    const int fd = fatal_error.fd;
+    DWORD code = exc_info->ExceptionRecord->ExceptionCode;
+
+    PUTS(fd, "Windows exception: ");
+    switch (code)
+    {
+    /* only format most common errors */
+    case EXCEPTION_ACCESS_VIOLATION: PUTS(fd, "access violation"); break;
+    case EXCEPTION_FLT_DIVIDE_BY_ZERO: PUTS(fd, "float divide by zero"); break;
+    case EXCEPTION_FLT_OVERFLOW: PUTS(fd, "float overflow"); break;
+    case EXCEPTION_INT_DIVIDE_BY_ZERO: PUTS(fd, "int divide by zero"); break;
+    case EXCEPTION_INT_OVERFLOW: PUTS(fd, "integer overflow"); break;
+    case EXCEPTION_IN_PAGE_ERROR: PUTS(fd, "page error"); break;
+    case EXCEPTION_STACK_OVERFLOW: PUTS(fd, "stack overflow"); break;
+    default:
+        PUTS(fd, "code 0x");
+        _Py_DumpHexadecimal(fd, code, sizeof(DWORD));
+    }
+    PUTS(fd, "\n\n");
+
+    if (code == EXCEPTION_ACCESS_VIOLATION) {
+        /* disable signal handler for SIGSEGV */
+        size_t i;
+        for (i=0; i < faulthandler_nsignals; i++) {
+            fault_handler_t *handler = &faulthandler_handlers[i];
+            if (handler->signum == SIGSEGV) {
+                faulthandler_disable_fatal_handler(handler);
+                break;
+            }
+        }
+    }
+
+    faulthandler_dump_traceback(fd, fatal_error.all_threads,
+                                fatal_error.interp);
+
+    /* call the next exception handler */
+    return EXCEPTION_CONTINUE_SEARCH;
+}
+#endif
+
 /* Install the handler for fatal signals, faulthandler_fatal_error(). */
 
-static PyObject*
-faulthandler_enable(PyObject *self, PyObject *args, PyObject *kwargs)
+int
+faulthandler_enable(void)
 {
-    static char *kwlist[] = {"file", "all_threads", NULL};
-    PyObject *file = NULL;
-    int all_threads = 1;
-    unsigned int i;
+    size_t i;
     fault_handler_t *handler;
 #ifdef HAVE_SIGACTION
     struct sigaction action;
 #endif
     int err;
+
+    if (fatal_error.enabled) {
+        return 0;
+    }
+
+    fatal_error.enabled = 1;
+
+    for (i=0; i < faulthandler_nsignals; i++) {
+        handler = &faulthandler_handlers[i];
+
+#ifdef HAVE_SIGACTION
+        action.sa_handler = faulthandler_fatal_error;
+        sigemptyset(&action.sa_mask);
+        /* Do not prevent the signal from being received from within
+           its own signal handler */
+        action.sa_flags = SA_NODEFER;
+#ifdef HAVE_SIGALTSTACK
+        if (stack.ss_sp != NULL) {
+            /* Call the signal handler on an alternate signal stack
+               provided by sigaltstack() */
+            action.sa_flags |= SA_ONSTACK;
+        }
+#endif
+        err = sigaction(handler->signum, &action, &handler->previous);
+#else
+        handler->previous = signal(handler->signum,
+                faulthandler_fatal_error);
+        err = (handler->previous == SIG_ERR);
+#endif
+        if (err) {
+            PyErr_SetFromErrno(PyExc_RuntimeError);
+            return -1;
+        }
+
+        handler->enabled = 1;
+    }
+
+#ifdef MS_WINDOWS
+    AddVectoredExceptionHandler(1, faulthandler_exc_handler);
+#endif
+    return 0;
+}
+
+static PyObject*
+faulthandler_py_enable(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = {"file", "all_threads", NULL};
+    PyObject *file = NULL;
+    int all_threads = 1;
     int fd;
     PyThreadState *tstate;
 
@@ -388,37 +486,10 @@
     fatal_error.all_threads = all_threads;
     fatal_error.interp = tstate->interp;
 
-    if (!fatal_error.enabled) {
-        fatal_error.enabled = 1;
+    if (faulthandler_enable() < 0) {
+        return NULL;
+    }
 
-        for (i=0; i < faulthandler_nsignals; i++) {
-            handler = &faulthandler_handlers[i];
-#ifdef HAVE_SIGACTION
-            action.sa_handler = faulthandler_fatal_error;
-            sigemptyset(&action.sa_mask);
-            /* Do not prevent the signal from being received from within
-               its own signal handler */
-            action.sa_flags = SA_NODEFER;
-#ifdef HAVE_SIGALTSTACK
-            if (stack.ss_sp != NULL) {
-                /* Call the signal handler on an alternate signal stack
-                   provided by sigaltstack() */
-                action.sa_flags |= SA_ONSTACK;
-            }
-#endif
-            err = sigaction(handler->signum, &action, &handler->previous);
-#else
-            handler->previous = signal(handler->signum,
-                                       faulthandler_fatal_error);
-            err = (handler->previous == SIG_ERR);
-#endif
-            if (err) {
-                PyErr_SetFromErrno(PyExc_RuntimeError);
-                return NULL;
-            }
-            handler->enabled = 1;
-        }
-    }
     Py_RETURN_NONE;
 }
 
@@ -432,14 +503,7 @@
         fatal_error.enabled = 0;
         for (i=0; i < faulthandler_nsignals; i++) {
             handler = &faulthandler_handlers[i];
-            if (!handler->enabled)
-                continue;
-#ifdef HAVE_SIGACTION
-            (void)sigaction(handler->signum, &handler->previous, NULL);
-#else
-            (void)signal(handler->signum, handler->previous);
-#endif
-            handler->enabled = 0;
+            faulthandler_disable_fatal_handler(handler);
         }
     }
 
@@ -991,7 +1055,10 @@
     Py_RETURN_NONE;
 }
 
+
 #if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)
+#define FAULTHANDLER_STACK_OVERFLOW
+
 #ifdef __INTEL_COMPILER
    /* Issue #23654: Turn off ICC's tail call optimization for the
     * stack_overflow generator. ICC turns the recursive tail call into
@@ -1005,12 +1072,21 @@
     /* allocate 4096 bytes on the stack at each call */
     unsigned char buffer[4096];
     Py_uintptr_t sp = (Py_uintptr_t)&buffer;
+    Py_uintptr_t stop;
+
     *depth += 1;
-    if (sp < min_sp || max_sp < sp)
+    if (sp < min_sp || max_sp < sp) {
+        printf("call #%lu\n", (unsigned long)*depth);
         return sp;
-    buffer[0] = 1;
-    buffer[4095] = 0;
-    return stack_overflow(min_sp, max_sp, depth);
+    }
+
+    memset(buffer, (unsigned char)*depth, sizeof(buffer));
+    stop = stack_overflow(min_sp, max_sp, depth) + buffer[0];
+
+    memset(buffer, (unsigned char)stop, sizeof(buffer));
+    stop = stack_overflow(min_sp, max_sp, depth) + buffer[0];
+
+    return stop;
 }
 
 static PyObject *
@@ -1018,13 +1094,19 @@
 {
     size_t depth, size;
     Py_uintptr_t sp = (Py_uintptr_t)&depth;
-    Py_uintptr_t stop;
+    Py_uintptr_t min_sp, max_sp, stop;
 
     faulthandler_suppress_crash_report();
+
     depth = 0;
-    stop = stack_overflow(sp - STACK_OVERFLOW_MAX_SIZE,
-                          sp + STACK_OVERFLOW_MAX_SIZE,
-                          &depth);
+    if (sp > STACK_OVERFLOW_MAX_SIZE)
+        min_sp = sp - STACK_OVERFLOW_MAX_SIZE;
+    else
+        min_sp = 0;
+    max_sp = sp + STACK_OVERFLOW_MAX_SIZE;
+
+    stop = stack_overflow(min_sp, max_sp, &depth);
+
     if (sp < stop)
         size = stop - sp;
     else
@@ -1035,7 +1117,7 @@
         size, depth);
     return NULL;
 }
-#endif
+#endif   /* (defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)) ... */
 
 
 static int
@@ -1058,12 +1140,25 @@
     return 0;
 }
 
+#ifdef MS_WINDOWS
+static PyObject *
+faulthandler_raise_exception(PyObject *self, PyObject *args)
+{
+    unsigned int code, flags = 0;
+    if (!PyArg_ParseTuple(args, "I|I:_raise_exception", &code, &flags))
+        return NULL;
+    faulthandler_suppress_crash_report();
+    RaiseException(code, flags, 0, NULL);
+    Py_RETURN_NONE;
+}
+#endif
+
 PyDoc_STRVAR(module_doc,
 "faulthandler module.");
 
 static PyMethodDef module_methods[] = {
     {"enable",
-     (PyCFunction)faulthandler_enable, METH_VARARGS|METH_KEYWORDS,
+     (PyCFunction)faulthandler_py_enable, METH_VARARGS|METH_KEYWORDS,
      PyDoc_STR("enable(file=sys.stderr, all_threads=True): "
                "enable the fault handler")},
     {"disable", (PyCFunction)faulthandler_disable_py, METH_NOARGS,
@@ -1117,10 +1212,14 @@
      PyDoc_STR("_sigfpe(): raise a SIGFPE signal")},
     {"_fatal_error", faulthandler_fatal_error_py, METH_VARARGS,
      PyDoc_STR("_fatal_error(message): call Py_FatalError(message)")},
-#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)
+#ifdef FAULTHANDLER_STACK_OVERFLOW
     {"_stack_overflow", (PyCFunction)faulthandler_stack_overflow, METH_NOARGS,
      PyDoc_STR("_stack_overflow(): recursive call to raise a stack overflow")},
 #endif
+#ifdef MS_WINDOWS
+    {"_raise_exception", faulthandler_raise_exception, METH_VARARGS,
+     PyDoc_STR("raise_exception(code, flags=0): Call RaiseException(code, flags).")},
+#endif
     {NULL, NULL}  /* sentinel */
 };
 
@@ -1139,7 +1238,33 @@
 PyMODINIT_FUNC
 PyInit_faulthandler(void)
 {
-    return PyModule_Create(&module_def);
+    PyObject *m = PyModule_Create(&module_def);
+    if (m == NULL)
+        return NULL;
+
+    /* Add constants for unit tests */
+#ifdef MS_WINDOWS
+    /* RaiseException() codes (prefixed by an underscore) */
+    if (PyModule_AddIntConstant(m, "_EXCEPTION_ACCESS_VIOLATION",
+                                EXCEPTION_ACCESS_VIOLATION))
+        return NULL;
+    if (PyModule_AddIntConstant(m, "_EXCEPTION_INT_DIVIDE_BY_ZERO",
+                                EXCEPTION_INT_DIVIDE_BY_ZERO))
+        return NULL;
+    if (PyModule_AddIntConstant(m, "_EXCEPTION_STACK_OVERFLOW",
+                                EXCEPTION_STACK_OVERFLOW))
+        return NULL;
+
+    /* RaiseException() flags (prefixed by an underscore) */
+    if (PyModule_AddIntConstant(m, "_EXCEPTION_NONCONTINUABLE",
+                                EXCEPTION_NONCONTINUABLE))
+        return NULL;
+    if (PyModule_AddIntConstant(m, "_EXCEPTION_NONCONTINUABLE_EXCEPTION",
+                                EXCEPTION_NONCONTINUABLE_EXCEPTION))
+        return NULL;
+#endif
+
+    return m;
 }
 
 /* Call faulthandler.enable() if the PYTHONFAULTHANDLER environment variable

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list