diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index d1f47eb..8c2cd83 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -497,6 +497,12 @@ These environment variables influence Python's behavior. times. +.. envvar:: PYTHONNOFAULTHANDLER + + If this is set, Python doesn't install the fault handler for ``SIGSEGV``, + ``SIGFPE``, ``SIGBUS`` and ``SIGILL`` signals. + + Debug-mode variables ~~~~~~~~~~~~~~~~~~~~ diff --git a/Include/pydebug.h b/Include/pydebug.h index 1cbb342..d176720 100644 --- a/Include/pydebug.h +++ b/Include/pydebug.h @@ -21,8 +21,8 @@ PyAPI_DATA(int) Py_NoUserSiteDirectory; 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); diff --git a/Makefile.pre.in b/Makefile.pre.in index edbb7dd..55dd000 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -327,6 +327,7 @@ PYTHON_OBJS= \ Python/dtoa.o \ Python/formatter_unicode.o \ Python/fileutils.o \ + Python/fault.o \ Python/$(DYNLOADFILE) \ $(LIBOBJS) \ $(MACHDEP_OBJS) \ diff --git a/Misc/NEWS b/Misc/NEWS index c486a59..746d7ae 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.2 Beta 2? Core and Builtins ----------------- +- Issue #8863: Add a handler for signals SIGSEGV, SIGFPE, SIGBUS and SIGILL. It + can be disabled by setting PYTHONNOFAULTHANDLER environment variable. + - Issue #8844: Regular and recursive lock acquisitions can now be interrupted by signals on platforms using pthreads. Patch by Reid Kleckner. @@ -303,7 +306,7 @@ Library - configparser: the SafeConfigParser class has been renamed to ConfigParser. The legacy ConfigParser class has been removed but its interpolation mechanism is still available as LegacyInterpolation. - + - configparser: Usage of RawConfigParser is now discouraged for new projects in favor of ConfigParser(interpolation=None). diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index f0c07ae..e8840ac 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -11,6 +11,7 @@ #include #include "structmember.h" #include "datetime.h" +#include #ifdef WITH_THREAD #include "pythread.h" @@ -2257,6 +2258,51 @@ format_unicode(PyObject *self, PyObject *args) } +static PyObject * +sigsegv(PyObject *self, PyObject *args) +{ + int *x = NULL; + return PyLong_FromLong(*x); + +} + +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(); + kill(pid, SIGBUS); + Py_RETURN_NONE; +} +#endif + +#if defined(SIGILL) && defined(HAVE_KILL) +static PyObject * +sigill(PyObject *self, PyObject *args) +{ + pid_t pid = getpid(); + 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 +2385,15 @@ static PyMethodDef TestMethods[] = { METH_VARARGS | METH_KEYWORDS}, {"crash_no_current_thread", (PyCFunction)crash_no_current_thread, METH_NOARGS}, {"format_unicode", format_unicode, METH_VARARGS}, + {"sigsegv", sigsegv, METH_NOARGS}, + {"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 */ }; diff --git a/Modules/main.c b/Modules/main.c index 3803eee..fc8153d 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -100,6 +100,8 @@ static char *usage_5 = " 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" +"PYTHONNOFAULTHANDLER: if set, don't install the fault handler for SIGSEGV,\n" +" SIGFPE, SIGBUS and SIGILL signals.\n" ; static int diff --git a/PCbuild/pythoncore.vcproj b/PCbuild/pythoncore.vcproj index c0ffae5..4af0b95 100644 --- a/PCbuild/pythoncore.vcproj +++ b/PCbuild/pythoncore.vcproj @@ -1875,6 +1875,10 @@ > + + diff --git a/Python/pythonrun.c b/Python/pythonrun.c index f7335a2..cbecc84 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -76,6 +76,10 @@ extern void _PyGILState_Init(PyInterpreterState *, PyThreadState *); extern void _PyGILState_Fini(void); #endif /* WITH_THREAD */ +extern void _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 */ @@ -396,6 +400,7 @@ Py_Finalize(void) interp = tstate->interp; /* Disable signal handling */ + _Py_FiniFaultHandler(); PyOS_FiniInterrupts(); /* Clear type lookup cache */ @@ -2089,10 +2094,23 @@ cleanup: 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); + if (!_Py_DumpBacktrace(fd)) { + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); + } } #ifdef MS_WINDOWS { @@ -2211,6 +2229,7 @@ initsigs(void) PyOS_setsig(SIGXFSZ, SIG_IGN); #endif PyOS_InitInterrupts(); /* May imply initsignal() */ + _Py_InitFaultHandler(); } diff --git a/configure b/configure index 2846170..bc04225 100755 --- a/configure +++ b/configure @@ -9249,7 +9249,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \ 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 diff --git a/configure.in b/configure.in index fe030b3..cc78e57 100644 --- a/configure.in +++ b/configure.in @@ -2534,7 +2534,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \ 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) diff --git a/pyconfig.h.in b/pyconfig.h.in index 9fbcef8..41c5bcf 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -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 diff --git a/Lib/test/test_fault.py b/Lib/test/test_fault.py new file mode 100644 index 0000000..4038171 --- /dev/null +++ b/Lib/test/test_fault.py @@ -0,0 +1,87 @@ +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, enabled=True): + code = '\n'.join(code) + env = os.environ.copy() + if enabled: + try: + del env['PYTHONNOFAULTHANDLER'] + 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'), + name) + else: + env['PYTHONNOFAULTHANDLER']='x' + 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() + if enabled: + self.assertSequenceEqual(lines, expected) + else: + self.assertNotIn(not_expected, lines) + + def check_output(self, code, line_number, name): + self._check_output(code, line_number, name, True) + self._check_output(code, line_number, name, False) + + 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): + # Py_FatalError() behaviour doesn't depend on PYTHONNOFAULTHANDLER + # environment variable + self._check_output( + ("import _testcapi", + "", + "_testcapi.fatal_error(b'Error message')"), + 3, + b'Error message', + True) + + +def test_main(): + test.support.run_unittest(FaultHandlerTests) + +if __name__ == "__main__": + test_main() diff --git a/Python/fault.c b/Python/fault.c new file mode 100644 index 0000000..738c899 --- /dev/null +++ b/Python/fault.c @@ -0,0 +1,331 @@ +/* + * Fault handler for SIGSEGV, SIGFPE, SIGBUS and SIGILL signals: display the + * Python backtrace and abort. 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 + +#ifdef MS_WINDOWS +# include "windows.h" +#else +# include +#endif + +#define MAX_DEPTH 100 + +#ifdef HAVE_SIGALTSTACK +static stack_t stack; +#endif + +static int + sigsegv_enabled = 0, sigfpe_enabled = 0 +#ifdef SIGBUS + , sigbus_enabled = 0 +#endif +#ifdef SIGILL + , sigill_enabled = 0 +#endif + ; +#ifdef HAVE_SIGACTION +typedef struct sigaction _Py_sighandler_t; +#else +typedef PyOS_sighandler_t _Py_sighandler_t; +#endif +static _Py_sighandler_t + old_sigsegv_handler, old_sigfpe_handler +#ifdef SIGBUS + , old_sigbus_handler +#endif +#ifdef SIGILL + , old_sigill_handler +#endif + ; + +#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 " ...". + */ + +int +_Py_DumpBacktrace(int fd) +{ + PyThreadState *tstate; + PyFrameObject *frame; + unsigned int depth; + + tstate = (PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current); + if (tstate == NULL) + return -1; + + frame = _PyThreadState_GetFrame(tstate); + if (frame == NULL) + return -1; + + 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++; + } + return 0; +} + +/* Fault handler: display the current Python backtrace and abort the process. + * It should only use signal-safe functions. */ + +static void +fault_handler(int signum) +{ + const int fd = 2; /* should be fileno(stderr) */ + const char *name; + + if (signum == SIGFPE) + name = "Floating point exception"; +#ifdef SIGBUS + else if (signum == SIGBUS) + name = "Bus error"; +#endif +#ifdef SIGILL + else if (signum == SIGILL) + name = "Illegal instruction"; +#endif + else + name = "Segmentation fault"; + + PUTS("Fatal Python error: ", fd); + PUTS(name, fd); + PUTS("\n\n", fd); + + if (!_Py_DumpBacktrace(fd)) { + PUTS(name, fd); + write(fd, "\n", 1); + } + +#if defined(MS_WINDOWS) && defined(_DEBUG) + DebugBreak(); +#endif + abort(); +} + +void _Py_InitFaultHandler(void) +{ +#ifdef HAVE_SIGACTION + struct sigaction action; +#endif + + if (Py_GETENV("PYTHONNOFAULTHANDLER") != NULL) { +#ifdef HAVE_SIGALTSTACK + stack.ss_sp = NULL; +#endif + return; + } + +#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 + +#ifdef HAVE_SIGACTION + action.sa_handler = fault_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_ONSTACK; +#define install(signum, enabled, old_handler) \ + if (sigaction(signum, &action, &old_handler) == 0) enabled = 1; +#else +#define install(signum, enabled, old_handler) \ + old_handler = signal(signum, fault_handler); \ + if (old_handler != SIG_ERR) enabled = 1; +#endif + + install(SIGSEGV, sigsegv_enabled, old_sigsegv_handler); + install(SIGFPE, sigfpe_enabled, old_sigfpe_handler); +#ifdef SIGBUS + install(SIGBUS, sigbus_enabled, old_sigbus_handler); +#endif +#ifdef SIGILL + install(SIGILL, sigill_enabled, old_sigill_handler); +#endif + +#undef install +} + +void _Py_FiniFaultHandler(void) +{ +#ifdef HAVE_SIGACTION +# define restore(signum, enabled, old_handler) \ + if (enabled) (void)sigaction(signum, &old_handler, NULL) +#else +# define restore(signum, enabled, old_handler) \ + if (enabled) (void)signal(signum, old_handler); +#endif + + restore(SIGSEGV, sigsegv_enabled, old_sigsegv_handler); + restore(SIGFPE, sigfpe_enabled, old_sigfpe_handler); +#ifdef SIGBUS + restore(SIGBUS, sigbus_enabled, old_sigbus_handler); +#endif +#ifdef SIGILL + restore(SIGILL, sigill_enabled, old_sigill_handler); +#endif + +#undef restore + +#ifdef HAVE_SIGALTSTACK + if (stack.ss_sp != NULL) + PyMem_Free(stack.ss_sp); +#endif +} +