diff -r 05e1ca25f7dc -r ec274420e9e2 Doc/library/debug.rst --- a/Doc/library/debug.rst Wed Mar 23 18:12:24 2011 -0700 +++ b/Doc/library/debug.rst Fri Mar 25 17:05:39 2011 +0100 @@ -10,7 +10,8 @@ .. toctree:: bdb.rst + faulthandler.rst pdb.rst profile.rst timeit.rst - trace.rst \ No newline at end of file + trace.rst diff -r 05e1ca25f7dc -r ec274420e9e2 Doc/library/faulthandler.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Doc/library/faulthandler.rst Fri Mar 25 17:05:39 2011 +0100 @@ -0,0 +1,166 @@ +:mod:`faulthandler` --- Dump the Python traceback +================================================= + +.. module:: faulthandler + :synopsis: Dump the Python traceback. + +This module contains functions to dump the Python traceback explicitly, on a +fault, after a timeout or on a user signal. Call :func:`faulthandler.enable` to +install fault handlers for :const:`SIGSEGV`, :const:`SIGFPE`, :const:`SIGBUS` +and :const:`SIGILL` signals. You can also enable them at startup using the +:envvar:`PYTHONFAULTHANDLER` environment variable or :option:`-X` +``faulthandler=1`` command line option. + +The fault handler is compatible with system fault handlers like Apport or +the Windows fault handler. The module uses an alternative stack for signal +handler, if the :c:func:`sigaltstack` function is available, to be able to dump +the traceback even on a stack overflow. + +The fault handler is called on catastrophic cases and so can only use +signal-safe functions (e.g. it cannot allocate memory on the heap). That's why +the traceback is limited: only support ASCII encoding (use the +``backslashreplace`` error handler), limit each string to 100 characters, don't +print the source code (only the filename, the function name and the line +number), limit to 100 frames and 100 threads. + +By default, the Python traceback is written to :data:`sys.stderr`. Start your +graphical applications in a terminal and run your server in foreground to see +the traceback, or specify a log file to :func:`faulthandler.enable()`. + +The module is implemented in C to be able to dump a traceback on a crash or +when Python is blocked (e.g. deadlock). + + +Dump the traceback +------------------ + +.. function:: dump_traceback(file=sys.stderr, all_threads=False) + + Dump the traceback of the current thread, or of all threads if *all_threads* + is ``True``, into *file*. + + +Fault handler state +------------------- + +.. function:: enable(file=sys.stderr, all_threads=False) + + Enable the fault handler: install handlers for :const:`SIGSEGV`, + :const:`SIGFPE`, :const:`SIGBUS` and :const:`SIGILL` signals to dump the + Python traceback on fault. It dumps the traceback of the current thread, or + all threads if *all_threads* is ``True``, into *file*. + + It keeps a reference to *file*: use :func:`disable` to clear this reference. + +.. function:: disable() + + Disable the fault handler: uninstall the signal handlers installed by + enable(). + +.. function:: is_enabled() + + Check if the fault handler is enabled. + + +Dump the traceback after a timeout +---------------------------------- + +.. function:: dump_traceback_later(delay, repeat=False, file=sys.stderr, all_threads=False) + + Dump the traceback of the current thread, or of all threads if *all_threads* + is ``True``, after a timeout of *delay* seconds, or each *delay* seconds if + *repeat* is ``True``. If the function is called twice, the new call replaces + previous parameters (reset the timeout). + + It is implemented using the :const:`SIGALRM` signal and the + :c:func:`alarm()` function: if the signal handler is called during a system + call, the system call is interrupted (return :const:`EINTR`). + + It keeps a reference to *file*: use :func:`cancel_dump_traceback_later` to + clear this reference. + + Not available on Windows. + +.. function:: cancel_dump_traceback_later() + + Cancel the last call to :func:`dump_traceback_later`. + + Not available on Windows. + + +Dump the traceback on a user signal +----------------------------------- + +.. function:: register(signum, file=sys.stderr, all_threads=False) + + Register a user signal: install a handler for the *signum* signal to dump + the traceback of the current thread, or of all threads if *all_threads* is + ``True``, into *file*. + + It keeps a reference to *file*: use :func:`unregister` to clear this + reference. + +.. function:: unregister(signum) + + Unregister a user signal: uninstall the handler of the *signum* signal + installed by :func:`register`. + + +File descriptor issue +--------------------- + +:func:`enable`, :func:`dump_traceback_later` and :func:`register` keeps the +file descriptor of their *file* argument. If the file is closed and its file +descriptor is reused by a new file, or if :func:`os.dup2` is used to replace +the file descriptor, the traceback will be written into a different file. Call +these functions again each time that the file is replaced. + + +Functions to test the fault handler +----------------------------------- + +.. function:: sigsegv() + + Raise a :const:`SIGSEGV` signal (Segmentation fault), read memory from NULL + (address 0). + +.. function:: sigfpe() + + Raise a :const:`SIGFPE` signal (Floating point exception), divide a number by zero. + +.. function:: sigbus() + + Raise a :const:`SIGBUS` signal (Bus error). + + Availability: Unix. + +.. function:: sigill() + + Raise a :const:`SIGILL` signal (Illegal instruction). + + Availability: Unix. + + +Module version +-------------- + +The module version can be read in the :data:`faulthandler.version` attribute: +use ``version >> 8`` to get the major version, and ``version & 255`` to get the +minor version. + + +Example +------- + +Example of a segmentation fault on Linux: :: + + $ python -q -X faulthandler + >>> import ctypes + >>> ctypes.string_at(0) + Fatal Python error: Segmentation fault + + Traceback (most recent call first): + File "/home/python/cpython/Lib/ctypes/__init__.py", line 486 in string_at + File "", line 1 in + Segmentation fault + diff -r 05e1ca25f7dc -r ec274420e9e2 Doc/using/cmdline.rst --- a/Doc/using/cmdline.rst Wed Mar 23 18:12:24 2011 -0700 +++ b/Doc/using/cmdline.rst Fri Mar 25 17:05:39 2011 +0100 @@ -498,6 +498,12 @@ separated string, it is equivalent to specifying :option:`-W` multiple times. +.. envvar:: PYTHONFAULTHANDLER + + Call :func:`faulthandler.enable` at startup: install a handler for SIGSEGV, + SIGFPE, SIGBUS and SIGILL signals to dump the Python backtrace on fault. + This is equivalent to :option:`-X` ``faulthandler=1`` option. + Debug-mode variables ~~~~~~~~~~~~~~~~~~~~ diff -r 05e1ca25f7dc -r ec274420e9e2 Include/traceback.h --- a/Include/traceback.h Wed Mar 23 18:12:24 2011 -0700 +++ b/Include/traceback.h Fri Mar 25 17:05:39 2011 +0100 @@ -5,6 +5,8 @@ extern "C" { #endif +#include "pystate.h" + struct _frame; /* Traceback interface */ @@ -28,6 +30,14 @@ PyAPI_DATA(PyTypeObject) PyTraceBack_Type; #define PyTraceBack_Check(v) (Py_TYPE(v) == &PyTraceBack_Type) +PyAPI_DATA(int) _Py_DumpTraceback( + int fd, + PyThreadState *tstate); +PyAPI_DATA(const char*) _Py_DumpTracebackThreads( + int fd, + PyThreadState *current_thread); + + #ifdef __cplusplus } #endif diff -r 05e1ca25f7dc -r ec274420e9e2 Lib/test/test_faulthandler.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/test/test_faulthandler.py Fri Mar 25 17:05:39 2011 +0100 @@ -0,0 +1,459 @@ +from contextlib import contextmanager +import faulthandler +import os +import re +import signal +import subprocess +import sys +from test import support +import tempfile +import unittest + +faulthandler.enable() + +Py_REF_DEBUG = hasattr(sys, 'gettotalrefcount') + +try: + skipIf = unittest.skipIf +except AttributeError: + import functools + def skipIf(test, reason): + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kw): + if not test: + return func(*args, **kw) + else: + print("skip %s: %s" % (func.__name__, reason)) + return wrapper + return decorator + +def decode_output(output): + return output.decode('ascii', 'backslashreplace') + +def read_file(filename): + with open(filename, "rb") as fp: + output = fp.read() + return decode_output(output) + +def expected_traceback(line1, line2, all_threads): + if all_threads: + expected = ['Current thread XXX:'] + else: + expected = ['Traceback (most recent call first):'] + expected.extend(( + ' File "", line %s in func' % line1, + ' File "", line %s in ' % line2)) + return expected + +@contextmanager +def temporary_filename(): + filename = tempfile.mktemp() + try: + yield filename + finally: + try: + os.unlink(filename) + except OSError: + pass + +class FaultHandlerTests(unittest.TestCase): + def setUp(self): + faulthandler.disable() + + def get_output(self, code, filename=None): + """ + Run the specified code in Python (in a new child process) and get the + output: read from standard error or from a file (if filename is set). + Return the output as a list. + + Strip the reference count from the standard error for Python debug + build. + """ + code = '\n'.join(code) + process = subprocess.Popen( + [sys.executable, '-c', code], + stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + if filename: + output = read_file(filename) + else: + output = decode_output(stderr) + if Py_REF_DEBUG: + output = re.sub(r"\[\d+ refs\]\r?\n?$", "", output) + output = re.sub('Current thread 0x[0-9a-f]+', + 'Current thread XXX', + output) + return output.splitlines() + + def check_fatal_error(self, code, line_number, name, + filename=None, all_threads=False): + """ + Check that the fault handler for fatal errors is enabled and check the + traceback from the child process output. + + Raise an error if the output doesn't match the expected format. + """ + expected = [ + 'Fatal Python error: ' + name, + ''] + if all_threads: + expected.append('Current thread XXX:') + else: + expected.append('Traceback (most recent call first):') + expected.append(' File "", line %s in ' % line_number) + lines = self.get_output(code, filename) + self.assertEqual(lines, expected) + + def test_sigsegv(self): + self.check_fatal_error( + ("import faulthandler; faulthandler.enable()", + "faulthandler.sigsegv()"), + 2, + 'Segmentation fault') + + @skipIf(sys.platform == 'win32', + "SIGFPE cannot be caught on Windows") + def test_sigfpe(self): + self.check_fatal_error( + ("import faulthandler; faulthandler.enable(); " + "faulthandler.sigfpe()",), + 1, + 'Floating point exception') + + @skipIf(not hasattr(faulthandler, 'sigbus'), + "need faulthandler.sigbus()") + def test_sigbus(self): + self.check_fatal_error( + ("import faulthandler; faulthandler.enable()", + "faulthandler.sigbus()"), + 2, + 'Bus error') + + @skipIf(not hasattr(faulthandler, 'sigill'), + "need faulthandler.sigill()") + def test_sigill(self): + self.check_fatal_error( + ("import faulthandler; faulthandler.enable()", + "faulthandler.sigill()"), + 2, + 'Illegal instruction') + + def test_gil_released(self): + self.check_fatal_error( + ("import faulthandler; faulthandler.enable()", + "faulthandler.sigsegv(True)"), + 2, + 'Segmentation fault') + + def test_enable_file(self): + with temporary_filename() as filename: + self.check_fatal_error( + ("import faulthandler", + "output = open(%r, 'wb')" % filename, + "faulthandler.enable(output)", + "faulthandler.sigsegv(True)"), + 4, + 'Segmentation fault', + filename=filename) + + def test_enable_threads(self): + self.check_fatal_error( + ("import faulthandler", + "faulthandler.enable(all_threads=True)", + "faulthandler.sigsegv(True)"), + 3, + 'Segmentation fault', + all_threads=True) + + def check_fatal_error_disabled(self, *code): + """ + Ensure that the faulthandle is disabled when a fatal error occurs. + """ + not_expected = 'Fatal Python error' + stderr = self.get_output(code) + stder = '\n'.join(stderr) + self.assertTrue(not_expected not in stderr, + "%r is present in %r" % (not_expected, stderr)) + + def test_disabled(self): + self.check_fatal_error_disabled( + "import faulthandler", + "faulthandler.sigsegv()") + + def test_enable_disable(self): + self.check_fatal_error_disabled( + "import faulthandler", + "faulthandler.enable()", + "faulthandler.disable()", + "faulthandler.sigsegv()") + + def test_is_enabled(self): + self.assertFalse(faulthandler.is_enabled()) + faulthandler.enable() + self.assertTrue(faulthandler.is_enabled()) + faulthandler.disable() + self.assertFalse(faulthandler.is_enabled()) + + def check_dump_traceback(self, filename): + """ + Call explicitly dump_traceback() function and check its output. + Raise an error if the output doesn't match the expected format. + """ + code = ( + 'from __future__ import with_statement', + 'import faulthandler', + '', + 'def funcB():', + ' if %r:' % (bool(filename),), + ' with open(%r, "wb") as fp:' % (filename,), + ' faulthandler.dump_traceback(fp)', + ' else:', + ' faulthandler.dump_traceback()', + '', + 'def funcA():', + ' funcB()', + '', + 'funcA()', + ) + if filename: + lineno = 7 + else: + lineno = 9 + expected = [ + 'Traceback (most recent call first):', + ' File "", line %s in funcB' % lineno, + ' File "", line 12 in funcA', + ' File "", line 14 in ' + ] + trace = self.get_output(code, filename) + self.assertEqual(trace, expected) + + def test_dump_traceback(self): + self.check_dump_traceback(None) + with temporary_filename() as filename: + self.check_dump_traceback(filename) + + def check_dump_traceback_threads(self, filename): + """ + Call explicitly dump_traceback(all_threads=True) and check the output. + Raise an error if the output doesn't match the expected format. + """ + output = self.get_output(( + 'from __future__ import with_statement', + 'import faulthandler', + 'from threading import Thread, Event', + 'import time', + '', + 'def dump():', + ' if %r:' % (bool(filename),), + ' with open(%r, "wb") as fp:' % (filename,), + ' faulthandler.dump_traceback(fp, all_threads=True)', + ' else:', + ' faulthandler.dump_traceback(all_threads=True)', + '', + 'class Waiter(Thread):', + ' def __init__(self):', + ' Thread.__init__(self)', + ' self.running = Event()', + ' self.stop = Event()', + '', + ' def run(self):', + ' self.running.set()', + ' self.stop.wait()', + '', + 'waiter = Waiter()', + 'waiter.start()', + 'waiter.running.wait()', + 'dump()', + 'waiter.stop.set()', + 'waiter.join()', + ), filename) + output = '\n'.join(output) + if filename: + lineno = 9 + else: + lineno = 11 + regex = ( + 'Thread 0x[0-9a-f]+:\n' + '(?: File ".*threading.py", line [0-9]+ in wait\n)?' + ' File ".*threading.py", line [0-9]+ in wait\n' + ' File "", line 21 in run\n' + ' File ".*threading.py", line [0-9]+ in _bootstrap_inner\n' + ' File ".*threading.py", line [0-9]+ in _bootstrap\n' + '\n' + 'Current thread XXX:\n' + ' File "", line %s in dump\n' + ' File "", line 26 in ' + ) % lineno + self.assertTrue(re.match(regex, output), + "<<<%s>>> doesn't match" % output) + + def test_dump_traceback_threads(self): + self.check_dump_traceback_threads(None) + with temporary_filename() as filename: + self.check_dump_traceback_threads(filename) + + def _check_dump_traceback_later(self, repeat, cancel, + filename, all_threads): + """ + Call dump_traceback_later() two times, or three times if repeat is True. + Check the output: the traceback may be written 1, 2 or 3 times + depending on repeat and cancel options. + + Raise an error if the output doesn't match the expect format. + """ + code = ( + 'import faulthandler', + 'import time', + '', + 'def func(repeat, cancel):', + ' if not repeat:', + ' loops = 2', + ' else:', + ' loops = 3', + ' dump = True', + ' for x in range(loops):', + ' a = time.time()', + ' time.sleep(2)', + ' b = time.time()', + ' diff = (b - a)', + ' if dump:', + ' # sleep() interrupted after 1 second', + ' assert diff < 2.0', + ' else:', + ' assert diff >= 2.0', + ' if repeat and cancel and 1 <= x:', + ' faulthandler.cancel_dump_traceback_later()', + ' dump = False', + ' cancel = False', + ' if not repeat:', + ' dump = False', + ' if repeat:', + ' faulthandler.cancel_dump_traceback_later()', + '', + 'repeat = %s' % repeat, + 'cancel = %s' % cancel, + 'if %s:' % bool(filename), + ' file = open(%r, "wb")' % filename, + 'else:', + ' file = None', + 'faulthandler.dump_traceback_later(1, ', + ' repeat=repeat, all_threads=%s, file=file)' % all_threads, + 'func(repeat, cancel)', + 'if file is not None:', + ' file.close()', + ) + trace = self.get_output(code, filename) + + expected = expected_traceback(12, 37, all_threads) + if repeat: + if cancel: + expected *= 2 + else: + expected *= 3 + self.assertEqual(trace, expected, + "%r != %r: repeat=%s, cancel=%s, use_filename=%s, all_threads=%s" + % (trace, expected, repeat, cancel, bool(filename), all_threads)) + + @skipIf(not hasattr(faulthandler, 'dump_traceback_later'), + 'need faulthandler.dump_traceback_later()') + def check_dump_traceback_later(self, repeat=False, cancel=False, + all_threads=False, file=False): + if file: + with temporary_filename() as filename: + self._check_dump_traceback_later(repeat, cancel, filename, all_threads) + else: + self._check_dump_traceback_later(repeat, cancel, None, all_threads) + + def test_dump_traceback_later(self): + self.check_dump_traceback_later() + + def test_dump_traceback_later_repeat(self): + self.check_dump_traceback_later(repeat=True) + + def test_dump_traceback_later_repeat_cancel(self): + self.check_dump_traceback_later(repeat=True, cancel=True) + + def test_dump_traceback_later_threads(self): + self.check_dump_traceback_later(all_threads=True) + + def test_dump_traceback_later_file(self): + self.check_dump_traceback_later(file=True) + + @skipIf(not hasattr(signal, "SIGUSR1"), + "need signal.SIGUSR1") + def check_register(self, filename=False, all_threads=False): + """ + Register a handler display the traceback on a user signal. Raise the + signal and check the written traceback. + + Raise an error if the output doesn't match the expected format. + """ + code = ( + 'import faulthandler', + 'import os', + 'import signal', + '', + 'def func(signum):', + ' os.kill(os.getpid(), signum)', + '', + 'signum = signal.SIGUSR1', + 'if %s:' % bool(filename), + ' file = open(%r, "wb")' % filename, + 'else:', + ' file = None', + 'faulthandler.register(signum, file=file, all_threads=%s)' % all_threads, + 'func(signum)', + 'if file is not None:', + ' file.close()', + ) + trace = self.get_output(code, filename) + expected = expected_traceback(6, 14, all_threads) + self.assertEqual(trace, expected, + "%r != %r: use_filename=%s, all_threads=%s" + % (trace, expected, bool(filename), all_threads)) + + def test_register(self): + self.check_register() + + def test_register_file(self): + with temporary_filename() as filename: + self.check_register(filename=filename) + + def test_register_threads(self): + self.check_register(all_threads=True) + + def test_refcount(self): + """ + Test written to check for reference leaks. + """ + faulthandler.enable() + faulthandler.enable() + faulthandler.disable() + faulthandler.disable() + faulthandler.enable() + faulthandler.disable() + + if hasattr(signal, "SIGUSR1"): + faulthandler.register(signal.SIGUSR1) + faulthandler.register(signal.SIGUSR1) + faulthandler.register(signal.SIGUSR2) + faulthandler.register(signal.SIGUSR2) + faulthandler.unregister(signal.SIGUSR1) + faulthandler.unregister(signal.SIGUSR2) + + if hasattr(faulthandler, 'dump_traceback_later'): + faulthandler.dump_traceback_later(3600) + faulthandler.dump_traceback_later(3600) + faulthandler.cancel_dump_traceback_later() + + with tempfile.NamedTemporaryFile() as file: + faulthandler.dump_traceback(file=file) + faulthandler.dump_traceback(file=file, all_threads=True) + +def test_main(): + support.run_unittest(FaultHandlerTests) + +if __name__ == "__main__": + test_main() diff -r 05e1ca25f7dc -r ec274420e9e2 Makefile.pre.in --- a/Makefile.pre.in Wed Mar 23 18:12:24 2011 -0700 +++ b/Makefile.pre.in Fri Mar 25 17:05:39 2011 +0100 @@ -491,7 +491,7 @@ -install_name $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK) \ -compatibility_version $(VERSION) \ -current_version $(VERSION) \ - -framework CoreFoundation $(LIBS); + -framework CoreFoundation $(LIBS); $(INSTALL) -d -m $(DIRMODE) \ $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/Resources/English.lproj $(INSTALL_DATA) $(RESSRCDIR)/Info.plist \ diff -r 05e1ca25f7dc -r ec274420e9e2 Modules/Setup.dist --- a/Modules/Setup.dist Wed Mar 23 18:12:24 2011 -0700 +++ b/Modules/Setup.dist Fri Mar 25 17:05:39 2011 +0100 @@ -102,7 +102,7 @@ # various reasons; therefore they are listed here instead of in the # normal order. -# This only contains the minimal set of modules required to run the +# This only contains the minimal set of modules required to run the # setup.py script in the root of the Python source tree. posix posixmodule.c # posix (UNIX) system calls @@ -115,7 +115,7 @@ _functools _functoolsmodule.c # Tools for working with functions and callable objects operator operator.c # operator.add() and similar goodies _collections _collectionsmodule.c # Container types -itertools itertoolsmodule.c # Functions creating iterators for efficient looping +itertools itertoolsmodule.c # Functions creating iterators for efficient looping # access to ISO C locale support _locale _localemodule.c # -lintl @@ -127,12 +127,15 @@ # builtin module avoids some bootstrapping problems and reduces overhead. zipimport zipimport.c +# faulthandler module +faulthandler faulthandler.c + # The rest of the modules listed in this file are all commented out by # default. Usually they can be detected and built as dynamically # loaded modules by the new setup.py script added in Python 2.1. If -# you're on a platform that doesn't support dynamic loading, want to -# compile modules statically into the Python binary, or need to -# specify some odd set of compiler switches, you can uncomment the +# you're on a platform that doesn't support dynamic loading, want to +# compile modules statically into the Python binary, or need to +# specify some odd set of compiler switches, you can uncomment the # appropriate lines below. # ====================================================================== @@ -182,7 +185,7 @@ # supported...) #fcntl fcntlmodule.c # fcntl(2) and ioctl(2) -#spwd spwdmodule.c # spwd(3) +#spwd spwdmodule.c # spwd(3) #grp grpmodule.c # grp(3) #select selectmodule.c # select(2); not on ancient System V @@ -299,7 +302,7 @@ #_curses _cursesmodule.c -lcurses -ltermcap # Wrapper for the panel library that's part of ncurses and SYSV curses. -#_curses_panel _curses_panel.c -lpanel -lncurses +#_curses_panel _curses_panel.c -lpanel -lncurses # Modules that provide persistent dictionary-like semantics. You will diff -r 05e1ca25f7dc -r ec274420e9e2 Modules/faulthandler.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Modules/faulthandler.c Fri Mar 25 17:05:39 2011 +0100 @@ -0,0 +1,882 @@ +/* + * faulthandler module + * + * Written by Victor Stinner. + */ + +#include "Python.h" +#include +#include +#include +#include + +#define VERSION 0x106 + +#ifdef SIGALRM +# define FAULTHANDLER_LATER +#endif + +#define PUTS(fd, str) write(fd, str, strlen(str)) + + +#ifdef HAVE_SIGACTION +typedef struct sigaction _Py_sighandler_t; +#else +typedef PyOS_sighandler_t _Py_sighandler_t; +#endif + +typedef struct { + int signum; + int enabled; + const char* name; + _Py_sighandler_t previous; + int all_threads; +} fault_handler_t; + +static struct { + int enabled; + PyObject *file; + int fd; + int all_threads; +} fatal_error = {0, NULL, -1, 0}; + +#ifdef FAULTHANDLER_LATER +static struct { + PyObject *file; + int fd; + int delay; + int repeat; + int all_threads; +} fault_alarm; +#endif + +typedef struct { + int signum; + PyObject *file; + int fd; + int all_threads; + _Py_sighandler_t previous; +} user_signal_t; + +static struct { + size_t nsignal; + user_signal_t *signals; +} user_signals = {0, NULL}; + + +static fault_handler_t faulthandler_handlers[] = { +#ifdef SIGBUS + {SIGBUS, 0, "Bus error", }, +#endif +#ifdef SIGILL + {SIGILL, 0, "Illegal instruction", }, +#endif + {SIGFPE, 0, "Floating point exception", }, + /* define SIGSEGV at the end to make it the default choice if searching the + handler fails in faulthandler_fatal_error() */ + {SIGSEGV, 0, "Segmentation fault", } +}; +static const unsigned char faulthandler_nsignals = \ + sizeof(faulthandler_handlers) / sizeof(faulthandler_handlers[0]); + +#ifdef HAVE_SIGALTSTACK +static stack_t stack; +#endif + + +static int +faulthandler_get_fileno(PyObject *file) +{ + PyObject *result; + long fd_long; + int fd; + + result = PyObject_CallMethod(file, "fileno", ""); + if (result == NULL) + return -1; + + fd = -1; + if (PyLong_Check(result)) { + fd_long = PyLong_AsLong(result); + if (0 < fd_long && fd_long < INT_MAX) + fd = (int)fd_long; + } + Py_DECREF(result); + + if (fd == -1) { + PyErr_SetString(PyExc_RuntimeError, + "file.fileno() is not a valid file descriptor"); + return -1; + } + + result = PyObject_CallMethod(file, "flush", ""); + if (result != NULL) + Py_DECREF(result); + else { + /* ignore flush() error */ + PyErr_Clear(); + } + return fd; +} + +static PyObject* +faulthandler_dump_traceback_py(PyObject *self, + PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"file", "all_threads", NULL}; + PyObject *file = NULL; + int all_threads = 0; + PyThreadState *tstate; + const char *errmsg; + int fd; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "|Oi:dump_traceback", kwlist, + &file, &all_threads)) + return NULL; + + if (file == NULL) { + file = PySys_GetObject("stderr"); + if (file == NULL) { + PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr"); + return NULL; + } + } + + fd = faulthandler_get_fileno(file); + if (fd == -1) + return NULL; + + /* The caller holds the GIL and so PyThreadState_Get() can be used */ + tstate = PyThreadState_Get(); + if (tstate == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "unable to get the current thread state"); + return NULL; + } + + if (all_threads) { + errmsg = _Py_DumpTracebackThreads(fd, tstate); + if (errmsg != NULL) { + PyErr_SetString(PyExc_RuntimeError, errmsg); + return NULL; + } + } + else { + _Py_DumpTraceback(fd, tstate); + } + Py_RETURN_NONE; +} + + +/* Handler of SIGSEGV, SIGFPE, SIGBUS and SIGILL signals. + + Display the current Python traceback and restore the previous handler. The + previous handler will be called when the fault handler exits, because the + fault will occur again. + + This function is signal safe and should only call signal safe functions. */ + +static void +faulthandler_fatal_error(int signum) +{ + const int fd = fatal_error.fd; + unsigned int i; + fault_handler_t *handler = NULL; + PyThreadState *tstate; + + if (!fatal_error.enabled) + return; + + /* restore the previous handler */ + for (i=0; i < faulthandler_nsignals; i++) { + handler = &faulthandler_handlers[i]; + if (handler->signum == signum) + break; + } + if (handler == NULL) { + /* faulthandler_nsignals == 0 (unlikely) */ + return; + } + +#ifdef HAVE_SIGACTION + (void)sigaction(handler->signum, &handler->previous, NULL); +#else + (void)signal(handler->signum, handler->previous); +#endif + handler->enabled = 0; + + PUTS(fd, "Fatal Python error: "); + PUTS(fd, handler->name); + PUTS(fd, "\n\n"); + + /* 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) + return; + + if (fatal_error.all_threads) + _Py_DumpTracebackThreads(fd, tstate); + else + _Py_DumpTraceback(fd, tstate); +} + +/* Install handler for fatal signals (SIGSEGV, SIGFPE, ...). */ + +static PyObject* +faulthandler_enable(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"file", "all_threads", NULL}; + PyObject *file = NULL; + int all_threads = 0; + unsigned int i; + fault_handler_t *handler; +#ifdef HAVE_SIGACTION + struct sigaction action; + int err; +#endif + int fd; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "|Oi:enable", kwlist, &file, &all_threads)) + return NULL; + + if (file == NULL) { + file = PySys_GetObject("stderr"); + if (file == NULL) { + PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr"); + return NULL; + } + } + + fd = faulthandler_get_fileno(file); + if (fd == -1) + return NULL; + + Py_XDECREF(fatal_error.file); + Py_INCREF(file); + fatal_error.file = file; + fatal_error.fd = fd; + fatal_error.all_threads = all_threads; + + 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); + action.sa_flags = 0; +#ifdef HAVE_SIGALTSTACK + if (stack.ss_sp != NULL) + action.sa_flags |= SA_ONSTACK; +#endif + err = sigaction(handler->signum, &action, &handler->previous); + if (!err) + handler->enabled = 1; +#else + handler->previous = signal(handler->signum, + faulthandler_fatal_error); + if (handler->previous != SIG_ERR) + handler->enabled = 1; +#endif + } + } + Py_RETURN_NONE; +} + +static void +faulthandler_disable(void) +{ + unsigned int i; + fault_handler_t *handler; + + if (fatal_error.enabled) { + 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; + } + } + + Py_CLEAR(fatal_error.file); +} + +static PyObject* +faulthandler_disable_py(PyObject *self) +{ + if (!fatal_error.enabled) { + Py_INCREF(Py_False); + return Py_False; + } + faulthandler_disable(); + Py_INCREF(Py_True); + return Py_True; +} + +static PyObject* +faulthandler_is_enabled(PyObject *self) +{ + return PyBool_FromLong(fatal_error.enabled); +} + +#ifdef FAULTHANDLER_LATER +/* Handler of the SIGALRM signal. + + Dump the traceback of the current thread, or of all threads if + fault_alarm.all_threads is true. On success, register itself again if + fault_alarm.repeat is true. + + This function is signal safe and should only call signal safe functions. */ + +static void +faulthandler_alarm(int signum) +{ + int ok; + PyThreadState *tstate; + + /* PyThreadState_Get() doesn't give the state of the current thread if + the thread doesn't hold the GIL. Read the thread local storage (TLS) + instead: call PyGILState_GetThisThreadState(). */ + tstate = PyGILState_GetThisThreadState(); + if (tstate == NULL) { + /* unable to get the current thread, do nothing */ + return; + } + + if (fault_alarm.all_threads) { + const char* errmsg; + + errmsg = _Py_DumpTracebackThreads(fault_alarm.fd, tstate); + ok = (errmsg == NULL); + } + else { + _Py_DumpTraceback(fault_alarm.fd, tstate); + ok = 1; + } + + if (ok && fault_alarm.repeat) + alarm(fault_alarm.delay); + else + /* don't call Py_CLEAR() here because it may call _Py_Dealloc() which + is not signal safe */ + alarm(0); +} + +static PyObject* +faulthandler_dump_traceback_later(PyObject *self, + PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"delay", "repeat", "file", "all_threads", NULL}; + int delay; + PyOS_sighandler_t previous; + int repeat = 0; + PyObject *file = NULL; + int all_threads = 0; + int fd; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "i|iOi:dump_traceback_later", kwlist, + &delay, &repeat, &file, &all_threads)) + return NULL; + if (delay <= 0) { + PyErr_SetString(PyExc_ValueError, "delay must be greater than 0"); + return NULL; + } + + if (file == NULL || file == Py_None) { + file = PySys_GetObject("stderr"); + if (file == NULL) { + PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr"); + return NULL; + } + } + + fd = faulthandler_get_fileno(file); + if (fd == -1) + return NULL; + + previous = signal(SIGALRM, faulthandler_alarm); + if (previous == SIG_ERR) { + PyErr_SetString(PyExc_RuntimeError, "unable to set SIGALRM handler"); + return NULL; + } + + Py_XDECREF(fault_alarm.file); + Py_INCREF(file); + fault_alarm.file = file; + fault_alarm.fd = fd; + fault_alarm.delay = delay; + fault_alarm.repeat = repeat; + fault_alarm.all_threads = all_threads; + + alarm(delay); + + Py_RETURN_NONE; +} + +static void +faulthandler_cancel_dump_traceback_later(void) +{ + alarm(0); + Py_CLEAR(fault_alarm.file); +} + +static PyObject* +faulthandler_cancel_dump_traceback_later_py(PyObject *self) +{ + faulthandler_cancel_dump_traceback_later(); + Py_RETURN_NONE; +} +#endif + +static user_signal_t * +faulthandler_user_find(int signum, unsigned int *p_index) +{ + unsigned int i; + + for (i=0; i < user_signals.nsignal; i++) { + if (user_signals.signals[i].signum == signum) { + if (p_index != NULL) + *p_index = i; + return &user_signals.signals[i]; + } + } + return NULL; +} + +/* Handler of user signals (e.g. SIGUSR1). + + Dump the traceback of the current thread, or of all threads if + fault_alarm.all_threads is true. + + This function is signal safe and should only call signal safe functions. */ + +static void +faulthandler_user(int signum) +{ + user_signal_t *user; + PyThreadState *tstate; + + user = faulthandler_user_find(signum, NULL); + if (user == NULL) + return; + + /* PyThreadState_Get() doesn't give the state of the current thread if + the thread doesn't hold the GIL. Read the thread local storage (TLS) + instead: call PyGILState_GetThisThreadState(). */ + tstate = PyGILState_GetThisThreadState(); + if (tstate == NULL) { + /* unable to get the current thread, do nothing */ + return; + } + + if (user->all_threads) + _Py_DumpTracebackThreads(user->fd, tstate); + else + _Py_DumpTraceback(user->fd, tstate); +} + +static PyObject* +faulthandler_register(PyObject *self, + PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"signum", "file", "all_threads", NULL}; + int signum; + PyObject *file = NULL; + int all_threads = 0; + int fd; + unsigned int i; + user_signal_t *user, *signals; + size_t size; + _Py_sighandler_t previous; +#ifdef HAVE_SIGACTION + struct sigaction action; +#endif + int is_new, err; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "i|Oi:register", kwlist, + &signum, &file, &all_threads)) + return NULL; + + for (i=0; i < faulthandler_nsignals; i++) { + if (faulthandler_handlers[i].signum != signum) + continue; + PyErr_Format(PyExc_RuntimeError, + "signal %i cannot be registered by register(): " + "use enable() instead", + signum); + return NULL; + } + + /* the following test comes from Python: Modules/signal.c */ +#ifdef MS_WINDOWS + /* Validate that sig_num is one of the allowable signals */ + switch (signum) { + case SIGABRT: break; +#ifdef SIGBREAK + /* Issue #10003: SIGBREAK is not documented as permitted, but works + and corresponds to CTRL_BREAK_EVENT. */ + case SIGBREAK: break; +#endif + case SIGFPE: break; + case SIGILL: break; + case SIGINT: break; + case SIGSEGV: break; + case SIGTERM: break; + default: + PyErr_SetString(PyExc_ValueError, "invalid signal value"); + return NULL; + } +#endif + + if (file == NULL || file == Py_None) { + file = PySys_GetObject("stderr"); + if (file == NULL) { + PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr"); + return NULL; + } + } + + fd = faulthandler_get_fileno(file); + if (fd == -1) + return NULL; + + user = faulthandler_user_find(signum, NULL); + is_new = (user == NULL); + if (is_new) { + user_signals.nsignal++; + size = user_signals.nsignal * sizeof(user_signal_t); + if (size / user_signals.nsignal != sizeof(user_signal_t)) { + /* integer overflow */ + return PyErr_NoMemory(); + } + signals = realloc(user_signals.signals, size); + if (signals == NULL) + return PyErr_NoMemory(); + user_signals.signals = signals; + user = &signals[user_signals.nsignal - 1]; + } + +#ifdef HAVE_SIGACTION + action.sa_handler = faulthandler_user; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_ONSTACK; + err = sigaction(signum, &action, &previous); +#else + previous = signal(signum, faulthandler_user); + err = (previous == SIG_ERR); +#endif + if (err) { + if (is_new) + user_signals.nsignal--; + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + user->signum = signum; + if (!is_new) + Py_DECREF(user->file); + Py_INCREF(file); + user->file = file; + user->fd = fd; + user->all_threads = all_threads; + user->previous = previous; + + Py_RETURN_NONE; +} + +static void +faulthandler_unregister(user_signal_t *user) +{ +#ifdef HAVE_SIGACTION + (void)sigaction(user->signum, &user->previous, NULL); +#else + (void)signal(user->signum, user->previous); +#endif + Py_DECREF(user->file); +} + +static PyObject* +faulthandler_unregister_py(PyObject *self, PyObject *args) +{ + int signum; + unsigned int index; + user_signal_t *user; + size_t size; + + if (!PyArg_ParseTuple(args, "i:unregister", &signum)) + return NULL; + + user = faulthandler_user_find(signum, &index); + if (user == NULL) { + Py_INCREF(Py_False); + return Py_False; + } + + faulthandler_unregister(user); + if (index != user_signals.nsignal - 1) { + size = user_signals.nsignal - index - 1; + size *= sizeof(user_signals.signals[0]); + memmove(&user_signals.signals[index], + &user_signals.signals[index+1], + size); + } + user_signals.nsignal--; + + Py_INCREF(Py_True); + return Py_True; +} + + +static PyObject * +faulthandler_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 * +faulthandler_sigfpe(PyObject *self, PyObject *args) +{ + int x = 1, y = 0, z; + z = x / y; + return PyLong_FromLong(z); +} + +#ifdef SIGBUS +static PyObject * +faulthandler_sigbus(PyObject *self, PyObject *args) +{ + while(1) + raise(SIGBUS); + Py_RETURN_NONE; +} +#endif + +#ifdef SIGILL +static PyObject * +faulthandler_sigill(PyObject *self, PyObject *args) +{ + while(1) + raise(SIGILL); + Py_RETURN_NONE; +} +#endif + +static PyObject * +faulthandler_fatal_error_py(PyObject *self, PyObject *args) +{ + char *message; + if (!PyArg_ParseTuple(args, "y:fatal_error", &message)) + return NULL; + Py_FatalError(message); + Py_RETURN_NONE; +} + + +static int +faulthandler_traverse(PyObject *module, visitproc visit, void *arg) +{ + unsigned int index; +#ifdef FAULTHANDLER_LATER + Py_VISIT(fault_alarm.file); +#endif + for (index=0; index < user_signals.nsignal; index++) + Py_VISIT(user_signals.signals[index].file); + Py_VISIT(fatal_error.file); + return 0; +} + +PyDoc_STRVAR(module_doc, +"faulthandler module."); + +static PyMethodDef module_methods[] = { + {"enable", + (PyCFunction)faulthandler_enable, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("enable(file=sys.stderr, all_threads=False): " + "enable the fault handler")}, + {"disable", (PyCFunction)faulthandler_disable_py, METH_NOARGS, + PyDoc_STR("disable(): disable the fault handler")}, + {"is_enabled", (PyCFunction)faulthandler_is_enabled, METH_NOARGS, + PyDoc_STR("is_enabled()->bool: check if the handler is enabled")}, + {"dump_traceback", + (PyCFunction)faulthandler_dump_traceback_py, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("dump_traceback(file=sys.stderr, all_threads=False): " + "dump the traceback of the current thread, or of all threads " + "if all_threads is True, into file")}, +#ifdef FAULTHANDLER_LATER + {"dump_traceback_later", + (PyCFunction)faulthandler_dump_traceback_later, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("dump_traceback_later(delay, repeat=False, file=sys.stderr, all_threads=False): " + "dump the traceback of the current thread, or of all threads " + "if all_threads is True, in delay seconds, or each delay " + "seconds if repeat is True.")}, + {"cancel_dump_traceback_later", + (PyCFunction)faulthandler_cancel_dump_traceback_later_py, METH_NOARGS, + PyDoc_STR("cancel_dump_traceback_later(): cancel the previous call " + "to dump_traceback_later().")}, +#endif + + {"register", + (PyCFunction)faulthandler_register, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("register(signum, file=sys.stderr, all_threads=False): " + "register an handler for the signal 'signum': dump the " + "traceback of the current thread, or of all threads if " + "all_threads is True, into file")}, + {"unregister", + faulthandler_unregister_py, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("unregister(signum): unregister the handler of the signal " + "'signum' registered by register()")}, + + {"sigsegv", faulthandler_sigsegv, METH_VARARGS, + PyDoc_STR("sigsegv(release_gil=False): raise a SIGSEGV signal")}, + {"sigfpe", (PyCFunction)faulthandler_sigfpe, METH_NOARGS, + PyDoc_STR("sigfpe(): raise a SIGFPE signal")}, +#ifdef SIGBUS + {"sigbus", (PyCFunction)faulthandler_sigbus, METH_NOARGS, + PyDoc_STR("sigbus(): raise a SIGBUS signal")}, +#endif +#ifdef SIGILL + {"sigill", (PyCFunction)faulthandler_sigill, METH_NOARGS, + PyDoc_STR("sigill(): raise a SIGILL signal")}, +#endif + {"fatal_error", faulthandler_fatal_error_py, METH_VARARGS, + PyDoc_STR("fatal_error(message): call Py_FatalError(message)")}, + {NULL, NULL} /* terminator */ +}; + +static struct PyModuleDef module_def = { + PyModuleDef_HEAD_INIT, + "faulthandler", + module_doc, + 0, /* non negative size to be able to unload the module */ + module_methods, + NULL, + faulthandler_traverse, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit_faulthandler(void) +{ + PyObject *m, *version; + + m = PyModule_Create(&module_def); + if (m == NULL) { + return NULL; + } + + version = PyLong_FromLong(VERSION); + PyModule_AddObject(m, "version", version); + + return m; +} + +/* Call faulthandler.enable() if PYTHONFAULTHANDLER environment variable is + defined, or if sys._xoptions has a 'faulthandler' key. */ + +static int +faulthandler_env_options(void) +{ + PyObject *xoptions, *key, *module, *res; + int enable; + + if (!Py_GETENV("PYTHONFAULTHANDLER")) { + xoptions = PySys_GetXOptions(); + if (xoptions == NULL) + return -1; + + key = PyUnicode_FromString("faulthandler"); + if (key == NULL) + return -1; + + enable = PyDict_Contains(xoptions, key); + Py_DECREF(key); + if (!enable) + return 0; + } + else + enable = 1; + + module = PyImport_ImportModule("faulthandler"); + if (module == NULL) { + return -1; + } + res = PyObject_CallMethod(module, "enable", ""); + Py_DECREF(module); + if (res == NULL) + return -1; + Py_DECREF(res); + return 0; +} + +int _PyFaulthandler_Init(void) +{ +#ifdef HAVE_SIGALTSTACK + /* Try to allocate an alternate stack for faulthandler() 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 + + return faulthandler_env_options(); +} + +void _PyFaulthandler_Fini(void) +{ + unsigned int i; + +#ifdef FAULTHANDLER_LATER + /* later */ + faulthandler_cancel_dump_traceback_later(); +#endif + + /* user */ + for (i=0; i < user_signals.nsignal; i++) + faulthandler_unregister(&user_signals.signals[i]); + user_signals.nsignal = 0; + free(user_signals.signals); + user_signals.signals = NULL; + + /* fatal */ + faulthandler_disable(); +#ifdef HAVE_SIGALTSTACK + if (stack.ss_sp != NULL) { + PyMem_Free(stack.ss_sp); + stack.ss_sp = NULL; + } +#endif +} diff -r 05e1ca25f7dc -r ec274420e9e2 Modules/main.c --- a/Modules/main.c Wed Mar 23 18:12:24 2011 -0700 +++ b/Modules/main.c Fri Mar 25 17:05:39 2011 +0100 @@ -100,6 +100,7 @@ " 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: dump the Python traceback on fatal errors.\n" ; static int diff -r 05e1ca25f7dc -r ec274420e9e2 PC/config.c --- a/PC/config.c Wed Mar 23 18:12:24 2011 -0700 +++ b/PC/config.c Fri Mar 25 17:05:39 2011 +0100 @@ -12,6 +12,7 @@ extern PyObject* PyInit_binascii(void); extern PyObject* PyInit_cmath(void); extern PyObject* PyInit_errno(void); +extern PyObject* PyInit_faulthandler(void); extern PyObject* PyInit_gc(void); extern PyObject* PyInit_math(void); extern PyObject* PyInit__md5(void); @@ -82,6 +83,7 @@ {"binascii", PyInit_binascii}, {"cmath", PyInit_cmath}, {"errno", PyInit_errno}, + {"faulthandler", PyInit_faulthandler}, {"gc", PyInit_gc}, {"math", PyInit_math}, {"nt", PyInit_nt}, /* Use the NT os functions, not posix */ diff -r 05e1ca25f7dc -r ec274420e9e2 PCbuild/pythoncore.vcproj --- a/PCbuild/pythoncore.vcproj Wed Mar 23 18:12:24 2011 -0700 +++ b/PCbuild/pythoncore.vcproj Fri Mar 25 17:05:39 2011 +0100 @@ -1087,6 +1087,10 @@ > + + diff -r 05e1ca25f7dc -r ec274420e9e2 Python/pythonrun.c --- a/Python/pythonrun.c Wed Mar 23 18:12:24 2011 -0700 +++ b/Python/pythonrun.c Fri Mar 25 17:05:39 2011 +0100 @@ -70,6 +70,8 @@ extern void _PyUnicode_Fini(void); extern int _PyLong_Init(void); extern void PyLong_Fini(void); +extern int _PyFaulthandler_Init(void); +extern void _PyFaulthandler_Fini(void); #ifdef WITH_THREAD extern void _PyGILState_Init(PyInterpreterState *, PyThreadState *); @@ -286,6 +288,10 @@ _PyImportHooks_Init(); + /* initialize the faulthandler module */ + if (_PyFaulthandler_Init()) + Py_FatalError("Py_Initialize: can't initialize faulthandler"); + /* Initialize _warnings. */ _PyWarnings_Init(); @@ -454,6 +460,9 @@ /* Destroy the database used by _PyImport_{Fixup,Find}Extension */ _PyImport_Fini(); + /* unload faulthandler module */ + _PyFaulthandler_Fini(); + /* Debugging stuff */ #ifdef COUNT_ALLOCS dump_counts(stdout); @@ -2100,11 +2109,23 @@ void Py_FatalError(const char *msg) { + const int fd = fileno(stderr); + fprintf(stderr, "Fatal Python error: %s\n", msg); fflush(stderr); /* it helps in Windows debug build */ if (PyErr_Occurred()) { PyErr_PrintEx(0); } + else { + /* The caller holds the GIL and so PyThreadState_Get() can be used */ + PyThreadState *tstate = PyThreadState_Get(); + if (tstate != NULL) { + fputc('\n', stderr); + fflush(stderr); + _Py_DumpTraceback(fd, tstate); + } + } + #ifdef MS_WINDOWS { size_t len = strlen(msg); diff -r 05e1ca25f7dc -r ec274420e9e2 Python/traceback.c --- a/Python/traceback.c Wed Mar 23 18:12:24 2011 -0700 +++ b/Python/traceback.c Fri Mar 25 17:05:39 2011 +0100 @@ -13,6 +13,11 @@ #define OFF(x) offsetof(PyTracebackObject, x) +#define PUTS(fd, str) write(fd, str, strlen(str)) +#define MAX_STRING_LENGTH 100 +#define MAX_FRAME_DEPTH 100 +#define MAX_NTHREADS 100 + /* Method from Parser/tokenizer.c */ extern char * PyTokenizer_FindEncoding(int); @@ -402,3 +407,297 @@ err = tb_printinternal((PyTracebackObject *)v, f, limit); return err; } + +/* Reverse a string. For example, "abcd" becomes "dcba". + + This function is signal safe. */ + +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. + + This function is signal safe. */ + +static void +dump_decimal(int fd, int value) +{ + 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. + + This function is signal safe. */ + +static void +dump_hexadecimal(int width, unsigned long value, int fd) +{ + const char *hexdigits = "0123456789abcdef"; + int len; + char buffer[sizeof(unsigned long) * 2 + 1]; + 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. + + This function is signal safe. */ + +static void +dump_ascii(int fd, PyObject *text) +{ + Py_ssize_t i, size; + int truncated; +#if PY_MAJOR_VERSION >= 3 + Py_UNICODE *u; + char c; + + size = PyUnicode_GET_SIZE(text); + u = PyUnicode_AS_UNICODE(text); +#else + char *s; + unsigned char c; + + size = PyString_GET_SIZE(text); + s = PyString_AS_STRING(text); +#endif + + if (MAX_STRING_LENGTH < size) { + size = MAX_STRING_LENGTH; + truncated = 1; + } + else + truncated = 0; + +#if PY_MAJOR_VERSION >= 3 + for (i=0; i < size; i++, u++) { + if (*u < 128) { + c = (char)*u; + write(fd, &c, 1); + } + else if (*u < 256) { + PUTS(fd, "\\x"); + dump_hexadecimal(2, *u, fd); + } + else +#ifdef Py_UNICODE_WIDE + if (*u < 65536) +#endif + { + PUTS(fd, "\\u"); + dump_hexadecimal(4, *u, fd); +#ifdef Py_UNICODE_WIDE + } + else { + PUTS(fd, "\\U"); + dump_hexadecimal(8, *u, fd); +#endif + } + } +#else + for (i=0; i < size; i++, s++) { + c = *s; + if (c < 128) { + write(fd, s, 1); + } + else { + PUTS(fd, "\\x"); + dump_hexadecimal(2, c, fd); + } + } +#endif + if (truncated) + PUTS(fd, "..."); +} + +/* Write a frame into the file fd: "File "xxx", line xxx in xxx". + + This function is signal safe. */ + +static void +dump_frame(int fd, PyFrameObject *frame) +{ + PyCodeObject *code; + int lineno; + + code = frame->f_code; + PUTS(fd, " File "); + if (code != NULL && code->co_filename != NULL + && PyUnicode_Check(code->co_filename)) + { + write(fd, "\"", 1); + dump_ascii(fd, code->co_filename); + write(fd, "\"", 1); + } else { + PUTS(fd, "???"); + } + +#if (PY_MAJOR_VERSION <= 2 && PY_MINOR_VERSION < 7) \ +|| (PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION < 2) + /* PyFrame_GetLineNumber() was introduced in Python 2.7.0 and 3.2.0 */ + lineno = PyCode_Addr2Line(frame->f_code, frame->f_lasti); +#else + lineno = PyFrame_GetLineNumber(frame); +#endif + PUTS(fd, ", line "); + dump_decimal(fd, lineno); + PUTS(fd, " in "); + + if (code != NULL && code->co_name != NULL + && PyUnicode_Check(code->co_name)) + dump_ascii(fd, code->co_name); + else + PUTS(fd, "???"); + + write(fd, "\n", 1); +} + +static int +dump_traceback(int fd, PyThreadState *tstate, int write_header) +{ + PyFrameObject *frame; + unsigned int depth; + + frame = _PyThreadState_GetFrame(tstate); + if (frame == NULL) + return -1; + + if (write_header) + PUTS(fd, "Traceback (most recent call first):\n"); + depth = 0; + while (frame != NULL) { + if (MAX_FRAME_DEPTH <= depth) { + PUTS(fd, " ...\n"); + break; + } + if (!PyFrame_Check(frame)) + break; + dump_frame(fd, frame); + frame = frame->f_back; + depth++; + } + return 0; +} + +/* Write the current Python traceback into the file 'fd'. For example: + + Traceback (most recent call first): + File "xxx", line xxx in + File "xxx", line xxx in + ... + File "xxx", line xxx in + + Return 0 on success, -1 on error. + + -- + + This function is written for debug purpose only, to dump the traceback in + the worst case: after a segmentation fault, at fatal error, etc. That's why, + it is very limited. Strings are truncated to MAX_STRING_LENGTH characters + and encoded to ASCII with backslashreplace. It doesn't write the source + code, only the function name, filename and line number of each frame. Write + only the first MAX_FRAME_DEPTH frames: if the traceback is truncated, write + the line " ...". + + This function is signal safe. */ + +int +_Py_DumpTraceback(int fd, PyThreadState *tstate) +{ + return dump_traceback(fd, tstate, 1); +} + +/* Write the thread identifier into the file 'fd': "Current thread 0xHHHH:\" if + is_current is true, "Thread 0xHHHH:\n" otherwise. + + This function is signal safe. */ + +static void +write_thread_id(int fd, PyThreadState *tstate, int is_current) +{ + if (is_current) + PUTS(fd, "Current thread 0x"); + else + PUTS(fd, "Thread 0x"); + dump_hexadecimal(sizeof(long)*2, (unsigned long)tstate->thread_id, fd); + PUTS(fd, ":\n"); +} + +/* Write the traceback of all threads into the file 'fd'. + Return NULL on success, or an error message on error. + + -- + + This function is written for debug purpose only. It calls + _Py_DumpTraceback() for each thread, and so has the same limitations. It + only write the traceback of the first MAX_NTHREADS threads: write "..." if + there are more threads. + + This function is signal safe. */ + +const char* +_Py_DumpTracebackThreads(int fd, PyThreadState *current_thread) +{ + PyInterpreterState *interp; + PyThreadState *tstate; + unsigned int nthreads; + + /* Get the current interpreter from the current thread */ + interp = current_thread->interp; + if (interp == NULL) + return "unable to get the interpreter"; + + tstate = PyInterpreterState_ThreadHead(interp); + if (tstate == NULL) + return "unable to get the thread head state"; + + /* Dump the traceback of each thread */ + tstate = PyInterpreterState_ThreadHead(interp); + nthreads = 0; + do + { + if (nthreads != 0) + write(fd, "\n", 1); + if (nthreads >= MAX_NTHREADS) { + PUTS(fd, "...\n"); + break; + } + write_thread_id(fd, tstate, tstate == current_thread); + dump_traceback(fd, tstate, 0); + tstate = PyThreadState_Next(tstate); + nthreads++; + } while (tstate != NULL); + + return NULL; +} + diff -r 05e1ca25f7dc -r ec274420e9e2 configure --- a/configure Wed Mar 23 18:12:24 2011 -0700 +++ b/configure Fri Mar 25 17:05:39 2011 +0100 @@ -9261,7 +9261,7 @@ select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \ setgid sethostname \ setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \ - sigaction siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync \ + sigaction sigaltstack siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \ wcscoll wcsftime wcsxfrm writev _getpty diff -r 05e1ca25f7dc -r ec274420e9e2 configure.in --- a/configure.in Wed Mar 23 18:12:24 2011 -0700 +++ b/configure.in Fri Mar 25 17:05:39 2011 +0100 @@ -2507,7 +2507,7 @@ select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \ setgid sethostname \ setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \ - sigaction siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync \ + sigaction sigaltstack siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \ wcscoll wcsftime wcsxfrm writev _getpty) diff -r 05e1ca25f7dc -r ec274420e9e2 pyconfig.h.in --- a/pyconfig.h.in Wed Mar 23 18:12:24 2011 -0700 +++ b/pyconfig.h.in Fri Mar 25 17:05:39 2011 +0100 @@ -710,6 +710,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