Issue #11393: Add the new faulthandler module

This commit is contained in:
Victor Stinner 2011-03-31 01:31:06 +02:00
parent d85456279f
commit 024e37adcc
19 changed files with 1907 additions and 5 deletions

View File

@ -10,7 +10,8 @@ allowing you to identify bottlenecks in your programs.
.. toctree:: .. toctree::
bdb.rst bdb.rst
faulthandler.rst
pdb.rst pdb.rst
profile.rst profile.rst
timeit.rst timeit.rst
trace.rst trace.rst

View File

@ -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

View File

@ -498,6 +498,13 @@ These environment variables influence Python's behavior.
separated string, it is equivalent to specifying :option:`-W` multiple separated string, it is equivalent to specifying :option:`-W` multiple
times. 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 Debug-mode variables
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~

View File

@ -68,6 +68,14 @@ New, Improved, and Deprecated Modules
* Stub * Stub
faulthandler
------------
New module: :mod:`faulthandler`.
* :envvar:`PYTHONFAULTHANDLER`
* :option:`-X` ``faulthandler``
os os
-- --

View File

@ -5,6 +5,8 @@
extern "C" { extern "C" {
#endif #endif
#include "pystate.h"
struct _frame; struct _frame;
/* Traceback interface */ /* Traceback interface */
@ -28,6 +30,44 @@ PyAPI_FUNC(int) _Py_DisplaySourceLine(PyObject *, PyObject *, int, int);
PyAPI_DATA(PyTypeObject) PyTraceBack_Type; PyAPI_DATA(PyTypeObject) PyTraceBack_Type;
#define PyTraceBack_Check(v) (Py_TYPE(v) == &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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -157,6 +157,7 @@ option '-uall,-gui'.
""" """
import builtins import builtins
import faulthandler
import getopt import getopt
import json import json
import os import os
@ -490,6 +491,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
next_single_test = alltests[alltests.index(selected[0])+1] next_single_test = alltests[alltests.index(selected[0])+1]
except IndexError: except IndexError:
next_single_test = None next_single_test = None
selected = ['test_faulthandler']
# Remove all the tests that precede start if it's set. # Remove all the tests that precede start if it's set.
if start: if start:
try: try:
@ -1551,6 +1553,9 @@ def _make_temp_dir_for_build(TEMPDIR):
return TEMPDIR, TESTCWD return TEMPDIR, TESTCWD
if __name__ == '__main__': 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 # Remove regrtest.py's own directory from the module search path. Despite
# the elimination of implicit relative imports, this is still needed to # the elimination of implicit relative imports, this is still needed to
# ensure that submodules of the test package do not inappropriately appear # ensure that submodules of the test package do not inappropriately appear

View File

@ -56,11 +56,12 @@ def assert_python_failure(*args, **env_vars):
""" """
return _assert_python(False, *args, **env_vars) return _assert_python(False, *args, **env_vars)
def spawn_python(*args): def spawn_python(*args, **kw):
cmd_line = [sys.executable, '-E'] cmd_line = [sys.executable, '-E']
cmd_line.extend(args) cmd_line.extend(args)
return subprocess.Popen(cmd_line, stdin=subprocess.PIPE, 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): def kill_python(p):
p.stdin.close() p.stdin.close()

View File

@ -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 + '$'
@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()

View File

@ -87,6 +87,8 @@ Core and Builtins
Library Library
------- -------
- Issue #11393: Add the new faulthandler module.
- Issue #11618: Fix the timeout logic in threading.Lock.acquire() under Windows. - Issue #11618: Fix the timeout logic in threading.Lock.acquire() under Windows.
- Removed the 'strict' argument to email.parser.Parser, which has been - Removed the 'strict' argument to email.parser.Parser, which has been

View File

@ -127,6 +127,9 @@ _io -I$(srcdir)/Modules/_io _io/_iomodule.c _io/iobase.c _io/fileio.c _io/bytesi
# builtin module avoids some bootstrapping problems and reduces overhead. # builtin module avoids some bootstrapping problems and reduces overhead.
zipimport zipimport.c zipimport zipimport.c
# faulthandler module
faulthandler faulthandler.c
# The rest of the modules listed in this file are all commented out by # The rest of the modules listed in this file are all commented out by
# default. Usually they can be detected and built as dynamically # default. Usually they can be detected and built as dynamically
# loaded modules by the new setup.py script added in Python 2.1. If # loaded modules by the new setup.py script added in Python 2.1. If

971
Modules/faulthandler.c Normal file
View File

@ -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
}

View File

@ -100,6 +100,7 @@ static char *usage_5 =
" The default module search path uses %s.\n" " The default module search path uses %s.\n"
"PYTHONCASEOK : ignore case in 'import' statements (Windows).\n" "PYTHONCASEOK : ignore case in 'import' statements (Windows).\n"
"PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n" "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n"
"PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.\n"
; ;
static int static int

View File

@ -12,6 +12,7 @@ extern PyObject* PyInit_audioop(void);
extern PyObject* PyInit_binascii(void); extern PyObject* PyInit_binascii(void);
extern PyObject* PyInit_cmath(void); extern PyObject* PyInit_cmath(void);
extern PyObject* PyInit_errno(void); extern PyObject* PyInit_errno(void);
extern PyObject* PyInit_faulthandler(void);
extern PyObject* PyInit_gc(void); extern PyObject* PyInit_gc(void);
extern PyObject* PyInit_math(void); extern PyObject* PyInit_math(void);
extern PyObject* PyInit__md5(void); extern PyObject* PyInit__md5(void);
@ -82,6 +83,7 @@ struct _inittab _PyImport_Inittab[] = {
{"binascii", PyInit_binascii}, {"binascii", PyInit_binascii},
{"cmath", PyInit_cmath}, {"cmath", PyInit_cmath},
{"errno", PyInit_errno}, {"errno", PyInit_errno},
{"faulthandler", PyInit_faulthandler},
{"gc", PyInit_gc}, {"gc", PyInit_gc},
{"math", PyInit_math}, {"math", PyInit_math},
{"nt", PyInit_nt}, /* Use the NT os functions, not posix */ {"nt", PyInit_nt}, /* Use the NT os functions, not posix */

View File

@ -1086,6 +1086,10 @@
RelativePath="..\Modules\errnomodule.c" RelativePath="..\Modules\errnomodule.c"
> >
</File> </File>
<File
RelativePath="..\Modules\faulthandler.c"
>
</File>
<File <File
RelativePath="..\Modules\gcmodule.c" RelativePath="..\Modules\gcmodule.c"
> >

View File

@ -70,6 +70,8 @@ extern void _PyUnicode_Init(void);
extern void _PyUnicode_Fini(void); extern void _PyUnicode_Fini(void);
extern int _PyLong_Init(void); extern int _PyLong_Init(void);
extern void PyLong_Fini(void); extern void PyLong_Fini(void);
extern int _PyFaulthandler_Init(void);
extern void _PyFaulthandler_Fini(void);
#ifdef WITH_THREAD #ifdef WITH_THREAD
extern void _PyGILState_Init(PyInterpreterState *, PyThreadState *); extern void _PyGILState_Init(PyInterpreterState *, PyThreadState *);
@ -286,6 +288,10 @@ Py_InitializeEx(int install_sigs)
_PyImportHooks_Init(); _PyImportHooks_Init();
/* initialize the faulthandler module */
if (_PyFaulthandler_Init())
Py_FatalError("Py_Initialize: can't initialize faulthandler");
/* Initialize _warnings. */ /* Initialize _warnings. */
_PyWarnings_Init(); _PyWarnings_Init();
@ -454,6 +460,9 @@ Py_Finalize(void)
/* Destroy the database used by _PyImport_{Fixup,Find}Extension */ /* Destroy the database used by _PyImport_{Fixup,Find}Extension */
_PyImport_Fini(); _PyImport_Fini();
/* unload faulthandler module */
_PyFaulthandler_Fini();
/* Debugging stuff */ /* Debugging stuff */
#ifdef COUNT_ALLOCS #ifdef COUNT_ALLOCS
dump_counts(stdout); dump_counts(stdout);
@ -2100,11 +2109,23 @@ cleanup:
void void
Py_FatalError(const char *msg) Py_FatalError(const char *msg)
{ {
const int fd = fileno(stderr);
PyThreadState *tstate;
fprintf(stderr, "Fatal Python error: %s\n", msg); fprintf(stderr, "Fatal Python error: %s\n", msg);
fflush(stderr); /* it helps in Windows debug build */ fflush(stderr); /* it helps in Windows debug build */
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
PyErr_PrintEx(0); 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 #ifdef MS_WINDOWS
{ {
size_t len = strlen(msg); size_t len = strlen(msg);

View File

@ -13,6 +13,11 @@
#define OFF(x) offsetof(PyTracebackObject, x) #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 */ /* Method from Parser/tokenizer.c */
extern char * PyTokenizer_FindEncoding(int); extern char * PyTokenizer_FindEncoding(int);
@ -402,3 +407,233 @@ PyTraceBack_Print(PyObject *v, PyObject *f)
err = tb_printinternal((PyTracebackObject *)v, f, limit); err = tb_printinternal((PyTracebackObject *)v, f, limit);
return err; 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;
}

2
configure vendored
View File

@ -9261,7 +9261,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \ select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
setgid sethostname \ setgid sethostname \
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \ 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 \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \ truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
wcscoll wcsftime wcsxfrm writev _getpty wcscoll wcsftime wcsxfrm writev _getpty

View File

@ -2507,7 +2507,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \ select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
setgid sethostname \ setgid sethostname \
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \ 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 \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \ truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
wcscoll wcsftime wcsxfrm writev _getpty) wcscoll wcsftime wcsxfrm writev _getpty)

View File

@ -710,6 +710,9 @@
/* Define to 1 if you have the `sigaction' function. */ /* Define to 1 if you have the `sigaction' function. */
#undef HAVE_SIGACTION #undef HAVE_SIGACTION
/* Define to 1 if you have the `sigaltstack' function. */
#undef HAVE_SIGALTSTACK
/* Define to 1 if you have the `siginterrupt' function. */ /* Define to 1 if you have the `siginterrupt' function. */
#undef HAVE_SIGINTERRUPT #undef HAVE_SIGINTERRUPT