Commit 404cdc5a authored by Victor Stinner's avatar Victor Stinner

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).
parent bd31b7c4
......@@ -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
......
......@@ -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
--
......
......@@ -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()
......@@ -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
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment