summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/faulthandler.rst3
-rw-r--r--Doc/whatsnew/3.6.rst8
-rw-r--r--Lib/test/test_faulthandler.py62
-rw-r--r--Misc/NEWS4
-rw-r--r--Modules/faulthandler.c253
5 files changed, 253 insertions, 77 deletions
diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst
index 3a5badd..3c49649 100644
--- a/Doc/library/faulthandler.rst
+++ b/Doc/library/faulthandler.rst
@@ -68,6 +68,9 @@ Fault handler state
.. 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
index 986c145..928d748 100644
--- a/Doc/whatsnew/3.6.rst
+++ b/Doc/whatsnew/3.6.rst
@@ -199,6 +199,14 @@ directives ``%G``, ``%u`` and ``%V``.
(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
index c3cd657..fbd535b 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -23,6 +23,7 @@ except ImportError:
_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 @@ class FaultHandlerTests(unittest.TestCase):
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 @@ class FaultHandlerTests(unittest.TestCase):
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 @@ class FaultHandlerTests(unittest.TestCase):
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 @@ class FaultHandlerTests(unittest.TestCase):
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
index 29fc65d..70ff3de 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -232,6 +232,10 @@ Core and Builtins
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
index 1c247b7..2214466 100644
--- a/Modules/faulthandler.c
+++ b/Modules/faulthandler.c
@@ -119,7 +119,7 @@ static fault_handler_t faulthandler_handlers[] = {
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 @@ faulthandler_dump_traceback_py(PyObject *self,
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 @@ static void
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 @@ faulthandler_fatal_error(int signum)
}
/* 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 @@ faulthandler_fatal_error(int signum)
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 @@ faulthandler_enable(PyObject *self, PyObject *args, PyObject *kwargs)
fatal_error.all_threads = all_threads;
fatal_error.interp = tstate->interp;
- if (!fatal_error.enabled) {
- 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 NULL;
- }
- handler->enabled = 1;
- }
+ if (faulthandler_enable() < 0) {
+ return NULL;
}
+
Py_RETURN_NONE;
}
@@ -432,14 +503,7 @@ faulthandler_disable(void)
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 @@ faulthandler_fatal_error_py(PyObject *self, PyObject *args)
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 @@ stack_overflow(Py_uintptr_t min_sp, Py_uintptr_t max_sp, size_t *depth)
/* 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 @@ faulthandler_stack_overflow(PyObject *self)
{
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 @@ faulthandler_stack_overflow(PyObject *self)
size, depth);
return NULL;
}
-#endif
+#endif /* (defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)) ... */
static int
@@ -1058,12 +1140,25 @@ faulthandler_traverse(PyObject *module, visitproc visit, void *arg)
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 @@ static PyMethodDef module_methods[] = {
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 @@ static struct PyModuleDef module_def = {
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