[Python-checkins] cpython: Issue #11393: Add the new faulthandler module

victor.stinner python-checkins at python.org
Thu Mar 31 01:32:00 CEST 2011


http://hg.python.org/cpython/rev/b0680b5a5215
changeset:   69070:b0680b5a5215
user:        Victor Stinner <victor.stinner at haypocalc.com>
date:        Thu Mar 31 01:31:06 2011 +0200
summary:
  Issue #11393: Add the new faulthandler module

files:
  Doc/library/debug.rst         |    3 +-
  Doc/library/faulthandler.rst  |  129 ++
  Doc/using/cmdline.rst         |    7 +
  Doc/whatsnew/3.3.rst          |    8 +
  Include/traceback.h           |   40 +
  Lib/test/regrtest.py          |    5 +
  Lib/test/script_helper.py     |    5 +-
  Lib/test/test_faulthandler.py |  469 ++++++++++
  Misc/NEWS                     |    2 +
  Modules/Setup.dist            |    3 +
  Modules/faulthandler.c        |  971 ++++++++++++++++++++++
  Modules/main.c                |    1 +
  PC/config.c                   |    2 +
  PCbuild/pythoncore.vcproj     |    4 +
  Python/pythonrun.c            |   21 +
  Python/traceback.c            |  235 +++++
  configure                     |    2 +-
  configure.in                  |    2 +-
  pyconfig.h.in                 |    3 +
  19 files changed, 1907 insertions(+), 5 deletions(-)


diff --git a/Doc/library/debug.rst b/Doc/library/debug.rst
--- a/Doc/library/debug.rst
+++ b/Doc/library/debug.rst
@@ -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 --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst
new file mode 100644
--- /dev/null
+++ b/Doc/library/faulthandler.rst
@@ -0,0 +1,129 @@
+: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 by setting the
+:envvar:`PYTHONFAULTHANDLER` environment variable or by using :option:`-X`
+``faulthandler`` 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
+handlers, 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).
+
+.. versionadded:: 3.3
+
+
+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. It dumps the traceback of the current thread, or all
+   threads if *all_threads* is ``True``, into *file*.
+
+.. function:: disable()
+
+   Disable the fault handler: uninstall the signal handlers installed by
+   :func:`enable`.
+
+.. function:: is_enabled()
+
+   Check if the fault handler is enabled.
+
+
+Dump the tracebacks after a timeout
+-----------------------------------
+
+.. function:: dump_tracebacks_later(timeout, repeat=False, file=sys.stderr, exit=False)
+
+   Dump the tracebacks of all threads, after a timeout of *timeout* seconds, or
+   each *timeout* seconds if *repeat* is ``True``.  If *exit* is True, call
+   :cfunc:`_exit` with status=1 after dumping the tracebacks to terminate
+   immediatly the process, which is not safe.  For example, :cfunc:`_exit`
+   doesn't flush file buffers.  If the function is called twice, the new call
+   replaces previous parameters (resets the timeout). The timer has a
+   sub-second resolution.
+
+   This function is implemented using a watchdog thread, and therefore is
+   not available if Python is compiled with threads disabled.
+
+.. function:: cancel_dump_traceback_later()
+
+   Cancel the last call to :func:`dump_traceback_later`.
+
+
+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*.
+
+   Not available on Windows.
+
+.. function:: unregister(signum)
+
+   Unregister a user signal: uninstall the handler of the *signum* signal
+   installed by :func:`register`.
+
+   Not available on Windows.
+
+
+File descriptor issue
+---------------------
+
+:func:`enable`, :func:`dump_traceback_later` and :func:`register` keep 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.
+
+
+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 "<stdin>", line 1 in <module>
+    Segmentation fault
+
diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst
--- a/Doc/using/cmdline.rst
+++ b/Doc/using/cmdline.rst
@@ -498,6 +498,13 @@
    separated string, it is equivalent to specifying :option:`-W` multiple
    times.
 
+.. envvar:: PYTHONFAULTHANDLER
+
+   If this environment variable is set, :func:`faulthandler.enable` is called
+   at startup: install a handler for :const:`SIGSEGV`, :const:`SIGFPE`,
+   :const:`SIGBUS` and :const:`SIGILL` signals to dump the Python traceback.
+   This is equivalent to :option:`-X` ``faulthandler`` option.
+
 
 Debug-mode variables
 ~~~~~~~~~~~~~~~~~~~~
diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst
--- a/Doc/whatsnew/3.3.rst
+++ b/Doc/whatsnew/3.3.rst
@@ -68,6 +68,14 @@
 
 * Stub
 
+faulthandler
+------------
+
+New module: :mod:`faulthandler`.
+
+ * :envvar:`PYTHONFAULTHANDLER`
+ * :option:`-X` ``faulthandler``
+
 os
 --
 
diff --git a/Include/traceback.h b/Include/traceback.h
--- a/Include/traceback.h
+++ b/Include/traceback.h
@@ -5,6 +5,8 @@
 extern "C" {
 #endif
 
+#include "pystate.h"
+
 struct _frame;
 
 /* Traceback interface */
@@ -28,6 +30,44 @@
 PyAPI_DATA(PyTypeObject) PyTraceBack_Type;
 #define PyTraceBack_Check(v) (Py_TYPE(v) == &PyTraceBack_Type)
 
+/* Write the Python traceback into the file 'fd'. For example:
+
+       Traceback (most recent call first):
+         File "xxx", line xxx in <xxx>
+         File "xxx", line xxx in <xxx>
+         ...
+         File "xxx", line xxx in <xxx>
+
+   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 100 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
+   100 frames: if the traceback is truncated, write the line " ...".
+
+   This function is signal safe. */
+
+PyAPI_DATA(int) _Py_DumpTraceback(
+    int fd,
+    PyThreadState *tstate);
+
+/* Write the traceback of all threads into the file 'fd'. current_thread can be
+   NULL. 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 100 threads: write "..." if there are
+   more threads.
+
+   This function is signal safe. */
+
+PyAPI_DATA(const char*) _Py_DumpTracebackThreads(
+    int fd, PyInterpreterState *interp,
+    PyThreadState *current_thread);
+
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py
--- a/Lib/test/regrtest.py
+++ b/Lib/test/regrtest.py
@@ -157,6 +157,7 @@
 """
 
 import builtins
+import faulthandler
 import getopt
 import json
 import os
@@ -490,6 +491,7 @@
             next_single_test = alltests[alltests.index(selected[0])+1]
         except IndexError:
             next_single_test = None
+    selected = ['test_faulthandler']
     # Remove all the tests that precede start if it's set.
     if start:
         try:
@@ -1551,6 +1553,9 @@
     return TEMPDIR, TESTCWD
 
 if __name__ == '__main__':
+    # Display the Python traceback on segfault and division by zero
+    faulthandler.enable()
+
     # Remove regrtest.py's own directory from the module search path. Despite
     # the elimination of implicit relative imports, this is still needed to
     # ensure that submodules of the test package do not inappropriately appear
diff --git a/Lib/test/script_helper.py b/Lib/test/script_helper.py
--- a/Lib/test/script_helper.py
+++ b/Lib/test/script_helper.py
@@ -56,11 +56,12 @@
     """
     return _assert_python(False, *args, **env_vars)
 
-def spawn_python(*args):
+def spawn_python(*args, **kw):
     cmd_line = [sys.executable, '-E']
     cmd_line.extend(args)
     return subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
-                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                            **kw)
 
 def kill_python(p):
     p.stdin.close()
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_faulthandler.py
@@ -0,0 +1,469 @@
+from contextlib import contextmanager
+import faulthandler
+import re
+import signal
+import subprocess
+import sys
+from test import support, script_helper
+import tempfile
+import unittest
+
+try:
+    from resource import setrlimit, RLIMIT_CORE, error as resource_error
+except ImportError:
+    prepare_subprocess = None
+else:
+    def prepare_subprocess():
+        # don't create core file
+        try:
+            setrlimit(RLIMIT_CORE, (0, 0))
+        except (ValueError, resource_error):
+            pass
+
+def expected_traceback(lineno1, lineno2, header, count=1):
+    regex = header
+    regex += r'  File "\<string\>", line %s in func\n' % lineno1
+    regex += r'  File "\<string\>", line %s in \<module\>' % lineno2
+    if count != 1:
+        regex = (regex + '\n') * (count - 1) + regex
+    return '^' + regex + '$'
+
+ at contextmanager
+def temporary_filename():
+    filename = tempfile.mktemp()
+    try:
+        yield filename
+    finally:
+        support.unlink(filename)
+
+class FaultHandlerTests(unittest.TestCase):
+    def get_output(self, code, expect_success, filename=None):
+        """
+        Run the specified code in Python (in a new child process) and read the
+        output from the standard error or from a file (if filename is set).
+        Return the output lines as a list.
+
+        Strip the reference count from the standard error for Python debug
+        build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
+        thread XXX".
+        """
+        options = {}
+        if prepare_subprocess:
+            options['preexec_fn'] = prepare_subprocess
+        process = script_helper.spawn_python('-c', code, **options)
+        stdout, stderr = process.communicate()
+        exitcode = process.wait()
+        if expect_success:
+            self.assertEqual(exitcode, 0)
+        else:
+            self.assertNotEqual(exitcode, 0)
+        if filename:
+            with open(filename, "rb") as fp:
+                output = fp.read()
+        else:
+            output = support.strip_python_stderr(stdout)
+        output = output.decode('ascii', 'backslashreplace')
+        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_regex,
+                               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.
+        """
+        if all_threads:
+            header = 'Current thread XXX'
+        else:
+            header = 'Traceback (most recent call first)'
+        regex = """
+^Fatal Python error: {name}
+
+{header}:
+  File "<string>", line {lineno} in <module>$
+""".strip()
+        regex = regex.format(
+            lineno=line_number,
+            name=name_regex,
+            header=re.escape(header))
+        output = self.get_output(code, False, filename)
+        output = '\n'.join(output)
+        self.assertRegex(output, regex)
+
+    def test_read_null(self):
+        self.check_fatal_error("""
+import faulthandler
+faulthandler.enable()
+faulthandler._read_null()
+""".strip(),
+            3,
+            '(?:Segmentation fault|Bus error)')
+
+    def test_sigsegv(self):
+        self.check_fatal_error("""
+import faulthandler
+faulthandler.enable()
+faulthandler._sigsegv()
+""".strip(),
+            3,
+            'Segmentation fault')
+
+    @unittest.skipIf(sys.platform == 'win32',
+                     "SIGFPE cannot be caught on Windows")
+    def test_sigfpe(self):
+        self.check_fatal_error("""
+import faulthandler
+faulthandler.enable()
+faulthandler._sigfpe()
+""".strip(),
+            3,
+            'Floating point exception')
+
+    @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
+                     "need faulthandler._sigbus()")
+    def test_sigbus(self):
+        self.check_fatal_error("""
+import faulthandler
+faulthandler.enable()
+faulthandler._sigbus()
+""".strip(),
+            3,
+            'Bus error')
+
+    @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
+                     "need faulthandler._sigill()")
+    def test_sigill(self):
+        self.check_fatal_error("""
+import faulthandler
+faulthandler.enable()
+faulthandler._sigill()
+""".strip(),
+            3,
+            'Illegal instruction')
+
+    def test_fatal_error(self):
+        self.check_fatal_error("""
+import faulthandler
+faulthandler._fatal_error(b'xyz')
+""".strip(),
+            2,
+            'xyz')
+
+    @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
+                     'need faulthandler._stack_overflow()')
+    def test_stack_overflow(self):
+        self.check_fatal_error("""
+import faulthandler
+faulthandler.enable()
+faulthandler._stack_overflow()
+""".strip(),
+            3,
+            '(?:Segmentation fault|Bus error)')
+
+    def test_gil_released(self):
+        self.check_fatal_error("""
+import faulthandler
+faulthandler.enable()
+faulthandler._read_null(True)
+""".strip(),
+            3,
+            '(?:Segmentation fault|Bus error)')
+
+    def test_enable_file(self):
+        with temporary_filename() as filename:
+            self.check_fatal_error("""
+import faulthandler
+output = open({filename}, 'wb')
+faulthandler.enable(output)
+faulthandler._read_null(True)
+""".strip().format(filename=repr(filename)),
+                4,
+                '(?:Segmentation fault|Bus error)',
+                filename=filename)
+
+    def test_enable_threads(self):
+        self.check_fatal_error("""
+import faulthandler
+faulthandler.enable(all_threads=True)
+faulthandler._read_null(True)
+""".strip(),
+            3,
+            '(?:Segmentation fault|Bus error)',
+            all_threads=True)
+
+    def test_disable(self):
+        code = """
+import faulthandler
+faulthandler.enable()
+faulthandler.disable()
+faulthandler._read_null()
+""".strip()
+        not_expected = 'Fatal Python error'
+        stderr = self.get_output(code, False)
+        stder = '\n'.join(stderr)
+        self.assertTrue(not_expected not in stderr,
+                     "%r is present in %r" % (not_expected, stderr))
+
+    def test_is_enabled(self):
+        was_enabled = faulthandler.is_enabled()
+        try:
+            faulthandler.enable()
+            self.assertTrue(faulthandler.is_enabled())
+            faulthandler.disable()
+            self.assertFalse(faulthandler.is_enabled())
+        finally:
+            if was_enabled:
+                faulthandler.enable()
+            else:
+                faulthandler.disable()
+
+    def check_dump_traceback(self, filename):
+        """
+        Explicitly call dump_traceback() function and check its output.
+        Raise an error if the output doesn't match the expected format.
+        """
+        code = """
+import faulthandler
+
+def funcB():
+    if {has_filename}:
+        with open({filename}, "wb") as fp:
+            faulthandler.dump_traceback(fp)
+    else:
+        faulthandler.dump_traceback()
+
+def funcA():
+    funcB()
+
+funcA()
+""".strip()
+        code = code.format(
+            filename=repr(filename),
+            has_filename=bool(filename),
+        )
+        if filename:
+            lineno = 6
+        else:
+            lineno = 8
+        expected = [
+            'Traceback (most recent call first):',
+            '  File "<string>", line %s in funcB' % lineno,
+            '  File "<string>", line 11 in funcA',
+            '  File "<string>", line 13 in <module>'
+        ]
+        trace = self.get_output(code, True, 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.
+        """
+        code = """
+import faulthandler
+from threading import Thread, Event
+import time
+
+def dump():
+    if {filename}:
+        with open({filename}, "wb") as fp:
+            faulthandler.dump_traceback(fp, all_threads=True)
+    else:
+        faulthandler.dump_traceback(all_threads=True)
+
+class Waiter(Thread):
+    # avoid blocking if the main thread raises an exception.
+    daemon = True
+
+    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()
+""".strip()
+        code = code.format(filename=repr(filename))
+        output = self.get_output(code, True, filename)
+        output = '\n'.join(output)
+        if filename:
+            lineno = 8
+        else:
+            lineno = 10
+        regex = """
+^Thread 0x[0-9a-f]+:
+(?:  File ".*threading.py", line [0-9]+ in wait
+)?  File ".*threading.py", line [0-9]+ in wait
+  File "<string>", line 23 in run
+  File ".*threading.py", line [0-9]+ in _bootstrap_inner
+  File ".*threading.py", line [0-9]+ in _bootstrap
+
+Current thread XXX:
+  File "<string>", line {lineno} in dump
+  File "<string>", line 28 in <module>$
+""".strip()
+        regex = regex.format(lineno=lineno)
+        self.assertRegex(output, regex)
+
+    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_tracebacks_later(self, repeat, cancel, filename):
+        """
+        Check how many times the traceback is written in timeout x 2.5 seconds,
+        or timeout x 3.5 seconds if cancel is True: 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, timeout):
+    pause = timeout * 2.5
+    a = time.time()
+    time.sleep(pause)
+    faulthandler.cancel_dump_tracebacks_later()
+    b = time.time()
+    # Check that sleep() was not interrupted
+    assert (b -a) >= pause
+
+    if cancel:
+        pause = timeout * 1.5
+        a = time.time()
+        time.sleep(pause)
+        b = time.time()
+        # Check that sleep() was not interrupted
+        assert (b -a) >= pause
+
+timeout = 0.5
+repeat = {repeat}
+cancel = {cancel}
+if {has_filename}:
+    file = open({filename}, "wb")
+else:
+    file = None
+faulthandler.dump_tracebacks_later(timeout,
+    repeat=repeat, file=file)
+func(repeat, cancel, timeout)
+if file is not None:
+    file.close()
+""".strip()
+        code = code.format(
+            filename=repr(filename),
+            has_filename=bool(filename),
+            repeat=repeat,
+            cancel=cancel,
+        )
+        trace = self.get_output(code, True, filename)
+        trace = '\n'.join(trace)
+
+        if repeat:
+            count = 2
+        else:
+            count = 1
+        header = 'Thread 0x[0-9a-f]+:\n'
+        regex = expected_traceback(7, 30, header, count=count)
+        self.assertRegex(trace, '^%s$' % regex)
+
+    @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
+                     'need faulthandler.dump_tracebacks_later()')
+    def check_dump_tracebacks_later(self, repeat=False, cancel=False,
+                                  file=False):
+        if file:
+            with temporary_filename() as filename:
+                self._check_dump_tracebacks_later(repeat, cancel, filename)
+        else:
+            self._check_dump_tracebacks_later(repeat, cancel, None)
+
+    def test_dump_tracebacks_later(self):
+        self.check_dump_tracebacks_later()
+
+    def test_dump_tracebacks_later_repeat(self):
+        self.check_dump_tracebacks_later(repeat=True)
+
+    def test_dump_tracebacks_later_repeat_cancel(self):
+        self.check_dump_tracebacks_later(repeat=True, cancel=True)
+
+    def test_dump_tracebacks_later_file(self):
+        self.check_dump_tracebacks_later(file=True)
+
+    @unittest.skipIf(not hasattr(faulthandler, "register"),
+                     "need faulthandler.register")
+    def check_register(self, filename=False, all_threads=False):
+        """
+        Register a handler displaying 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 {has_filename}:
+    file = open({filename}, "wb")
+else:
+    file = None
+faulthandler.register(signum, file=file, all_threads={all_threads})
+func(signum)
+if file is not None:
+    file.close()
+""".strip()
+        code = code.format(
+            filename=repr(filename),
+            has_filename=bool(filename),
+            all_threads=all_threads,
+        )
+        trace = self.get_output(code, True, filename)
+        trace = '\n'.join(trace)
+        if all_threads:
+            regex = 'Current thread XXX:\n'
+        else:
+            regex = 'Traceback \(most recent call first\):\n'
+        regex = expected_traceback(6, 14, regex)
+        self.assertTrue(re.match(regex, trace),
+                         "[%s] doesn't match [%s]: use_filename=%s, all_threads=%s"
+                         % (regex, trace, 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_main():
+    support.run_unittest(FaultHandlerTests)
+
+if __name__ == "__main__":
+    test_main()
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -87,6 +87,8 @@
 Library
 -------
 
+- Issue #11393: Add the new faulthandler module.
+
 - Issue #11618: Fix the timeout logic in threading.Lock.acquire() under Windows.
 
 - Removed the 'strict' argument to email.parser.Parser, which has been
diff --git a/Modules/Setup.dist b/Modules/Setup.dist
--- a/Modules/Setup.dist
+++ b/Modules/Setup.dist
@@ -127,6 +127,9 @@
 # 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
diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c
new file mode 100644
--- /dev/null
+++ b/Modules/faulthandler.c
@@ -0,0 +1,971 @@
+#include "Python.h"
+#include "pythread.h"
+#include <signal.h>
+#include <object.h>
+#include <frameobject.h>
+#include <signal.h>
+
+#ifdef WITH_THREAD
+#  define FAULTHANDLER_LATER
+#endif
+
+#ifndef MS_WINDOWS
+   /* register() is useless on Windows, because only SIGSEGV and SIGILL can be
+      handled by the process, and these signals can only be used with enable(),
+      not using register() */
+#  define FAULTHANDLER_USER
+#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;
+    PY_TIMEOUT_T timeout_ms;   /* timeout in microseconds */
+    int repeat;
+    volatile int running;
+    PyInterpreterState *interp;
+    int exit;
+    /* released by parent thread when cancel request */
+    PyThread_type_lock cancel_event;
+    /* released by child thread when joined */
+    PyThread_type_lock join_event;
+} thread;
+#endif
+
+#ifdef FAULTHANDLER_USER
+typedef struct {
+    int enabled;
+    PyObject *file;
+    int fd;
+    int all_threads;
+    _Py_sighandler_t previous;
+} user_signal_t;
+
+static user_signal_t *user_signals;
+
+/* the following macros come from Python: Modules/signalmodule.c */
+#if defined(PYOS_OS2) && !defined(PYCC_GCC)
+#define NSIG 12
+#endif
+#ifndef NSIG
+# if defined(_NSIG)
+#  define NSIG _NSIG            /* For BSD/SysV */
+# elif defined(_SIGMAX)
+#  define NSIG (_SIGMAX + 1)    /* For QNX */
+# elif defined(SIGMAX)
+#  define NSIG (SIGMAX + 1)     /* For djgpp */
+# else
+#  define NSIG 64               /* Use a reasonable default value */
+# endif
+#endif
+
+#endif /* FAULTHANDLER_USER */
+
+
+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
+
+
+/* Get the file descriptor of a file by calling its fileno() method and then
+   call its flush() method.
+
+   If file is NULL or Py_None, use sys.stderr as the new file.
+
+   On success, return the new file and write the file descriptor into *p_fd.
+   On error, return NULL. */
+
+static PyObject*
+faulthandler_get_fileno(PyObject *file, int *p_fd)
+{
+    PyObject *result;
+    long fd_long;
+    int fd;
+
+    if (file == NULL || file == Py_None) {
+        file = PySys_GetObject("stderr");
+        if (file == NULL) {
+            PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr");
+            return NULL;
+        }
+    }
+
+    result = PyObject_CallMethod(file, "fileno", "");
+    if (result == NULL)
+        return NULL;
+
+    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 NULL;
+    }
+
+    result = PyObject_CallMethod(file, "flush", "");
+    if (result != NULL)
+        Py_DECREF(result);
+    else {
+        /* ignore flush() error */
+        PyErr_Clear();
+    }
+    *p_fd = fd;
+    return file;
+}
+
+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;
+
+    file = faulthandler_get_fileno(file, &fd);
+    if (file == NULL)
+        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->interp, 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, restore the previous handler and call
+   the previous handler.
+
+   On Windows, don't call explictly the previous handler, because Windows
+   signal handler would not be called (for an unknown reason). The execution of
+   the program continues at faulthandler_fatal_error() exit, but the same
+   instruction will raise the same fault (signal), and so the previous handler
+   will be called.
+
+   This function is signal safe and should only call signal safe functions. */
+
+static void
+faulthandler_fatal_error(
+    int signum
+#ifdef HAVE_SIGACTION
+    , siginfo_t *siginfo, void *ucontext
+#endif
+)
+{
+    const int fd = fatal_error.fd;
+    unsigned int i;
+    fault_handler_t *handler = NULL;
+    PyThreadState *tstate;
+
+    if (!fatal_error.enabled)
+        return;
+
+    for (i=0; i < faulthandler_nsignals; i++) {
+        handler = &faulthandler_handlers[i];
+        if (handler->signum == signum)
+            break;
+    }
+    if (handler == NULL) {
+        /* faulthandler_nsignals == 0 (unlikely) */
+        return;
+    }
+
+    /* restore the previous handler */
+#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->interp, tstate);
+    else
+        _Py_DumpTraceback(fd, tstate);
+
+#ifndef MS_WINDOWS
+    /* call the previous signal handler: it is called if we use sigaction()
+       thanks to SA_NODEFER flag, otherwise it is deferred */
+    raise(signum);
+#else
+    /* on Windows, don't call explictly the previous handler, because Windows
+       signal handler would not be called */
+#endif
+}
+
+/* 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;
+#endif
+    int err;
+    int fd;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+        "|Oi:enable", kwlist, &file, &all_threads))
+        return NULL;
+
+    file = faulthandler_get_fileno(file, &fd);
+    if (file == NULL)
+        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_sigaction = 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;
+        }
+    }
+    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
+
+static void
+faulthandler_thread(void *unused)
+{
+    PyLockStatus st;
+    const char* errmsg;
+    PyThreadState *current;
+    int ok;
+
+    do {
+        st = PyThread_acquire_lock_timed(thread.cancel_event,
+                                         thread.timeout_ms, 0);
+        if (st == PY_LOCK_ACQUIRED) {
+            /* Cancelled by user */
+            break;
+        }
+        /* Timeout => dump traceback */
+        assert(st == PY_LOCK_FAILURE);
+
+        /* get the thread holding the GIL, NULL if no thread hold the GIL */
+        current = _Py_atomic_load_relaxed(&_PyThreadState_Current);
+
+        errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, current);
+        ok = (errmsg == NULL);
+
+        if (thread.exit)
+            _exit(1);
+    } while (ok && thread.repeat);
+
+    /* The only way out */
+    thread.running = 0;
+    PyThread_release_lock(thread.join_event);
+    PyThread_release_lock(thread.cancel_event);
+}
+
+static void
+faulthandler_cancel_dump_traceback_later(void)
+{
+    if (thread.running) {
+        /* Notify cancellation */
+        PyThread_release_lock(thread.cancel_event);
+        /* Wait for thread to join */
+        PyThread_acquire_lock(thread.join_event, 1);
+        assert(thread.running == 0);
+        PyThread_release_lock(thread.join_event);
+    }
+    Py_CLEAR(thread.file);
+}
+
+static PyObject*
+faulthandler_dump_traceback_later(PyObject *self,
+                                  PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = {"timeout", "repeat", "file", "exit", NULL};
+    double timeout;
+    PY_TIMEOUT_T timeout_ms;
+    int repeat = 0;
+    PyObject *file = NULL;
+    int fd;
+    int exit = 0;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+        "d|iOi:dump_tracebacks_later", kwlist,
+        &timeout, &repeat, &file, &exit))
+        return NULL;
+    timeout *= 1e6;
+    if (timeout >= (double) PY_TIMEOUT_MAX) {
+        PyErr_SetString(PyExc_OverflowError,  "timeout value is too large");
+        return NULL;
+    }
+    timeout_ms = (PY_TIMEOUT_T)timeout;
+    if (timeout_ms <= 0) {
+        PyErr_SetString(PyExc_ValueError, "timeout must be greater than 0");
+        return NULL;
+    }
+
+    file = faulthandler_get_fileno(file, &fd);
+    if (file == NULL)
+        return NULL;
+
+    /* Cancel previous thread, if running */
+    faulthandler_cancel_dump_traceback_later();
+
+    Py_XDECREF(thread.file);
+    Py_INCREF(file);
+    thread.file = file;
+    thread.fd = fd;
+    thread.timeout_ms = timeout_ms;
+    thread.repeat = repeat;
+    thread.interp = PyThreadState_Get()->interp;
+    thread.exit = exit;
+
+    /* Arm these locks to serve as events when released */
+    PyThread_acquire_lock(thread.join_event, 1);
+    PyThread_acquire_lock(thread.cancel_event, 1);
+
+    thread.running = 1;
+    if (PyThread_start_new_thread(faulthandler_thread, NULL) == -1) {
+        thread.running = 0;
+        Py_CLEAR(thread.file);
+        PyErr_SetString(PyExc_RuntimeError,
+                        "unable to start watchdog thread");
+        return NULL;
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject*
+faulthandler_cancel_dump_traceback_later_py(PyObject *self)
+{
+    faulthandler_cancel_dump_traceback_later();
+    Py_RETURN_NONE;
+}
+#endif /* FAULTHANDLER_LATER */
+
+#ifdef FAULTHANDLER_USER
+/* Handler of user signals (e.g. SIGUSR1).
+
+   Dump the traceback of the current thread, or of all threads if
+   thread.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 = &user_signals[signum];
+    if (!user->enabled)
+        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->interp, 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;
+    _Py_sighandler_t previous;
+#ifdef HAVE_SIGACTION
+    struct sigaction action;
+#endif
+    int err;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+        "i|Oi:register", kwlist,
+        &signum, &file, &all_threads))
+        return NULL;
+
+    if (signum < 1 || NSIG <= signum) {
+        PyErr_SetString(PyExc_ValueError, "signal number out of range");
+        return NULL;
+    }
+
+    for (i=0; i < faulthandler_nsignals; i++) {
+        if (faulthandler_handlers[i].signum == signum) {
+            PyErr_Format(PyExc_RuntimeError,
+                         "signal %i cannot be registered by register(), "
+                         "use enable() instead",
+                         signum);
+            return NULL;
+        }
+    }
+
+    file = faulthandler_get_fileno(file, &fd);
+    if (file == NULL)
+        return NULL;
+
+    if (user_signals == NULL) {
+        user_signals = calloc(NSIG, sizeof(user_signal_t));
+        if (user_signals == NULL)
+            return PyErr_NoMemory();
+    }
+    user = &user_signals[signum];
+
+    if (!user->enabled) {
+#ifdef HAVE_SIGACTION
+        action.sa_handler = faulthandler_user;
+        sigemptyset(&action.sa_mask);
+        /* if the signal is received while the kernel is executing a system
+           call, try to restart the system call instead of interrupting it and
+           return EINTR */
+        action.sa_flags = SA_RESTART;
+#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(signum, &action, &previous);
+#else
+        previous = signal(signum, faulthandler_user);
+        err = (previous == SIG_ERR);
+#endif
+        if (err) {
+            PyErr_SetFromErrno(PyExc_OSError);
+            return NULL;
+        }
+    }
+
+    Py_XDECREF(user->file);
+    Py_INCREF(file);
+    user->file = file;
+    user->fd = fd;
+    user->all_threads = all_threads;
+    user->previous = previous;
+    user->enabled = 1;
+
+    Py_RETURN_NONE;
+}
+
+static int
+faulthandler_unregister(user_signal_t *user, int signum)
+{
+    if (user->enabled)
+        return 0;
+    user->enabled = 0;
+#ifdef HAVE_SIGACTION
+    (void)sigaction(signum, &user->previous, NULL);
+#else
+    (void)signal(signum, user->previous);
+#endif
+    Py_CLEAR(user->file);
+    user->fd = -1;
+    return 1;
+}
+
+static PyObject*
+faulthandler_unregister_py(PyObject *self, PyObject *args)
+{
+    int signum;
+    user_signal_t *user;
+    int change;
+
+    if (!PyArg_ParseTuple(args, "i:unregister", &signum))
+        return NULL;
+
+    if (signum < 1 || NSIG <= signum) {
+        PyErr_SetString(PyExc_ValueError, "signal number out of range");
+        return NULL;
+    }
+
+    user = &user_signals[signum];
+    change = faulthandler_unregister(user, signum);
+    return PyBool_FromLong(change);
+}
+#endif   /* FAULTHANDLER_USER */
+
+
+static PyObject *
+faulthandler_read_null(PyObject *self, PyObject *args)
+{
+    int *x = NULL, y;
+    int release_gil = 0;
+    if (!PyArg_ParseTuple(args, "|i:_read_null", &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_sigsegv(PyObject *self, PyObject *args)
+{
+#if defined(MS_WINDOWS)
+    /* faulthandler_fatal_error() restores the previous signal handler and then
+       gives back the execution flow to the program. In a normal case, the
+       SIGSEGV was raised by the kernel because of a fault, and so if the
+       program retries to execute the same instruction, the fault will be
+       raised again.
+
+       Here the fault is simulated by a fake SIGSEGV signal raised by the
+       application. We have to raise SIGSEGV at lease twice: once for
+       faulthandler_fatal_error(), and one more time for the previous signal
+       handler. */
+    while(1)
+        raise(SIGSEGV);
+#else
+    raise(SIGSEGV);
+#endif
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+faulthandler_sigfpe(PyObject *self, PyObject *args)
+{
+    /* Do an integer division by zero: raise a SIGFPE on Intel CPU, but not on
+       PowerPC. Use volatile to disable compile-time optimizations. */
+    volatile int x = 1, y = 0, z;
+    z = x / y;
+    /* if the division by zero didn't raise a SIGFPE, raise it manually */
+    raise(SIGFPE);
+    Py_RETURN_NONE;
+}
+
+#ifdef SIGBUS
+static PyObject *
+faulthandler_sigbus(PyObject *self, PyObject *args)
+{
+    raise(SIGBUS);
+    Py_RETURN_NONE;
+}
+#endif
+
+#ifdef SIGILL
+static PyObject *
+faulthandler_sigill(PyObject *self, PyObject *args)
+{
+#if defined(MS_WINDOWS)
+    /* see faulthandler_sigsegv() for the explanation about while(1) */
+    while(1)
+        raise(SIGILL);
+#else
+    raise(SIGILL);
+#endif
+    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;
+}
+
+#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)
+static PyObject *
+faulthandler_stack_overflow(PyObject *self)
+{
+    /* allocate 4096 bytes on the stack at each call */
+    unsigned char buffer[4096];
+    buffer[0] = 1;
+    buffer[4095] = 2;
+    faulthandler_stack_overflow(self);
+    return PyLong_FromLong(buffer[0] + buffer[4095]);
+}
+#endif
+
+
+static int
+faulthandler_traverse(PyObject *module, visitproc visit, void *arg)
+{
+#ifdef FAULTHANDLER_USER
+    unsigned int index;
+#endif
+
+#ifdef FAULTHANDLER_LATER
+    Py_VISIT(thread.file);
+#endif
+#ifdef FAULTHANDLER_USER
+    if (user_signals != NULL) {
+        for (index=0; index < NSIG; index++)
+            Py_VISIT(user_signals[index].file);
+    }
+#endif
+    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_tracebacks_later",
+     (PyCFunction)faulthandler_dump_traceback_later, METH_VARARGS|METH_KEYWORDS,
+     PyDoc_STR("dump_tracebacks_later(timeout, repeat=False, file=sys.stderr):\n"
+               "dump the traceback of all threads in timeout seconds,\n"
+               "or each timeout seconds if repeat is True.")},
+    {"cancel_dump_tracebacks_later",
+     (PyCFunction)faulthandler_cancel_dump_traceback_later_py, METH_NOARGS,
+     PyDoc_STR("cancel_dump_tracebacks_later():\ncancel the previous call "
+               "to dump_tracebacks_later().")},
+#endif
+
+#ifdef FAULTHANDLER_USER
+    {"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()")},
+#endif
+
+    {"_read_null", faulthandler_read_null, METH_VARARGS,
+     PyDoc_STR("_read_null(release_gil=False): read from NULL, raise "
+               "a SIGSEGV or SIGBUS signal depending on the platform")},
+    {"_sigsegv", faulthandler_sigsegv, METH_VARARGS,
+     PyDoc_STR("_sigsegv(): 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)")},
+#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)
+    {"_stack_overflow", (PyCFunction)faulthandler_stack_overflow, METH_NOARGS,
+     PyDoc_STR("_stack_overflow(): recursive call to raise a stack overflow")},
+#endif
+    {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)
+{
+    return PyModule_Create(&module_def);
+}
+
+/* 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
+    int err;
+
+    /* 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 = 0;
+    stack.ss_size = SIGSTKSZ;
+    stack.ss_sp = PyMem_Malloc(stack.ss_size);
+    if (stack.ss_sp != NULL) {
+        err = sigaltstack(&stack, NULL);
+        if (err) {
+            PyMem_Free(stack.ss_sp);
+            stack.ss_sp = NULL;
+        }
+    }
+#endif
+#ifdef FAULTHANDLER_LATER
+    thread.running = 0;
+    thread.file = NULL;
+    thread.cancel_event = PyThread_allocate_lock();
+    thread.join_event = PyThread_allocate_lock();
+    if (!thread.cancel_event || !thread.join_event) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "could not allocate locks for faulthandler");
+        return -1;
+    }
+#endif
+
+    return faulthandler_env_options();
+}
+
+void _PyFaulthandler_Fini(void)
+{
+#ifdef FAULTHANDLER_USER
+    unsigned int i;
+#endif
+
+#ifdef FAULTHANDLER_LATER
+    /* later */
+    faulthandler_cancel_dump_traceback_later();
+    if (thread.cancel_event) {
+        PyThread_free_lock(thread.cancel_event);
+        thread.cancel_event = NULL;
+    }
+    if (thread.join_event) {
+        PyThread_free_lock(thread.join_event);
+        thread.join_event = NULL;
+    }
+#endif
+
+#ifdef FAULTHANDLER_USER
+    /* user */
+    if (user_signals != NULL) {
+        for (i=0; i < NSIG; i++)
+            faulthandler_unregister(&user_signals[i], i+1);
+        free(user_signals);
+        user_signals = NULL;
+    }
+#endif
+
+    /* fatal */
+    faulthandler_disable();
+#ifdef HAVE_SIGALTSTACK
+    if (stack.ss_sp != NULL) {
+        PyMem_Free(stack.ss_sp);
+        stack.ss_sp = NULL;
+    }
+#endif
+}
diff --git a/Modules/main.c b/Modules/main.c
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -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 --git a/PC/config.c b/PC/config.c
--- a/PC/config.c
+++ b/PC/config.c
@@ -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 --git a/PCbuild/pythoncore.vcproj b/PCbuild/pythoncore.vcproj
--- a/PCbuild/pythoncore.vcproj
+++ b/PCbuild/pythoncore.vcproj
@@ -1087,6 +1087,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\Modules\faulthandler.c"
+				>
+			</File>
+			<File
 				RelativePath="..\Modules\gcmodule.c"
 				>
 			</File>
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -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);
+    PyThreadState *tstate;
+
     fprintf(stderr, "Fatal Python error: %s\n", msg);
     fflush(stderr); /* it helps in Windows debug build */
     if (PyErr_Occurred()) {
         PyErr_PrintEx(0);
     }
+    else {
+        tstate = _Py_atomic_load_relaxed(&_PyThreadState_Current);
+        if (tstate != NULL) {
+            fputc('\n', stderr);
+            fflush(stderr);
+            _Py_DumpTraceback(fd, tstate);
+        }
+    }
+
 #ifdef MS_WINDOWS
     {
         size_t len = strlen(msg);
diff --git a/Python/traceback.c b/Python/traceback.c
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -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,233 @@
         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;
+    Py_UNICODE *u;
+    char c;
+
+    size = PyUnicode_GET_SIZE(text);
+    u = PyUnicode_AS_UNICODE(text);
+
+    if (MAX_STRING_LENGTH < size) {
+        size = MAX_STRING_LENGTH;
+        truncated = 1;
+    }
+    else
+        truncated = 0;
+
+    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
+        }
+    }
+    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, "???");
+    }
+
+    /* PyFrame_GetLineNumber() was introduced in Python 2.7.0 and 3.2.0 */
+    lineno = PyCode_Addr2Line(frame->f_code, frame->f_lasti);
+    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;
+}
+
+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");
+}
+
+const char*
+_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
+                         PyThreadState *current_thread)
+{
+    PyThreadState *tstate;
+    unsigned int nthreads;
+
+    /* Get the current interpreter from the current thread */
+    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 --git a/configure b/configure
--- a/configure
+++ b/configure
@@ -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 --git a/configure.in b/configure.in
--- a/configure.in
+++ b/configure.in
@@ -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 --git a/pyconfig.h.in b/pyconfig.h.in
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -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
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list