cpython: b0680b5a5215 (original) (raw)
--- a/Doc/library/debug.rst +++ b/Doc/library/debug.rst @@ -10,7 +10,8 @@ allowing you to identify bottlenecks in .. toctree:: bdb.rst
new file mode 100644
--- /dev/null
+++ b/Doc/library/faulthandler.rst
@@ -0,0 +1,129 @@
+:mod:faulthandler
--- Dump the Python traceback
+=================================================
+
+.. module:: faulthandler
+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)
+
+ +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. +
+ +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() +
+ +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: ::
+
- Traceback (most recent call first):
File "/home/python/cpython/Lib/ctypes/__init__.py", line 486 in string_at[](#l2.130)
File "<stdin>", line 1 in <module>[](#l2.131)
- Segmentation fault
--- a/Doc/using/cmdline.rst
+++ b/Doc/using/cmdline.rst
@@ -498,6 +498,13 @@ These environment variables influence Py
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. +
--- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -68,6 +68,14 @@ New, Improved, and Deprecated Modules
--- 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_FUNC(int) _Py_DisplaySourceLine(Py 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):[](#l5.18)
File "xxx", line xxx in <xxx>[](#l5.19)
File "xxx", line xxx in <xxx>[](#l5.20)
...[](#l5.21)
File "xxx", line xxx in <xxx>[](#l5.22)
- 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(
+ +/* 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(
+ + #ifdef __cplusplus } #endif
--- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -157,6 +157,7 @@ option '-uall,-gui'. """ import builtins +import faulthandler import getopt import json import os @@ -490,6 +491,7 @@ def main(tests=None, testdir=None, verbo next_single_test = alltests[alltests.index(selected[0])+1] except IndexError: next_single_test = None
@@ -1551,6 +1553,9 @@ def _make_temp_dir_for_build(TEMPDIR): return TEMPDIR, TESTCWD if name == 'main':
+ # 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
--- a/Lib/test/script_helper.py +++ b/Lib/test/script_helper.py @@ -56,11 +56,12 @@ def assert_python_failure(*args, **env_v """ 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)[](#l7.12)
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,[](#l7.13)
**kw)[](#l7.14)
def kill_python(p): p.stdin.close()
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:
- def prepare_subprocess():
# don't create core file[](#l8.21)
try:[](#l8.22)
setrlimit(RLIMIT_CORE, (0, 0))[](#l8.23)
except (ValueError, resource_error):[](#l8.24)
pass[](#l8.25)
+ +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[](#l8.32)
- return '^' + regex + '$'
+ +@contextmanager +def temporary_filename():
- filename = tempfile.mktemp()
- try:
yield filename[](#l8.39)
- finally:
support.unlink(filename)[](#l8.41)
+ +class FaultHandlerTests(unittest.TestCase):
- def get_output(self, code, expect_success, filename=None):
"""[](#l8.45)
Run the specified code in Python (in a new child process) and read the[](#l8.46)
output from the standard error or from a file (if filename is set).[](#l8.47)
Return the output lines as a list.[](#l8.48)
Strip the reference count from the standard error for Python debug[](#l8.50)
build, and replace "Current thread 0x00007f8d8fbd9700" by "Current[](#l8.51)
thread XXX".[](#l8.52)
"""[](#l8.53)
options = {}[](#l8.54)
if prepare_subprocess:[](#l8.55)
options['preexec_fn'] = prepare_subprocess[](#l8.56)
process = script_helper.spawn_python('-c', code, **options)[](#l8.57)
stdout, stderr = process.communicate()[](#l8.58)
exitcode = process.wait()[](#l8.59)
if expect_success:[](#l8.60)
self.assertEqual(exitcode, 0)[](#l8.61)
else:[](#l8.62)
self.assertNotEqual(exitcode, 0)[](#l8.63)
if filename:[](#l8.64)
with open(filename, "rb") as fp:[](#l8.65)
output = fp.read()[](#l8.66)
else:[](#l8.67)
output = support.strip_python_stderr(stdout)[](#l8.68)
output = output.decode('ascii', 'backslashreplace')[](#l8.69)
output = re.sub('Current thread 0x[0-9a-f]+',[](#l8.70)
'Current thread XXX',[](#l8.71)
output)[](#l8.72)
return output.splitlines()[](#l8.73)
- def check_fatal_error(self, code, line_number, name_regex,
filename=None, all_threads=False):[](#l8.76)
"""[](#l8.77)
Check that the fault handler for fatal errors is enabled and check the[](#l8.78)
traceback from the child process output.[](#l8.79)
Raise an error if the output doesn't match the expected format.[](#l8.81)
"""[](#l8.82)
if all_threads:[](#l8.83)
header = 'Current thread XXX'[](#l8.84)
else:[](#l8.85)
header = 'Traceback (most recent call first)'[](#l8.86)
regex = """[](#l8.87)
+^Fatal Python error: {name} + +{header}:
- File "", line {lineno} in $ +""".strip()
regex = regex.format([](#l8.93)
lineno=line_number,[](#l8.94)
name=name_regex,[](#l8.95)
header=re.escape(header))[](#l8.96)
output = self.get_output(code, False, filename)[](#l8.97)
output = '\n'.join(output)[](#l8.98)
self.assertRegex(output, regex)[](#l8.99)
+import faulthandler +faulthandler.enable() +faulthandler._read_null() +""".strip(),
3,[](#l8.107)
'(?:Segmentation fault|Bus error)')[](#l8.108)
+import faulthandler +faulthandler.enable() +faulthandler._sigsegv() +""".strip(),
3,[](#l8.116)
'Segmentation fault')[](#l8.117)
- @unittest.skipIf(sys.platform == 'win32',
"SIGFPE cannot be caught on Windows")[](#l8.120)
- def test_sigfpe(self):
self.check_fatal_error("""[](#l8.122)
+import faulthandler +faulthandler.enable() +faulthandler._sigfpe() +""".strip(),
3,[](#l8.127)
'Floating point exception')[](#l8.128)
- @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
"need faulthandler._sigbus()")[](#l8.131)
- def test_sigbus(self):
self.check_fatal_error("""[](#l8.133)
+import faulthandler +faulthandler.enable() +faulthandler._sigbus() +""".strip(),
3,[](#l8.138)
'Bus error')[](#l8.139)
- @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
"need faulthandler._sigill()")[](#l8.142)
- def test_sigill(self):
self.check_fatal_error("""[](#l8.144)
+import faulthandler +faulthandler.enable() +faulthandler._sigill() +""".strip(),
3,[](#l8.149)
'Illegal instruction')[](#l8.150)
+import faulthandler +faulthandler._fatal_error(b'xyz') +""".strip(),
2,[](#l8.157)
'xyz')[](#l8.158)
- @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
'need faulthandler._stack_overflow()')[](#l8.161)
- def test_stack_overflow(self):
self.check_fatal_error("""[](#l8.163)
+import faulthandler +faulthandler.enable() +faulthandler._stack_overflow() +""".strip(),
3,[](#l8.168)
'(?:Segmentation fault|Bus error)')[](#l8.169)
+import faulthandler +faulthandler.enable() +faulthandler._read_null(True) +""".strip(),
3,[](#l8.177)
'(?:Segmentation fault|Bus error)')[](#l8.178)
- def test_enable_file(self):
with temporary_filename() as filename:[](#l8.181)
self.check_fatal_error("""[](#l8.182)
+import faulthandler +output = open({filename}, 'wb') +faulthandler.enable(output) +faulthandler._read_null(True) +""".strip().format(filename=repr(filename)),
4,[](#l8.188)
'(?:Segmentation fault|Bus error)',[](#l8.189)
filename=filename)[](#l8.190)
+import faulthandler +faulthandler.enable(all_threads=True) +faulthandler._read_null(True) +""".strip(),
3,[](#l8.198)
'(?:Segmentation fault|Bus error)',[](#l8.199)
all_threads=True)[](#l8.200)
+import faulthandler +faulthandler.enable() +faulthandler.disable() +faulthandler._read_null() +""".strip()
not_expected = 'Fatal Python error'[](#l8.209)
stderr = self.get_output(code, False)[](#l8.210)
stder = '\n'.join(stderr)[](#l8.211)
self.assertTrue(not_expected not in stderr,[](#l8.212)
"%r is present in %r" % (not_expected, stderr))[](#l8.213)
- def test_is_enabled(self):
was_enabled = faulthandler.is_enabled()[](#l8.216)
try:[](#l8.217)
faulthandler.enable()[](#l8.218)
self.assertTrue(faulthandler.is_enabled())[](#l8.219)
faulthandler.disable()[](#l8.220)
self.assertFalse(faulthandler.is_enabled())[](#l8.221)
finally:[](#l8.222)
if was_enabled:[](#l8.223)
faulthandler.enable()[](#l8.224)
else:[](#l8.225)
faulthandler.disable()[](#l8.226)
- def check_dump_traceback(self, filename):
"""[](#l8.229)
Explicitly call dump_traceback() function and check its output.[](#l8.230)
Raise an error if the output doesn't match the expected format.[](#l8.231)
"""[](#l8.232)
code = """[](#l8.233)
+import faulthandler + +def funcB():
- if {has_filename}:
with open({filename}, "wb") as fp:[](#l8.238)
faulthandler.dump_traceback(fp)[](#l8.239)
- else:
faulthandler.dump_traceback()[](#l8.241)
code = code.format([](#l8.248)
filename=repr(filename),[](#l8.249)
has_filename=bool(filename),[](#l8.250)
)[](#l8.251)
if filename:[](#l8.252)
lineno = 6[](#l8.253)
else:[](#l8.254)
lineno = 8[](#l8.255)
expected = [[](#l8.256)
'Traceback (most recent call first):',[](#l8.257)
' File "<string>", line %s in funcB' % lineno,[](#l8.258)
' File "<string>", line 11 in funcA',[](#l8.259)
' File "<string>", line 13 in <module>'[](#l8.260)
][](#l8.261)
trace = self.get_output(code, True, filename)[](#l8.262)
self.assertEqual(trace, expected)[](#l8.263)
- def test_dump_traceback(self):
self.check_dump_traceback(None)[](#l8.266)
with temporary_filename() as filename:[](#l8.267)
self.check_dump_traceback(filename)[](#l8.268)
- def check_dump_traceback_threads(self, filename):
"""[](#l8.271)
Call explicitly dump_traceback(all_threads=True) and check the output.[](#l8.272)
Raise an error if the output doesn't match the expected format.[](#l8.273)
"""[](#l8.274)
code = """[](#l8.275)
+import faulthandler +from threading import Thread, Event +import time + +def dump():
- if {filename}:
with open({filename}, "wb") as fp:[](#l8.282)
faulthandler.dump_traceback(fp, all_threads=True)[](#l8.283)
- else:
faulthandler.dump_traceback(all_threads=True)[](#l8.285)
- def init(self):
Thread.__init__(self)[](#l8.292)
self.running = Event()[](#l8.293)
self.stop = Event()[](#l8.294)
+ +waiter = Waiter() +waiter.start() +waiter.running.wait() +dump() +waiter.stop.set() +waiter.join() +""".strip()
code = code.format(filename=repr(filename))[](#l8.307)
output = self.get_output(code, True, filename)[](#l8.308)
output = '\n'.join(output)[](#l8.309)
if filename:[](#l8.310)
lineno = 8[](#l8.311)
else:[](#l8.312)
lineno = 10[](#l8.313)
regex = """[](#l8.314)
+^Thread 0x[0-9a-f]+: +(?: File ".*threading.py", line [0-9]+ in wait +)? File ".*threading.py", line [0-9]+ in wait
- File "", 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 "", line {lineno} in dump
- File "", line 28 in $ +""".strip()
regex = regex.format(lineno=lineno)[](#l8.326)
self.assertRegex(output, regex)[](#l8.327)
- def test_dump_traceback_threads(self):
self.check_dump_traceback_threads(None)[](#l8.330)
with temporary_filename() as filename:[](#l8.331)
self.check_dump_traceback_threads(filename)[](#l8.332)
- def _check_dump_tracebacks_later(self, repeat, cancel, filename):
"""[](#l8.335)
Check how many times the traceback is written in timeout x 2.5 seconds,[](#l8.336)
or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending[](#l8.337)
on repeat and cancel options.[](#l8.338)
Raise an error if the output doesn't match the expect format.[](#l8.340)
"""[](#l8.341)
code = """[](#l8.342)
+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[](#l8.356)
a = time.time()[](#l8.357)
time.sleep(pause)[](#l8.358)
b = time.time()[](#l8.359)
# Check that sleep() was not interrupted[](#l8.360)
assert (b -a) >= pause[](#l8.361)
+ +timeout = 0.5 +repeat = {repeat} +cancel = {cancel} +if {has_filename}:
+faulthandler.dump_tracebacks_later(timeout,
+func(repeat, cancel, timeout) +if file is not None:
code = code.format([](#l8.376)
filename=repr(filename),[](#l8.377)
has_filename=bool(filename),[](#l8.378)
repeat=repeat,[](#l8.379)
cancel=cancel,[](#l8.380)
)[](#l8.381)
trace = self.get_output(code, True, filename)[](#l8.382)
trace = '\n'.join(trace)[](#l8.383)
if repeat:[](#l8.385)
count = 2[](#l8.386)
else:[](#l8.387)
count = 1[](#l8.388)
header = 'Thread 0x[0-9a-f]+:\n'[](#l8.389)
regex = expected_traceback(7, 30, header, count=count)[](#l8.390)
self.assertRegex(trace, '^%s$' % regex)[](#l8.391)
- @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
'need faulthandler.dump_tracebacks_later()')[](#l8.394)
- def check_dump_tracebacks_later(self, repeat=False, cancel=False,
file=False):[](#l8.396)
if file:[](#l8.397)
with temporary_filename() as filename:[](#l8.398)
self._check_dump_tracebacks_later(repeat, cancel, filename)[](#l8.399)
else:[](#l8.400)
self._check_dump_tracebacks_later(repeat, cancel, None)[](#l8.401)
- def test_dump_tracebacks_later_repeat(self):
self.check_dump_tracebacks_later(repeat=True)[](#l8.407)
- def test_dump_tracebacks_later_repeat_cancel(self):
self.check_dump_tracebacks_later(repeat=True, cancel=True)[](#l8.410)
- @unittest.skipIf(not hasattr(faulthandler, "register"),
"need faulthandler.register")[](#l8.416)
- def check_register(self, filename=False, all_threads=False):
"""[](#l8.418)
Register a handler displaying the traceback on a user signal. Raise the[](#l8.419)
signal and check the written traceback.[](#l8.420)
Raise an error if the output doesn't match the expected format.[](#l8.422)
"""[](#l8.423)
code = """[](#l8.424)
+import faulthandler +import os +import signal + +def func(signum):
+ +signum = signal.SIGUSR1 +if {has_filename}:
+faulthandler.register(signum, file=file, all_threads={all_threads}) +func(signum) +if file is not None:
code = code.format([](#l8.442)
filename=repr(filename),[](#l8.443)
has_filename=bool(filename),[](#l8.444)
all_threads=all_threads,[](#l8.445)
)[](#l8.446)
trace = self.get_output(code, True, filename)[](#l8.447)
trace = '\n'.join(trace)[](#l8.448)
if all_threads:[](#l8.449)
regex = 'Current thread XXX:\n'[](#l8.450)
else:[](#l8.451)
regex = 'Traceback \(most recent call first\):\n'[](#l8.452)
regex = expected_traceback(6, 14, regex)[](#l8.453)
self.assertTrue(re.match(regex, trace),[](#l8.454)
"[%s] doesn't match [%s]: use_filename=%s, all_threads=%s"[](#l8.455)
% (regex, trace, bool(filename), all_threads))[](#l8.456)
- def test_register_file(self):
with temporary_filename() as filename:[](#l8.462)
self.check_register(filename=filename)[](#l8.463)
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -87,6 +87,8 @@ Core and Builtins 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
--- a/Modules/Setup.dist +++ b/Modules/Setup.dist @@ -127,6 +127,9 @@ itertools itertoolsmodule.c # Functio
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
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(),[](#l11.18)
not using register() */[](#l11.19)
+# 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 {
+} fault_handler_t; + +static struct {
+} 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 {
+} 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
- {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() */[](#l11.101)
- {SIGSEGV, 0, "Segmentation fault", }
+}; +static const unsigned char faulthandler_nsignals = [](#l11.104)
+ +#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) +{
- if (file == NULL || file == Py_None) {
file = PySys_GetObject("stderr");[](#l11.128)
if (file == NULL) {[](#l11.129)
PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr");[](#l11.130)
return NULL;[](#l11.131)
}[](#l11.132)
- }
- fd = -1;
- if (PyLong_Check(result)) {
fd_long = PyLong_AsLong(result);[](#l11.141)
if (0 <= fd_long && fd_long < INT_MAX)[](#l11.142)
fd = (int)fd_long;[](#l11.143)
- }
- Py_DECREF(result);
- if (fd == -1) {
PyErr_SetString(PyExc_RuntimeError,[](#l11.148)
"file.fileno() is not a valid file descriptor");[](#l11.149)
return NULL;[](#l11.150)
- }
- result = PyObject_CallMethod(file, "flush", "");
- if (result != NULL)
Py_DECREF(result);[](#l11.155)
- else {
/* ignore flush() error */[](#l11.157)
PyErr_Clear();[](#l11.158)
- }
- *p_fd = fd;
- return file;
+} + +static PyObject* +faulthandler_dump_traceback_py(PyObject *self,
PyObject *args, PyObject *kwargs)[](#l11.166)
- 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,[](#l11.176)
&file, &all_threads))[](#l11.177)
return NULL;[](#l11.178)
- /* The caller holds the GIL and so PyThreadState_Get() can be used */
- tstate = PyThreadState_Get();
- if (tstate == NULL) {
PyErr_SetString(PyExc_RuntimeError,[](#l11.187)
"unable to get the current thread state");[](#l11.188)
return NULL;[](#l11.189)
- }
- if (all_threads) {
errmsg = _Py_DumpTracebackThreads(fd, tstate->interp, tstate);[](#l11.193)
if (errmsg != NULL) {[](#l11.194)
PyErr_SetString(PyExc_RuntimeError, errmsg);[](#l11.195)
return NULL;[](#l11.196)
}[](#l11.197)
- }
- else {
_Py_DumpTraceback(fd, tstate);[](#l11.200)
- }
- 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(
- const int fd = fatal_error.fd;
- unsigned int i;
- fault_handler_t *handler = NULL;
- PyThreadState *tstate;
- for (i=0; i < faulthandler_nsignals; i++) {
handler = &faulthandler_handlers[i];[](#l11.236)
if (handler->signum == signum)[](#l11.237)
break;[](#l11.238)
- }
- if (handler == NULL) {
/* faulthandler_nsignals == 0 (unlikely) */[](#l11.241)
return;[](#l11.242)
- }
- /* SIGSEGV, SIGFPE, SIGBUS and SIGILL are synchronous signals and so are
delivered to the thread that caused the fault. Get the Python thread[](#l11.258)
state of the current thread.[](#l11.259)
PyThreadState_Get() doesn't give the state of the thread that caused the[](#l11.261)
fault if the thread released the GIL, and so this function cannot be[](#l11.262)
used. Read the thread local storage (TLS) instead: call[](#l11.263)
PyGILState_GetThisThreadState(). */[](#l11.264)
- tstate = PyGILState_GetThisThreadState();
- if (tstate == NULL)
return;[](#l11.267)
- if (fatal_error.all_threads)
_Py_DumpTracebackThreads(fd, tstate->interp, tstate);[](#l11.270)
- else
_Py_DumpTraceback(fd, tstate);[](#l11.272)
- /* call the previous signal handler: it is called if we use sigaction()
thanks to SA_NODEFER flag, otherwise it is deferred */[](#l11.276)
- raise(signum);
- /* on Windows, don't call explictly the previous handler, because Windows
signal handler would not be called */[](#l11.280)
+#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;
- if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"|Oi:enable", kwlist, &file, &all_threads))[](#l11.301)
return NULL;[](#l11.302)
- Py_XDECREF(fatal_error.file);
- Py_INCREF(file);
- fatal_error.file = file;
- fatal_error.fd = fd;
- fatal_error.all_threads = all_threads;
for (i=0; i < faulthandler_nsignals; i++) {[](#l11.317)
handler = &faulthandler_handlers[i];[](#l11.318)
action.sa_sigaction = faulthandler_fatal_error;[](#l11.320)
sigemptyset(&action.sa_mask);[](#l11.321)
/* Do not prevent the signal from being received from within[](#l11.322)
its own signal handler */[](#l11.323)
action.sa_flags = SA_NODEFER;[](#l11.324)
if (stack.ss_sp != NULL) {[](#l11.326)
/* Call the signal handler on an alternate signal stack[](#l11.327)
provided by sigaltstack() */[](#l11.328)
action.sa_flags |= SA_ONSTACK;[](#l11.329)
}[](#l11.330)
err = sigaction(handler->signum, &action, &handler->previous);[](#l11.332)
handler->previous = signal(handler->signum,[](#l11.334)
faulthandler_fatal_error);[](#l11.335)
err = (handler->previous == SIG_ERR);[](#l11.336)
if (err) {[](#l11.338)
PyErr_SetFromErrno(PyExc_RuntimeError);[](#l11.339)
return NULL;[](#l11.340)
}[](#l11.341)
handler->enabled = 1;[](#l11.342)
}[](#l11.343)
- }
- Py_RETURN_NONE;
+} + +static void +faulthandler_disable(void) +{
- if (fatal_error.enabled) {
fatal_error.enabled = 0;[](#l11.355)
for (i=0; i < faulthandler_nsignals; i++) {[](#l11.356)
handler = &faulthandler_handlers[i];[](#l11.357)
if (!handler->enabled)[](#l11.358)
continue;[](#l11.359)
(void)sigaction(handler->signum, &handler->previous, NULL);[](#l11.361)
(void)signal(handler->signum, handler->previous);[](#l11.363)
+} + +static PyObject* +faulthandler_disable_py(PyObject *self) +{
- if (!fatal_error.enabled) {
Py_INCREF(Py_False);[](#l11.376)
return Py_False;[](#l11.377)
- }
- faulthandler_disable();
- Py_INCREF(Py_True);
- return Py_True;
+} + +static PyObject* +faulthandler_is_enabled(PyObject *self) +{
+} + +#ifdef FAULTHANDLER_LATER + +static void +faulthandler_thread(void *unused) +{
- do {
st = PyThread_acquire_lock_timed(thread.cancel_event,[](#l11.401)
thread.timeout_ms, 0);[](#l11.402)
if (st == PY_LOCK_ACQUIRED) {[](#l11.403)
/* Cancelled by user */[](#l11.404)
break;[](#l11.405)
}[](#l11.406)
/* Timeout => dump traceback */[](#l11.407)
assert(st == PY_LOCK_FAILURE);[](#l11.408)
/* get the thread holding the GIL, NULL if no thread hold the GIL */[](#l11.410)
current = _Py_atomic_load_relaxed(&_PyThreadState_Current);[](#l11.411)
errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, current);[](#l11.413)
ok = (errmsg == NULL);[](#l11.414)
- /* 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 */[](#l11.430)
PyThread_release_lock(thread.cancel_event);[](#l11.431)
/* Wait for thread to join */[](#l11.432)
PyThread_acquire_lock(thread.join_event, 1);[](#l11.433)
assert(thread.running == 0);[](#l11.434)
PyThread_release_lock(thread.join_event);[](#l11.435)
- }
- Py_CLEAR(thread.file);
+} + +static PyObject* +faulthandler_dump_traceback_later(PyObject *self,
PyObject *args, PyObject *kwargs)[](#l11.442)
- 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,[](#l11.453)
&timeout, &repeat, &file, &exit))[](#l11.454)
return NULL;[](#l11.455)
- timeout *= 1e6;
- if (timeout >= (double) PY_TIMEOUT_MAX) {
PyErr_SetString(PyExc_OverflowError, "timeout value is too large");[](#l11.458)
return NULL;[](#l11.459)
- }
- timeout_ms = (PY_TIMEOUT_T)timeout;
- if (timeout_ms <= 0) {
PyErr_SetString(PyExc_ValueError, "timeout must be greater than 0");[](#l11.463)
return NULL;[](#l11.464)
- }
- 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;[](#l11.489)
Py_CLEAR(thread.file);[](#l11.490)
PyErr_SetString(PyExc_RuntimeError,[](#l11.491)
"unable to start watchdog thread");[](#l11.492)
return NULL;[](#l11.493)
- }
+} + +static PyObject* +faulthandler_cancel_dump_traceback_later_py(PyObject *self) +{
+} +#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) +{
- /* 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)[](#l11.526)
instead: call PyGILState_GetThisThreadState(). */[](#l11.527)
- tstate = PyGILState_GetThisThreadState();
- if (tstate == NULL) {
/* unable to get the current thread, do nothing */[](#l11.530)
return;[](#l11.531)
- }
- if (user->all_threads)
_Py_DumpTracebackThreads(user->fd, tstate->interp, tstate);[](#l11.535)
- else
_Py_DumpTraceback(user->fd, tstate);[](#l11.537)
+} + +static PyObject* +faulthandler_register(PyObject *self,
PyObject *args, PyObject *kwargs)[](#l11.542)
- 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;
- if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"i|Oi:register", kwlist,[](#l11.558)
&signum, &file, &all_threads))[](#l11.559)
return NULL;[](#l11.560)
- if (signum < 1 || NSIG <= signum) {
PyErr_SetString(PyExc_ValueError, "signal number out of range");[](#l11.563)
return NULL;[](#l11.564)
- }
- for (i=0; i < faulthandler_nsignals; i++) {
if (faulthandler_handlers[i].signum == signum) {[](#l11.568)
PyErr_Format(PyExc_RuntimeError,[](#l11.569)
"signal %i cannot be registered by register(), "[](#l11.570)
"use enable() instead",[](#l11.571)
signum);[](#l11.572)
return NULL;[](#l11.573)
}[](#l11.574)
- }
- if (user_signals == NULL) {
user_signals = calloc(NSIG, sizeof(user_signal_t));[](#l11.582)
if (user_signals == NULL)[](#l11.583)
return PyErr_NoMemory();[](#l11.584)
- }
- user = &user_signals[signum];
action.sa_handler = faulthandler_user;[](#l11.590)
sigemptyset(&action.sa_mask);[](#l11.591)
/* if the signal is received while the kernel is executing a system[](#l11.592)
call, try to restart the system call instead of interrupting it and[](#l11.593)
return EINTR */[](#l11.594)
action.sa_flags = SA_RESTART;[](#l11.595)
if (stack.ss_sp != NULL) {[](#l11.597)
/* Call the signal handler on an alternate signal stack[](#l11.598)
provided by sigaltstack() */[](#l11.599)
action.sa_flags |= SA_ONSTACK;[](#l11.600)
}[](#l11.601)
err = sigaction(signum, &action, &previous);[](#l11.603)
previous = signal(signum, faulthandler_user);[](#l11.605)
err = (previous == SIG_ERR);[](#l11.606)
if (err) {[](#l11.608)
PyErr_SetFromErrno(PyExc_OSError);[](#l11.609)
return NULL;[](#l11.610)
}[](#l11.611)
- }
- Py_XDECREF(user->file);
- Py_INCREF(file);
- user->file = file;
- user->fd = fd;
- user->all_threads = all_threads;
- user->previous = previous;
- user->enabled = 1;
+} + +static int +faulthandler_unregister(user_signal_t *user, int signum) +{
+} + +static PyObject* +faulthandler_unregister_py(PyObject *self, PyObject *args) +{
- if (signum < 1 || NSIG <= signum) {
PyErr_SetString(PyExc_ValueError, "signal number out of range");[](#l11.652)
return NULL;[](#l11.653)
- }
- 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;[](#l11.669)
- if (release_gil) {
Py_BEGIN_ALLOW_THREADS[](#l11.671)
y = *x;[](#l11.672)
Py_END_ALLOW_THREADS[](#l11.673)
- } else
y = *x;[](#l11.675)
- 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[](#l11.685)
SIGSEGV was raised by the kernel because of a fault, and so if the[](#l11.686)
program retries to execute the same instruction, the fault will be[](#l11.687)
raised again.[](#l11.688)
Here the fault is simulated by a fake SIGSEGV signal raised by the[](#l11.690)
application. We have to raise SIGSEGV at lease twice: once for[](#l11.691)
faulthandler_fatal_error(), and one more time for the previous signal[](#l11.692)
handler. */[](#l11.693)
- while(1)
raise(SIGSEGV);[](#l11.695)
+} + +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. */[](#l11.706)
- 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) +{
+} +#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);[](#l11.730)
+} +#endif + +static PyObject * +faulthandler_fatal_error_py(PyObject *self, PyObject *args) +{
- char *message;
- if (!PyArg_ParseTuple(args, "y:fatal_error", &message))
return NULL;[](#l11.743)
- 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
+#endif + +#ifdef FAULTHANDLER_LATER
+#endif +#ifdef FAULTHANDLER_USER
- if (user_signals != NULL) {
for (index=0; index < NSIG; index++)[](#l11.774)
Py_VISIT(user_signals[index].file);[](#l11.775)
- }
+} + +PyDoc_STRVAR(module_doc, +"faulthandler module."); + +static PyMethodDef module_methods[] = {
- {"enable",
(PyCFunction)faulthandler_enable, METH_VARARGS|METH_KEYWORDS,[](#l11.787)
PyDoc_STR("enable(file=sys.stderr, all_threads=False): "[](#l11.788)
"enable the fault handler")},[](#l11.789)
- {"disable", (PyCFunction)faulthandler_disable_py, METH_NOARGS,
PyDoc_STR("disable(): disable the fault handler")},[](#l11.791)
- {"is_enabled", (PyCFunction)faulthandler_is_enabled, METH_NOARGS,
PyDoc_STR("is_enabled()->bool: check if the handler is enabled")},[](#l11.793)
- {"dump_traceback",
(PyCFunction)faulthandler_dump_traceback_py, METH_VARARGS|METH_KEYWORDS,[](#l11.795)
PyDoc_STR("dump_traceback(file=sys.stderr, all_threads=False): "[](#l11.796)
"dump the traceback of the current thread, or of all threads "[](#l11.797)
"if all_threads is True, into file")},[](#l11.798)
- {"dump_tracebacks_later",
(PyCFunction)faulthandler_dump_traceback_later, METH_VARARGS|METH_KEYWORDS,[](#l11.801)
PyDoc_STR("dump_tracebacks_later(timeout, repeat=False, file=sys.stderr):\n"[](#l11.802)
"dump the traceback of all threads in timeout seconds,\n"[](#l11.803)
"or each timeout seconds if repeat is True.")},[](#l11.804)
- {"cancel_dump_tracebacks_later",
(PyCFunction)faulthandler_cancel_dump_traceback_later_py, METH_NOARGS,[](#l11.806)
PyDoc_STR("cancel_dump_tracebacks_later():\ncancel the previous call "[](#l11.807)
"to dump_tracebacks_later().")},[](#l11.808)
+#endif + +#ifdef FAULTHANDLER_USER
- {"register",
(PyCFunction)faulthandler_register, METH_VARARGS|METH_KEYWORDS,[](#l11.813)
PyDoc_STR("register(signum, file=sys.stderr, all_threads=False): "[](#l11.814)
"register an handler for the signal 'signum': dump the "[](#l11.815)
"traceback of the current thread, or of all threads if "[](#l11.816)
"all_threads is True, into file")},[](#l11.817)
- {"unregister",
faulthandler_unregister_py, METH_VARARGS|METH_KEYWORDS,[](#l11.819)
PyDoc_STR("unregister(signum): unregister the handler of the signal "[](#l11.820)
"'signum' registered by register()")},[](#l11.821)
- {"_read_null", faulthandler_read_null, METH_VARARGS,
PyDoc_STR("_read_null(release_gil=False): read from NULL, raise "[](#l11.825)
"a SIGSEGV or SIGBUS signal depending on the platform")},[](#l11.826)
- {"_sigsegv", faulthandler_sigsegv, METH_VARARGS,
PyDoc_STR("_sigsegv(): raise a SIGSEGV signal")},[](#l11.828)
- {"_sigfpe", (PyCFunction)faulthandler_sigfpe, METH_NOARGS,
PyDoc_STR("_sigfpe(): raise a SIGFPE signal")},[](#l11.830)
- {"_sigbus", (PyCFunction)faulthandler_sigbus, METH_NOARGS,
PyDoc_STR("_sigbus(): raise a SIGBUS signal")},[](#l11.833)
- {"_sigill", (PyCFunction)faulthandler_sigill, METH_NOARGS,
PyDoc_STR("_sigill(): raise a SIGILL signal")},[](#l11.837)
- {"_fatal_error", faulthandler_fatal_error_py, METH_VARARGS,
PyDoc_STR("_fatal_error(message): call Py_FatalError(message)")},[](#l11.840)
+#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")},[](#l11.843)
+}; + +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) +{
+} + +/* Call faulthandler.enable() if PYTHONFAULTHANDLER environment variable is
+static int +faulthandler_env_options(void) +{
- if (!Py_GETENV("PYTHONFAULTHANDLER")) {
xoptions = PySys_GetXOptions();[](#l11.876)
if (xoptions == NULL)[](#l11.877)
return -1;[](#l11.878)
key = PyUnicode_FromString("faulthandler");[](#l11.880)
if (key == NULL)[](#l11.881)
return -1;[](#l11.882)
enable = PyDict_Contains(xoptions, key);[](#l11.884)
Py_DECREF(key);[](#l11.885)
if (!enable)[](#l11.886)
return 0;[](#l11.887)
- }
- else
enable = 1;[](#l11.890)
- module = PyImport_ImportModule("faulthandler");
- if (module == NULL) {
return -1;[](#l11.894)
- }
- res = PyObject_CallMethod(module, "enable", "");
- Py_DECREF(module);
- if (res == NULL)
return -1;[](#l11.899)
- Py_DECREF(res);
- return 0;
+} + +int _PyFaulthandler_Init(void) +{ +#ifdef HAVE_SIGALTSTACK
- /* Try to allocate an alternate stack for faulthandler() signal handler to
* be able to allocate memory on the stack, even on a stack overflow. If it[](#l11.910)
* fails, ignore the error. */[](#l11.911)
- 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);[](#l11.916)
if (err) {[](#l11.917)
PyMem_Free(stack.ss_sp);[](#l11.918)
stack.ss_sp = NULL;[](#l11.919)
}[](#l11.920)
- }
+#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,[](#l11.929)
"could not allocate locks for faulthandler");[](#l11.930)
return -1;[](#l11.931)
- }
+} + +void _PyFaulthandler_Fini(void) +{ +#ifdef FAULTHANDLER_USER
+#endif + +#ifdef FAULTHANDLER_LATER
- /* later */
- faulthandler_cancel_dump_traceback_later();
- if (thread.cancel_event) {
PyThread_free_lock(thread.cancel_event);[](#l11.948)
thread.cancel_event = NULL;[](#l11.949)
- }
- if (thread.join_event) {
PyThread_free_lock(thread.join_event);[](#l11.952)
thread.join_event = NULL;[](#l11.953)
- }
+#endif + +#ifdef FAULTHANDLER_USER
- /* user */
- if (user_signals != NULL) {
for (i=0; i < NSIG; i++)[](#l11.960)
faulthandler_unregister(&user_signals[i], i+1);[](#l11.961)
free(user_signals);[](#l11.962)
user_signals = NULL;[](#l11.963)
- }
--- a/Modules/main.c +++ b/Modules/main.c @@ -100,6 +100,7 @@ static char *usage_5 = " The default module search path uses %s.\n" "PYTHONCASEOK : ignore case in 'import' statements (Windows).\n" "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n" +"PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.\n" ; static int
--- a/PC/config.c +++ b/PC/config.c @@ -12,6 +12,7 @@ extern PyObject* PyInit_audioop(void); 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 @@ struct _inittab _PyImport_Inittab[] = { {"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 */
--- a/PCbuild/pythoncore.vcproj +++ b/PCbuild/pythoncore.vcproj @@ -1087,6 +1087,10 @@ > <File + RelativePath="..\Modules\faulthandler.c" + > + + <File RelativePath="..\Modules\gcmodule.c" >
--- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -70,6 +70,8 @@ extern void _PyUnicode_Init(void); 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 @@ Py_InitializeEx(int install_sigs) _PyImportHooks_Init();
- /* initialize the faulthandler module */
- if (_PyFaulthandler_Init())
Py_FatalError("Py_Initialize: can't initialize faulthandler");[](#l15.18)
+ /* Initialize _warnings. / _PyWarnings_Init(); @@ -454,6 +460,9 @@ Py_Finalize(void) / Destroy the database used by PyImport{Fixup,Find}Extension */ _PyImport_Fini();
+ /* Debugging stuff */ #ifdef COUNT_ALLOCS dump_counts(stdout); @@ -2100,11 +2109,23 @@ cleanup: void Py_FatalError(const char *msg) {
+ fprintf(stderr, "Fatal Python error: %s\n", msg); fflush(stderr); /* it helps in Windows debug build */ if (PyErr_Occurred()) { PyErr_PrintEx(0); }
- else {
tstate = _Py_atomic_load_relaxed(&_PyThreadState_Current);[](#l15.46)
if (tstate != NULL) {[](#l15.47)
fputc('\n', stderr);[](#l15.48)
fflush(stderr);[](#l15.49)
_Py_DumpTraceback(fd, tstate);[](#l15.50)
}[](#l15.51)
- }
+ #ifdef MS_WINDOWS { size_t len = strlen(msg);
--- 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 @@ PyTraceBack_Print(PyObject *v, PyObject err = tb_printinternal((PyTracebackObject )v, f, limit); return err; } + +/ Reverse a string. For example, "abcd" becomes "dcba". +
+static void +reverse_string(char *text, const size_t len) +{
- char tmp;
- size_t i, j;
- if (len == 0)
return;[](#l16.30)
- for (i=0, j=len-1; i < j; i++, j--) {
tmp = text[i];[](#l16.32)
text[i] = text[j];[](#l16.33)
text[j] = tmp;[](#l16.34)
- }
+} + +/* Format an integer in range [0; 999999] to decimal,
+static void +dump_decimal(int fd, int value) +{
- char buffer[7];
- int len;
- if (value < 0 || 999999 < value)
return;[](#l16.49)
- len = 0;
- do {
buffer[len] = '0' + (value % 10);[](#l16.52)
value /= 10;[](#l16.53)
len++;[](#l16.54)
- } while (value);
- reverse_string(buffer, len);
- write(fd, buffer, len);
+} + +/* Format an integer in range [0; 0xffffffff] to hexdecimal of 'width' digits,
+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];[](#l16.73)
value >>= 4;[](#l16.74)
len++;[](#l16.75)
- } while (len < width || value);
- reverse_string(buffer, len);
- write(fd, buffer, len);
+} + +/* Write an unicode object into the file fd using ascii+backslashreplace. +
+static void +dump_ascii(int fd, PyObject *text) +{
- if (MAX_STRING_LENGTH < size) {
size = MAX_STRING_LENGTH;[](#l16.97)
truncated = 1;[](#l16.98)
- }
- else
truncated = 0;[](#l16.101)
- for (i=0; i < size; i++, u++) {
if (*u < 128) {[](#l16.104)
c = (char)*u;[](#l16.105)
write(fd, &c, 1);[](#l16.106)
}[](#l16.107)
else if (*u < 256) {[](#l16.108)
PUTS(fd, "\\x");[](#l16.109)
dump_hexadecimal(2, *u, fd);[](#l16.110)
}[](#l16.111)
else[](#l16.112)
if (*u < 65536)[](#l16.114)
{[](#l16.116)
PUTS(fd, "\\u");[](#l16.117)
dump_hexadecimal(4, *u, fd);[](#l16.118)
}[](#l16.120)
else {[](#l16.121)
PUTS(fd, "\\U");[](#l16.122)
dump_hexadecimal(8, *u, fd);[](#l16.123)
+} + +/* Write a frame into the file fd: "File "xxx", line xxx in xxx". +
+static void +dump_frame(int fd, PyFrameObject *frame) +{
- code = frame->f_code;
- PUTS(fd, " File ");
- if (code != NULL && code->co_filename != NULL
&& PyUnicode_Check(code->co_filename))[](#l16.144)
- {
write(fd, "\"", 1);[](#l16.146)
dump_ascii(fd, code->co_filename);[](#l16.147)
write(fd, "\"", 1);[](#l16.148)
- } else {
PUTS(fd, "???");[](#l16.150)
- }
- /* 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))[](#l16.160)
dump_ascii(fd, code->co_name);[](#l16.161)
- else
PUTS(fd, "???");[](#l16.163)
+} + +static int +dump_traceback(int fd, PyThreadState *tstate, int write_header) +{
- if (write_header)
PUTS(fd, "Traceback (most recent call first):\n");[](#l16.179)
- depth = 0;
- while (frame != NULL) {
if (MAX_FRAME_DEPTH <= depth) {[](#l16.182)
PUTS(fd, " ...\n");[](#l16.183)
break;[](#l16.184)
}[](#l16.185)
if (!PyFrame_Check(frame))[](#l16.186)
break;[](#l16.187)
dump_frame(fd, frame);[](#l16.188)
frame = frame->f_back;[](#l16.189)
depth++;[](#l16.190)
- }
- return 0;
+} + +int +_Py_DumpTraceback(int fd, PyThreadState *tstate) +{
+} + +/* Write the thread identifier into the file 'fd': "Current thread 0xHHHH:" if
+static void +write_thread_id(int fd, PyThreadState *tstate, int is_current) +{
- if (is_current)
PUTS(fd, "Current thread 0x");[](#l16.210)
- else
PUTS(fd, "Thread 0x");[](#l16.212)
- 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)[](#l16.219)
- /* Get the current interpreter from the current thread */
- tstate = PyInterpreterState_ThreadHead(interp);
- if (tstate == NULL)
return "unable to get the thread head state";[](#l16.227)
- /* Dump the traceback of each thread */
- tstate = PyInterpreterState_ThreadHead(interp);
- nthreads = 0;
- do
- {
if (nthreads != 0)[](#l16.234)
write(fd, "\n", 1);[](#l16.235)
if (nthreads >= MAX_NTHREADS) {[](#l16.236)
PUTS(fd, "...\n");[](#l16.237)
break;[](#l16.238)
}[](#l16.239)
write_thread_id(fd, tstate, tstate == current_thread);[](#l16.240)
dump_traceback(fd, tstate, 0);[](#l16.241)
tstate = PyThreadState_Next(tstate);[](#l16.242)
nthreads++;[](#l16.243)
- } while (tstate != NULL);
--- a/configure +++ b/configure @@ -9261,7 +9261,7 @@ for ac_func in alarm accept4 setitimer g select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid [](#l17.4) setgid sethostname [](#l17.5) setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf [](#l17.6)
- sigaction siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync [](#l17.7)
- sigaction sigaltstack siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync [](#l17.8) sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r [](#l17.9) truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 [](#l17.10) wcscoll wcsftime wcsxfrm writev _getpty
--- a/configure.in +++ b/configure.in @@ -2507,7 +2507,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer g select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid [](#l18.4) setgid sethostname [](#l18.5) setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf [](#l18.6)
- sigaction siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync [](#l18.7)
- sigaction sigaltstack siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync [](#l18.8) sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r [](#l18.9) truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 [](#l18.10) wcscoll wcsftime wcsxfrm writev _getpty)
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -710,6 +710,9 @@
/* Define to 1 if you have the sigaction' function. */[](#l19.4) #undef HAVE_SIGACTION[](#l19.5) [](#l19.6) +/* Define to 1 if you have the
sigaltstack' function. /
+#undef HAVE_SIGALTSTACK
+
/ Define to 1 if you have the `siginterrupt' function. */
#undef HAVE_SIGINTERRUPT