Index: configure =================================================================== --- configure (révision 87441) +++ configure (copie de travail) @@ -9249,7 +9249,7 @@ select sem_open sem_timedwait sem_getvalue sem_unlink setegid seteuid \ setgid \ setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setuid setvbuf \ - sigaction siginterrupt sigrelse snprintf strftime strlcpy \ + sigaction sigaltstack siginterrupt sigrelse snprintf strftime strlcpy \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ truncate uname unsetenv utimes waitpid wait3 wait4 \ wcscoll wcsftime wcsxfrm _getpty Index: Python/fault.c =================================================================== --- Python/fault.c (révision 0) +++ Python/fault.c (révision 0) @@ -0,0 +1,374 @@ +/* + * Fault handler for SIGSEGV, SIGFPE, SIGBUS and SIGILL signals: display the + * Python backtrace and restore the previous handler. Allocate an alternate + * stack for this handler, if sigaltstack() is available, to be able to + * allocate memory on the stack, even on stack overflow. + */ + +#include "Python.h" +#include "frameobject.h" +#include "code.h" +#include +#include + +#define MAX_DEPTH 100 + +#ifdef HAVE_SIGACTION +typedef struct sigaction _Py_sighandler_t; +#else +typedef PyOS_sighandler_t _Py_sighandler_t; +#endif + +#ifdef HAVE_SIGALTSTACK +static stack_t stack; +#endif + +static int fault_handler_enabled = 0; + +typedef struct { + int signum; + int enabled; + const char* name; + _Py_sighandler_t previous; +} fault_handler_t; + +static int fault_signals[] = { +#ifdef SIGBUS + SIGBUS, +#endif +#ifdef SIGILL + SIGILL, +#endif + SIGFPE, + /* define SIGSEGV at the end to make it the default choice if searching the + handler fails in fault_handler() */ + SIGSEGV +}; +#define NFAULT_SIGNALS (sizeof(fault_signals) / sizeof(fault_signals[0])) +static fault_handler_t fault_handlers[NFAULT_SIGNALS]; + +#define PUTS(str, fd) write(fd, str, strlen(str)) + +static void +reverse_string(char *text, const size_t len) +{ + char tmp; + size_t i, j; + if (len == 0) + return; + for (i=0, j=len-1; i < j; i++, j--) { + tmp = text[i]; + text[i] = text[j]; + text[j] = tmp; + } +} + +/* Format an integer in range [0; 999999] to decimal, + and write it into the file fd */ + +static void +dump_decimal(int value, int fd) +{ + char buffer[7]; + int len; + if (value < 0 || 999999 < value) + return; + len = 0; + do { + buffer[len] = '0' + (value % 10); + value /= 10; + len++; + } while (value); + reverse_string(buffer, len); + write(fd, buffer, len); +} + +/* Format an integer in range [0; 0xffffffff] to hexdecimal of 'width' digits, + and write it into the file fd */ + +static void +dump_hexadecimal(int width, unsigned int value, int fd) +{ + const char *hexdigits = "0123456789abcdef"; + char buffer[9]; + int len; + if (0xffffffffU < value) + return; + len = 0; + do { + buffer[len] = hexdigits[value & 15]; + value >>= 4; + len++; + } while (len < width || value); + reverse_string(buffer, len); + write(fd, buffer, len); +} + +/* Write an unicode object into the file fd using ascii+backslashreplace */ + +static void +dump_ascii(PyUnicodeObject *unicode, int fd) +{ + Py_ssize_t i, size; + Py_UNICODE *u; + char c; + + size = PyUnicode_GET_SIZE(unicode); + u = PyUnicode_AS_UNICODE(unicode); + for (i=0; i < size; i++, u++) { + if (*u < 128) { + c = (char)*u; + write(fd, &c, 1); + } + else if (*u < 256) { + PUTS("\\x", fd); + dump_hexadecimal(2, *u, fd); + } + else +#ifdef Py_UNICODE_WIDE + if (*u < 65536) +#endif + { + PUTS("\\u", fd); + dump_hexadecimal(4, *u, fd); +#ifdef Py_UNICODE_WIDE + } + else { + PUTS("\\U", fd); + dump_hexadecimal(8, *u, fd); +#endif + } + } +} + +/* Write a frame into the file fd: "File "xxx", line xxx in xxx" */ + +static void +dump_frame(PyFrameObject *frame, int fd) +{ + PyCodeObject *code; + int lineno; + + code = frame->f_code; + PUTS(" File ", fd); + if (code != NULL && code->co_filename != NULL + && PyUnicode_Check(code->co_filename)) + { + write(fd, "\"", 1); + dump_ascii((PyUnicodeObject*)code->co_filename, fd); + write(fd, "\"", 1); + } else { + PUTS("???", fd); + } + + lineno = PyFrame_GetLineNumber(frame); + PUTS(", line ", fd); + dump_decimal(lineno, fd); + PUTS(" in ", fd); + + if (code != NULL && code->co_name != NULL + && PyUnicode_Check(code->co_name)) + dump_ascii((PyUnicodeObject*)code->co_name, fd); + else + PUTS("???", fd); + + write(fd, "\n", 1); +} + +/* Write the current Python backtrace into the file 'fd': + + Traceback (most recent call first): + File "xxx", line xxx in + File "xxx", line xxx in + ... + File "xxx", line xxx in + + Write only the first MAX_DEPTH frames. If the traceback is truncated, write + the line " ...". + */ + +void +_Py_DumpBacktrace(int fd) +{ + PyThreadState *tstate; + PyFrameObject *frame; + unsigned int depth; + static int running = 0; + + if (!fault_handler_enabled) + return; + + if (running) { + /* Error: recursive call, do nothing. It may occurs if Py_FatalError() + is called (eg. by find_key()). */ + return; + } + running = 1; + + /* SIGSEGV, SIGFPE, SIGBUS and SIGILL are synchronous signals and so are + delivered to the thread that caused the fault. Get the Python thread + state of the current thread. + + PyThreadState_Get() doesn't give the state of the thread that caused the + fault if the thread released the GIL, and so this function cannot be + used. Read the thread local storage (TLS) instead: call + PyGILState_GetThisThreadState(). */ + tstate = PyGILState_GetThisThreadState(); + if (tstate == NULL) + goto error; + + frame = _PyThreadState_GetFrame(tstate); + if (frame == NULL) + goto error; + + PUTS("Traceback (most recent call first):\n", fd); + depth = 0; + while (frame != NULL) { + if (MAX_DEPTH <= depth) { + PUTS(" ...\n", fd); + break; + } + if (!PyFrame_Check(frame)) + break; + dump_frame(frame, fd); + frame = frame->f_back; + depth++; + } +error: + running = 0; +} + +/* Fault handler: display the current Python backtrace and restore the previous + handler. It should only use signal-safe functions. The previous handler will + be called when the fault handler exits, because the fault will occur + again. */ + +static void +fault_handler(int signum) +{ + const int fd = 2; /* should be fileno(stderr) */ + unsigned int i; + fault_handler_t *handler; + + /* restore the previous handler */ + for (i=0; i < NFAULT_SIGNALS; i++) { + handler = &fault_handlers[i]; + if (handler->signum == signum) + break; + } +#ifdef HAVE_SIGACTION + (void)sigaction(handler->signum, &handler->previous, NULL); +#else + (void)signal(handler->signum, handler->previous); +#endif + handler->enabled = 0; + + PUTS("Fatal Python error: ", fd); + PUTS(handler->name, fd); + PUTS("\n\n", fd); + + _Py_DumpBacktrace(fd); +} + +int _Py_InitFaultHandler(void) +{ + unsigned int i; + fault_handler_t *handler; +#ifdef HAVE_SIGACTION + struct sigaction action; + int err; +#endif + + fault_handler_enabled = 0; + if (Py_GETENV("PYTHONFAULTHANDLER") != NULL) + fault_handler_enabled = 1; + else { + PyObject *xoptions = PySys_GetXOptions(); + PyObject *item; + if (xoptions == NULL) + return -1; + item = PyDict_GetItemString(xoptions, "faulthandler"); + if (item == NULL) { + if (PyErr_Occurred()) + return -1; + } else + fault_handler_enabled = 1; + } + + if (!fault_handler_enabled) { +#ifdef HAVE_SIGALTSTACK + stack.ss_sp = NULL; +#endif + return 0; + } + +#ifdef HAVE_SIGALTSTACK + /* Try to allocate an alternate stack for fault_handler() signal handler to + * be able to allocate memory on the stack, even on a stack overflow. If it + * fails, ignore the error. */ + stack.ss_flags = SS_ONSTACK; + stack.ss_size = SIGSTKSZ; + stack.ss_sp = PyMem_Malloc(stack.ss_size); + if (stack.ss_sp != NULL) + (void)sigaltstack(&stack, NULL); +#endif + + for (i=0; i < NFAULT_SIGNALS; i++) { + handler = &fault_handlers[i]; + handler->signum = fault_signals[i];; + handler->enabled = 0; + if (handler->signum == SIGFPE) + handler->name = "Floating point exception"; +#ifdef SIGBUS + else if (handler->signum == SIGBUS) + handler->name = "Bus error"; +#endif +#ifdef SIGILL + else if (handler->signum == SIGILL) + handler->name = "Illegal instruction"; +#endif + else + handler->name = "Segmentation fault"; + } + + for (i=0; i < NFAULT_SIGNALS; i++) { + handler = &fault_handlers[i]; +#ifdef HAVE_SIGACTION + action.sa_handler = fault_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_ONSTACK; + err = sigaction(handler->signum, &action, &handler->previous); + if (!err) + handler->enabled = 1; +#else + handler->previous = signal(handler->signum, fault_handler); + if (handler->previous != SIG_ERR) + handler->enabled = 1; +#endif + } + return 0; +} + +void _Py_FiniFaultHandler(void) +{ + unsigned int i; + fault_handler_t *handler; + + for (i=0; i < NFAULT_SIGNALS; i++) { + handler = &fault_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; + } + +#ifdef HAVE_SIGALTSTACK + if (stack.ss_sp != NULL) + PyMem_Free(stack.ss_sp); +#endif +} + Index: Python/pythonrun.c =================================================================== --- Python/pythonrun.c (révision 87441) +++ Python/pythonrun.c (copie de travail) @@ -76,6 +76,10 @@ extern void _PyGILState_Fini(void); #endif /* WITH_THREAD */ +extern int _Py_InitFaultHandler(void); +extern void _Py_FiniFaultHandler(void); +extern int _Py_DumpBacktrace(int fd); + int Py_DebugFlag; /* Needed by parser.c */ int Py_VerboseFlag; /* Needed by import.c */ int Py_InteractiveFlag; /* Needed by Py_FdIsInteractive() below */ @@ -292,8 +296,12 @@ initfsencoding(); - if (install_sigs) + if (install_sigs) { initsigs(); /* Signal handling stuff, including initintr() */ + if (_Py_InitFaultHandler() < 0) + Py_FatalError( + "Py_Initialize: can't initialize the fault handler"); + } initmain(); /* Module __main__ */ if (initstdio() < 0) @@ -396,6 +404,7 @@ interp = tstate->interp; /* Disable signal handling */ + _Py_FiniFaultHandler(); PyOS_FiniInterrupts(); /* Clear type lookup cache */ @@ -2089,10 +2098,19 @@ void Py_FatalError(const char *msg) { - fprintf(stderr, "Fatal Python error: %s\n", msg); - fflush(stderr); /* it helps in Windows debug build */ - if (PyErr_Occurred()) { + const int fd = fileno(stderr); + + fputs("Fatal Python error: ", stderr); + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); + + if (PyErr_Occurred()) PyErr_PrintEx(0); + else { + fputc('\n', stderr); + fflush(stderr); + _Py_DumpBacktrace(fd); } #ifdef MS_WINDOWS { Index: PCbuild/pythoncore.vcproj =================================================================== --- PCbuild/pythoncore.vcproj (révision 87441) +++ PCbuild/pythoncore.vcproj (copie de travail) @@ -1875,6 +1875,10 @@ > + + Index: Include/pydebug.h =================================================================== --- Include/pydebug.h (révision 87441) +++ Include/pydebug.h (copie de travail) @@ -21,8 +21,8 @@ PyAPI_DATA(int) Py_UnbufferedStdioFlag; /* this is a wrapper around getenv() that pays attention to - Py_IgnoreEnvironmentFlag. It should be used for getting variables like - PYTHONPATH and PYTHONHOME from the environment */ + Py_IgnoreEnvironmentFlag. It should be used for getting PYTHON* variables + from the environment */ #define Py_GETENV(s) (Py_IgnoreEnvironmentFlag ? NULL : getenv(s)) PyAPI_FUNC(void) Py_FatalError(const char *message); Index: configure.in =================================================================== --- configure.in (révision 87441) +++ configure.in (copie de travail) @@ -2534,7 +2534,7 @@ select sem_open sem_timedwait sem_getvalue sem_unlink setegid seteuid \ setgid \ setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setuid setvbuf \ - sigaction siginterrupt sigrelse snprintf strftime strlcpy \ + sigaction sigaltstack siginterrupt sigrelse snprintf strftime strlcpy \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ truncate uname unsetenv utimes waitpid wait3 wait4 \ wcscoll wcsftime wcsxfrm _getpty) Index: Misc/NEWS =================================================================== --- Misc/NEWS (révision 87441) +++ Misc/NEWS (copie de travail) @@ -27,6 +27,10 @@ Core and Builtins ----------------- +- Issue #8863: Add an optional fault handler for SIGSEGV, SIGFPE, SIGBUS and + SIGILL signals. It can be enabled by setting PYTHONFAULTHANDLER environment + variable or with "-X faulthandler" command line option. + - Issue #8844: Regular and recursive lock acquisitions can now be interrupted by signals on platforms using pthreads. Patch by Reid Kleckner. Index: Doc/using/cmdline.rst =================================================================== --- Doc/using/cmdline.rst (révision 87441) +++ Doc/using/cmdline.rst (copie de travail) @@ -497,6 +497,13 @@ times. +.. envvar:: PYTHONFAULTHANDLER + + If this is set, Python installs a fault handler for ``SIGSEGV``, ``SIGFPE``, + ``SIGBUS`` and ``SIGILL`` signals, and display the Python backtrace on a + fatal error. + + Debug-mode variables ~~~~~~~~~~~~~~~~~~~~ Index: Lib/test/test_fault.py =================================================================== --- Lib/test/test_fault.py (révision 0) +++ Lib/test/test_fault.py (révision 0) @@ -0,0 +1,105 @@ +import _testcapi +import os +import subprocess +import sys +import test.support +import unittest + +class FaultHandlerTests(unittest.TestCase): + def check_output(self, code, line_number, name, use_xoption=False): + code = '\n'.join(code) + env = os.environ.copy() + if not use_xoption: + arguments = [sys.executable, '-c', code] + env['PYTHONFAULTHANDLER']='yes' + else: + arguments = [sys.executable, '-X', 'faulthandler', '-c', code] + try: + del env['PYTHONFAULTHANDLER'] + except KeyError: + pass + line = ' File "", line %s in ' % line_number + expected = ( + b'Fatal Python error: ' + name, + b'', + b'Traceback (most recent call first):', + line.encode('ascii')) + process = subprocess.Popen( + arguments, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + stdout, stderr = process.communicate() + lines = stderr.splitlines() + self.assertSequenceEqual(lines, expected) + + def test_sigsegv(self): + self.check_output( + ("import _testcapi", "_testcapi.sigsegv()"), + 2, + b'Segmentation fault') + + @unittest.skipIf(sys.platform == 'win32', + "SIGFPE cannot be catched on Windows") + def test_sigfpe(self): + self.check_output( + ("import _testcapi; _testcapi.sigfpe()",), + 1, + b'Floating point exception') + + @unittest.skipUnless(hasattr(_testcapi, 'sigbus'), + "need _testcapi.sigbus()") + def test_sigbus(self): + self.check_output( + ("import _testcapi", "_testcapi.sigbus()"), + 2, + b'Bus error') + + @unittest.skipUnless(hasattr(_testcapi, 'sigill'), + "need _testcapi.sigill()") + def test_sigill(self): + self.check_output( + ("import _testcapi", "_testcapi.sigill()"), + 2, + b'Illegal instruction') + + def test_fatal_error(self): + self.check_output( + ("import _testcapi", + "", + "_testcapi.fatal_error(b'Error message')"), + 3, + b'Error message') + + def test_gil_released(self): + self.check_output( + ("import _testcapi", "_testcapi.sigsegv(True)"), + 2, + b'Segmentation fault') + + def test_xoption(self): + self.check_output( + ("import _testcapi", "_testcapi.sigsegv()"), + 2, + b'Segmentation fault', + use_xoption=True) + + def test_disabled(self): + code = "import _testcapi; _testcapi.sigsegv()" + env = os.environ.copy() + try: + del env['PYTHONFAULTHANDLER'] + except KeyError: + pass + not_expected = b'Fatal Python error' + process = subprocess.Popen( + [sys.executable, '-c', code], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + stdout, stderr = process.communicate() + lines = stderr.splitlines() + self.assertNotIn(not_expected, lines) + + +def test_main(): + test.support.run_unittest(FaultHandlerTests) + +if __name__ == "__main__": + test_main() Index: Makefile.pre.in =================================================================== --- Makefile.pre.in (révision 87441) +++ Makefile.pre.in (copie de travail) @@ -327,6 +327,7 @@ Python/dtoa.o \ Python/formatter_unicode.o \ Python/fileutils.o \ + Python/fault.o \ Python/$(DYNLOADFILE) \ $(LIBOBJS) \ $(MACHDEP_OBJS) \ Index: Modules/_testcapimodule.c =================================================================== --- Modules/_testcapimodule.c (révision 87441) +++ Modules/_testcapimodule.c (copie de travail) @@ -11,6 +11,7 @@ #include #include "structmember.h" #include "datetime.h" +#include #ifdef WITH_THREAD #include "pythread.h" @@ -2257,6 +2258,62 @@ } +static PyObject * +sigsegv(PyObject *self, PyObject *args) +{ + int *x = NULL, y; + int release_gil = 0; + if (!PyArg_ParseTuple(args, "|i", &release_gil)) + return NULL; + if (release_gil) { + Py_BEGIN_ALLOW_THREADS + y = *x; + Py_END_ALLOW_THREADS + } else + y = *x; + return PyLong_FromLong(y); + +} + +static PyObject * +sigfpe(PyObject *self, PyObject *args) +{ + int x = 1, y = 0; + return PyLong_FromLong(x / y); +} + +#if defined(SIGBUS) && defined(HAVE_KILL) +static PyObject * +sigbus(PyObject *self, PyObject *args) +{ + pid_t pid = getpid(); + while(1) + kill(pid, SIGBUS); + Py_RETURN_NONE; +} +#endif + +#if defined(SIGILL) && defined(HAVE_KILL) +static PyObject * +sigill(PyObject *self, PyObject *args) +{ + pid_t pid = getpid(); + while(1) + kill(pid, SIGILL); + Py_RETURN_NONE; +} +#endif + +static PyObject * +fatal_error(PyObject *self, PyObject *args) +{ + char *message; + if (!PyArg_ParseTuple(args, "y", &message)) + return NULL; + Py_FatalError(message); + return NULL; +} + static PyMethodDef TestMethods[] = { {"raise_exception", raise_exception, METH_VARARGS}, {"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS}, @@ -2339,6 +2396,15 @@ METH_VARARGS | METH_KEYWORDS}, {"crash_no_current_thread", (PyCFunction)crash_no_current_thread, METH_NOARGS}, {"format_unicode", format_unicode, METH_VARARGS}, + {"sigsegv", sigsegv, METH_VARARGS}, + {"sigfpe", sigfpe, METH_NOARGS}, +#if defined(SIGBUS) && defined(HAVE_KILL) + {"sigbus", sigbus, METH_NOARGS}, +#endif +#if defined(SIGILL) && defined(HAVE_KILL) + {"sigill", sigill, METH_NOARGS}, +#endif + {"fatal_error", fatal_error, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; Index: Modules/main.c =================================================================== --- Modules/main.c (révision 87441) +++ Modules/main.c (copie de travail) @@ -100,6 +100,9 @@ " The default module search path uses %s.\n" "PYTHONCASEOK : ignore case in 'import' statements (Windows).\n" "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n" +"PYTHONFAULTHANDLER: if set, install a fault handler for SIGSEGV,\n" +" SIGFPE, SIGBUS and SIGILL signals, and display\n" +" the Python backtrace on a fatal error.\n" ; static int Index: pyconfig.h.in =================================================================== --- pyconfig.h.in (révision 87441) +++ pyconfig.h.in (copie de travail) @@ -626,6 +626,9 @@ /* Define to 1 if you have the `sigaction' function. */ #undef HAVE_SIGACTION +/* Define to 1 if you have the `sigaltstack' function. */ +#undef HAVE_SIGALTSTACK + /* Define to 1 if you have the `siginterrupt' function. */ #undef HAVE_SIGINTERRUPT