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:
parent
bd31b7c483
commit
404cdc5a92
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue