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).
This commit is contained in:
Victor Stinner 2016-03-23 10:39:17 +01:00
parent bd31b7c483
commit 404cdc5a92
5 changed files with 253 additions and 77 deletions

View File

@ -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

View File

@ -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
--

View File

@ -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()

View File

@ -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

View File

@ -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,9 +1212,13 @@ 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