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 ...@@ -68,6 +68,9 @@ Fault handler state
.. versionchanged:: 3.5 .. versionchanged:: 3.5
Added support for passing file descriptor to this function. Added support for passing file descriptor to this function.
.. versionchanged:: 3.6
On Windows, a handler for Windows exception is also installed.
.. function:: disable() .. function:: disable()
Disable the fault handler: uninstall the signal handlers installed by Disable the fault handler: uninstall the signal handlers installed by
......
...@@ -199,6 +199,14 @@ directives ``%G``, ``%u`` and ``%V``. ...@@ -199,6 +199,14 @@ directives ``%G``, ``%u`` and ``%V``.
(Contributed by Ashley Anderson in :issue:`12006`.) (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 os
-- --
......
...@@ -23,6 +23,7 @@ except ImportError: ...@@ -23,6 +23,7 @@ except ImportError:
_testcapi = None _testcapi = None
TIMEOUT = 0.5 TIMEOUT = 0.5
MS_WINDOWS = (os.name == 'nt')
def expected_traceback(lineno1, lineno2, header, min_count=1): def expected_traceback(lineno1, lineno2, header, min_count=1):
regex = header regex = header
...@@ -76,7 +77,7 @@ class FaultHandlerTests(unittest.TestCase): ...@@ -76,7 +77,7 @@ class FaultHandlerTests(unittest.TestCase):
output = output.decode('ascii', 'backslashreplace') output = output.decode('ascii', 'backslashreplace')
return output.splitlines(), exitcode return output.splitlines(), exitcode
def check_fatal_error(self, code, line_number, name_regex, def check_error(self, code, line_number, fatal_error, *,
filename=None, all_threads=True, other_regex=None, filename=None, all_threads=True, other_regex=None,
fd=None, know_current_thread=True): fd=None, know_current_thread=True):
""" """
...@@ -93,14 +94,14 @@ class FaultHandlerTests(unittest.TestCase): ...@@ -93,14 +94,14 @@ class FaultHandlerTests(unittest.TestCase):
else: else:
header = 'Stack' header = 'Stack'
regex = """ regex = """
^Fatal Python error: {name} ^{fatal_error}
{header} \(most recent call first\): {header} \(most recent call first\):
File "<string>", line {lineno} in <module> File "<string>", line {lineno} in <module>
""" """
regex = dedent(regex.format( regex = dedent(regex.format(
lineno=line_number, lineno=line_number,
name=name_regex, fatal_error=fatal_error,
header=header)).strip() header=header)).strip()
if other_regex: if other_regex:
regex += '|' + other_regex regex += '|' + other_regex
...@@ -109,9 +110,18 @@ class FaultHandlerTests(unittest.TestCase): ...@@ -109,9 +110,18 @@ class FaultHandlerTests(unittest.TestCase):
self.assertRegex(output, regex) self.assertRegex(output, regex)
self.assertNotEqual(exitcode, 0) 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'), @unittest.skipIf(sys.platform.startswith('aix'),
"the first page of memory is a mapped read-only on AIX") "the first page of memory is a mapped read-only on AIX")
def test_read_null(self): def test_read_null(self):
if not MS_WINDOWS:
self.check_fatal_error(""" self.check_fatal_error("""
import faulthandler import faulthandler
faulthandler.enable() faulthandler.enable()
...@@ -119,7 +129,17 @@ class FaultHandlerTests(unittest.TestCase): ...@@ -119,7 +129,17 @@ class FaultHandlerTests(unittest.TestCase):
""", """,
3, 3,
# Issue #12700: Read NULL raises SIGILL on Mac OS X Lion # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion
'(?:Segmentation fault|Bus error|Illegal instruction)') '(?: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): def test_sigsegv(self):
self.check_fatal_error(""" self.check_fatal_error("""
...@@ -708,6 +728,22 @@ class FaultHandlerTests(unittest.TestCase): ...@@ -708,6 +728,22 @@ class FaultHandlerTests(unittest.TestCase):
with self.check_stderr_none(): with self.check_stderr_none():
faulthandler.register(signal.SIGUSR1) 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__": if __name__ == "__main__":
unittest.main() unittest.main()
...@@ -232,6 +232,10 @@ Core and Builtins ...@@ -232,6 +232,10 @@ Core and Builtins
Library 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 - Issue #26530: Add C functions :c:func:`_PyTraceMalloc_Track` and
:c:func:`_PyTraceMalloc_Untrack` to track memory blocks using the :c:func:`_PyTraceMalloc_Untrack` to track memory blocks using the
:mod:`tracemalloc` module. Add :c:func:`_PyTraceMalloc_GetTraceback` to get :mod:`tracemalloc` module. Add :c:func:`_PyTraceMalloc_GetTraceback` to get
......
...@@ -119,7 +119,7 @@ static fault_handler_t faulthandler_handlers[] = { ...@@ -119,7 +119,7 @@ static fault_handler_t faulthandler_handlers[] = {
handler fails in faulthandler_fatal_error() */ handler fails in faulthandler_fatal_error() */
{SIGSEGV, 0, "Segmentation fault", } {SIGSEGV, 0, "Segmentation fault", }
}; };
static const unsigned char faulthandler_nsignals = \ static const size_t faulthandler_nsignals = \
Py_ARRAY_LENGTH(faulthandler_handlers); Py_ARRAY_LENGTH(faulthandler_handlers);
#ifdef HAVE_SIGALTSTACK #ifdef HAVE_SIGALTSTACK
...@@ -290,6 +290,19 @@ faulthandler_dump_traceback_py(PyObject *self, ...@@ -290,6 +290,19 @@ faulthandler_dump_traceback_py(PyObject *self,
Py_RETURN_NONE; 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. /* Handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals.
...@@ -308,7 +321,7 @@ static void ...@@ -308,7 +321,7 @@ static void
faulthandler_fatal_error(int signum) faulthandler_fatal_error(int signum)
{ {
const int fd = fatal_error.fd; const int fd = fatal_error.fd;
unsigned int i; size_t i;
fault_handler_t *handler = NULL; fault_handler_t *handler = NULL;
int save_errno = errno; int save_errno = errno;
...@@ -326,12 +339,7 @@ faulthandler_fatal_error(int signum) ...@@ -326,12 +339,7 @@ faulthandler_fatal_error(int signum)
} }
/* restore the previous handler */ /* restore the previous handler */
#ifdef HAVE_SIGACTION faulthandler_disable_fatal_handler(handler);
(void)sigaction(signum, &handler->previous, NULL);
#else
(void)signal(signum, handler->previous);
#endif
handler->enabled = 0;
PUTS(fd, "Fatal Python error: "); PUTS(fd, "Fatal Python error: ");
PUTS(fd, handler->name); PUTS(fd, handler->name);
...@@ -353,46 +361,71 @@ faulthandler_fatal_error(int signum) ...@@ -353,46 +361,71 @@ faulthandler_fatal_error(int signum)
raise(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(). */ /* Install the handler for fatal signals, faulthandler_fatal_error(). */
static PyObject* int
faulthandler_enable(PyObject *self, PyObject *args, PyObject *kwargs) faulthandler_enable(void)
{ {
static char *kwlist[] = {"file", "all_threads", NULL}; size_t i;
PyObject *file = NULL;
int all_threads = 1;
unsigned int i;
fault_handler_t *handler; fault_handler_t *handler;
#ifdef HAVE_SIGACTION #ifdef HAVE_SIGACTION
struct sigaction action; struct sigaction action;
#endif #endif
int err; int err;
int fd;
PyThreadState *tstate;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"|Oi:enable", kwlist, &file, &all_threads))
return NULL;
fd = faulthandler_get_fileno(&file); if (fatal_error.enabled) {
if (fd < 0) return 0;
return NULL; }
tstate = get_thread_state();
if (tstate == NULL)
return NULL;
Py_XINCREF(file);
Py_SETREF(fatal_error.file, file);
fatal_error.fd = fd;
fatal_error.all_threads = all_threads;
fatal_error.interp = tstate->interp;
if (!fatal_error.enabled) {
fatal_error.enabled = 1; fatal_error.enabled = 1;
for (i=0; i < faulthandler_nsignals; i++) { for (i=0; i < faulthandler_nsignals; i++) {
handler = &faulthandler_handlers[i]; handler = &faulthandler_handlers[i];
#ifdef HAVE_SIGACTION #ifdef HAVE_SIGACTION
action.sa_handler = faulthandler_fatal_error; action.sa_handler = faulthandler_fatal_error;
sigemptyset(&action.sa_mask); sigemptyset(&action.sa_mask);
...@@ -414,11 +447,49 @@ faulthandler_enable(PyObject *self, PyObject *args, PyObject *kwargs) ...@@ -414,11 +447,49 @@ faulthandler_enable(PyObject *self, PyObject *args, PyObject *kwargs)
#endif #endif
if (err) { if (err) {
PyErr_SetFromErrno(PyExc_RuntimeError); PyErr_SetFromErrno(PyExc_RuntimeError);
return NULL; return -1;
} }
handler->enabled = 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;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"|Oi:enable", kwlist, &file, &all_threads))
return NULL;
fd = faulthandler_get_fileno(&file);
if (fd < 0)
return NULL;
tstate = get_thread_state();
if (tstate == NULL)
return NULL;
Py_XINCREF(file);
Py_SETREF(fatal_error.file, file);
fatal_error.fd = fd;
fatal_error.all_threads = all_threads;
fatal_error.interp = tstate->interp;
if (faulthandler_enable() < 0) {
return NULL;
} }
Py_RETURN_NONE; Py_RETURN_NONE;
} }
...@@ -432,14 +503,7 @@ faulthandler_disable(void) ...@@ -432,14 +503,7 @@ faulthandler_disable(void)
fatal_error.enabled = 0; fatal_error.enabled = 0;
for (i=0; i < faulthandler_nsignals; i++) { for (i=0; i < faulthandler_nsignals; i++) {
handler = &faulthandler_handlers[i]; handler = &faulthandler_handlers[i];
if (!handler->enabled) faulthandler_disable_fatal_handler(handler);
continue;
#ifdef HAVE_SIGACTION
(void)sigaction(handler->signum, &handler->previous, NULL);
#else
(void)signal(handler->signum, handler->previous);
#endif
handler->enabled = 0;
} }
} }
...@@ -991,7 +1055,10 @@ faulthandler_fatal_error_py(PyObject *self, PyObject *args) ...@@ -991,7 +1055,10 @@ faulthandler_fatal_error_py(PyObject *self, PyObject *args)
Py_RETURN_NONE; Py_RETURN_NONE;
} }
#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION) #if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)
#define FAULTHANDLER_STACK_OVERFLOW
#ifdef __INTEL_COMPILER #ifdef __INTEL_COMPILER
/* Issue #23654: Turn off ICC's tail call optimization for the /* Issue #23654: Turn off ICC's tail call optimization for the
* stack_overflow generator. ICC turns the recursive tail call into * 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) ...@@ -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 */ /* allocate 4096 bytes on the stack at each call */
unsigned char buffer[4096]; unsigned char buffer[4096];
Py_uintptr_t sp = (Py_uintptr_t)&buffer; Py_uintptr_t sp = (Py_uintptr_t)&buffer;
Py_uintptr_t stop;
*depth += 1; *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; 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 * static PyObject *
...@@ -1018,13 +1094,19 @@ faulthandler_stack_overflow(PyObject *self) ...@@ -1018,13 +1094,19 @@ faulthandler_stack_overflow(PyObject *self)
{ {
size_t depth, size; size_t depth, size;
Py_uintptr_t sp = (Py_uintptr_t)&depth; Py_uintptr_t sp = (Py_uintptr_t)&depth;
Py_uintptr_t stop; Py_uintptr_t min_sp, max_sp, stop;
faulthandler_suppress_crash_report(); faulthandler_suppress_crash_report();
depth = 0; depth = 0;
stop = stack_overflow(sp - STACK_OVERFLOW_MAX_SIZE, if (sp > STACK_OVERFLOW_MAX_SIZE)
sp + STACK_OVERFLOW_MAX_SIZE, min_sp = sp - STACK_OVERFLOW_MAX_SIZE;
&depth); else
min_sp = 0;
max_sp = sp + STACK_OVERFLOW_MAX_SIZE;
stop = stack_overflow(min_sp, max_sp, &depth);
if (sp < stop) if (sp < stop)
size = stop - sp; size = stop - sp;
else else
...@@ -1035,7 +1117,7 @@ faulthandler_stack_overflow(PyObject *self) ...@@ -1035,7 +1117,7 @@ faulthandler_stack_overflow(PyObject *self)
size, depth); size, depth);
return NULL; return NULL;
} }
#endif #endif /* (defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)) ... */
static int static int
...@@ -1058,12 +1140,25 @@ faulthandler_traverse(PyObject *module, visitproc visit, void *arg) ...@@ -1058,12 +1140,25 @@ faulthandler_traverse(PyObject *module, visitproc visit, void *arg)
return 0; 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, PyDoc_STRVAR(module_doc,
"faulthandler module."); "faulthandler module.");
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
{"enable", {"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): " PyDoc_STR("enable(file=sys.stderr, all_threads=True): "
"enable the fault handler")}, "enable the fault handler")},
{"disable", (PyCFunction)faulthandler_disable_py, METH_NOARGS, {"disable", (PyCFunction)faulthandler_disable_py, METH_NOARGS,
...@@ -1117,9 +1212,13 @@ static PyMethodDef module_methods[] = { ...@@ -1117,9 +1212,13 @@ static PyMethodDef module_methods[] = {
PyDoc_STR("_sigfpe(): raise a SIGFPE signal")}, PyDoc_STR("_sigfpe(): raise a SIGFPE signal")},
{"_fatal_error", faulthandler_fatal_error_py, METH_VARARGS, {"_fatal_error", faulthandler_fatal_error_py, METH_VARARGS,
PyDoc_STR("_fatal_error(message): call Py_FatalError(message)")}, 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, {"_stack_overflow", (PyCFunction)faulthandler_stack_overflow, METH_NOARGS,
PyDoc_STR("_stack_overflow(): recursive call to raise a stack overflow")}, 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 #endif
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };
...@@ -1139,7 +1238,33 @@ static struct PyModuleDef module_def = { ...@@ -1139,7 +1238,33 @@ static struct PyModuleDef module_def = {
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit_faulthandler(void) 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 /* 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