mirror of https://github.com/python/cpython
Issue #11393: Add the new faulthandler module
This commit is contained in:
parent
d85456279f
commit
024e37adcc
|
@ -10,7 +10,8 @@ allowing you to identify bottlenecks in your programs.
|
|||
.. toctree::
|
||||
|
||||
bdb.rst
|
||||
faulthandler.rst
|
||||
pdb.rst
|
||||
profile.rst
|
||||
timeit.rst
|
||||
trace.rst
|
||||
trace.rst
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
:mod:`faulthandler` --- Dump the Python traceback
|
||||
=================================================
|
||||
|
||||
.. module:: faulthandler
|
||||
:synopsis: Dump the Python traceback.
|
||||
|
||||
This module contains functions to dump the Python traceback explicitly, on a
|
||||
fault, after a timeout or on a user signal. Call :func:`faulthandler.enable` to
|
||||
install fault handlers for :const:`SIGSEGV`, :const:`SIGFPE`, :const:`SIGBUS`
|
||||
and :const:`SIGILL` signals. You can also enable them at startup by setting the
|
||||
:envvar:`PYTHONFAULTHANDLER` environment variable or by using :option:`-X`
|
||||
``faulthandler`` command line option.
|
||||
|
||||
The fault handler is compatible with system fault handlers like Apport or
|
||||
the Windows fault handler. The module uses an alternative stack for signal
|
||||
handlers, if the :c:func:`sigaltstack` function is available, to be able to
|
||||
dump the traceback even on a stack overflow.
|
||||
|
||||
The fault handler is called on catastrophic cases and so can only use
|
||||
signal-safe functions (e.g. it cannot allocate memory on the heap). That's why
|
||||
the traceback is limited: only support ASCII encoding (use the
|
||||
``backslashreplace`` error handler), limit each string to 100 characters, don't
|
||||
print the source code (only the filename, the function name and the line
|
||||
number), limit to 100 frames and 100 threads.
|
||||
|
||||
By default, the Python traceback is written to :data:`sys.stderr`. Start your
|
||||
graphical applications in a terminal and run your server in foreground to see
|
||||
the traceback, or specify a log file to :func:`faulthandler.enable()`.
|
||||
|
||||
The module is implemented in C to be able to dump a traceback on a crash or
|
||||
when Python is blocked (e.g. deadlock).
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Dump the traceback
|
||||
------------------
|
||||
|
||||
.. function:: dump_traceback(file=sys.stderr, all_threads=False)
|
||||
|
||||
Dump the traceback of the current thread, or of all threads if *all_threads*
|
||||
is ``True``, into *file*.
|
||||
|
||||
|
||||
Fault handler state
|
||||
-------------------
|
||||
|
||||
.. function:: enable(file=sys.stderr, all_threads=False)
|
||||
|
||||
Enable the fault handler: install handlers for :const:`SIGSEGV`,
|
||||
:const:`SIGFPE`, :const:`SIGBUS` and :const:`SIGILL` signals to dump the
|
||||
Python traceback. It dumps the traceback of the current thread, or all
|
||||
threads if *all_threads* is ``True``, into *file*.
|
||||
|
||||
.. function:: disable()
|
||||
|
||||
Disable the fault handler: uninstall the signal handlers installed by
|
||||
:func:`enable`.
|
||||
|
||||
.. function:: is_enabled()
|
||||
|
||||
Check if the fault handler is enabled.
|
||||
|
||||
|
||||
Dump the tracebacks after a timeout
|
||||
-----------------------------------
|
||||
|
||||
.. function:: dump_tracebacks_later(timeout, repeat=False, file=sys.stderr, exit=False)
|
||||
|
||||
Dump the tracebacks of all threads, after a timeout of *timeout* seconds, or
|
||||
each *timeout* seconds if *repeat* is ``True``. If *exit* is True, call
|
||||
:cfunc:`_exit` with status=1 after dumping the tracebacks to terminate
|
||||
immediatly the process, which is not safe. For example, :cfunc:`_exit`
|
||||
doesn't flush file buffers. If the function is called twice, the new call
|
||||
replaces previous parameters (resets the timeout). The timer has a
|
||||
sub-second resolution.
|
||||
|
||||
This function is implemented using a watchdog thread, and therefore is
|
||||
not available if Python is compiled with threads disabled.
|
||||
|
||||
.. function:: cancel_dump_traceback_later()
|
||||
|
||||
Cancel the last call to :func:`dump_traceback_later`.
|
||||
|
||||
|
||||
Dump the traceback on a user signal
|
||||
-----------------------------------
|
||||
|
||||
.. function:: register(signum, file=sys.stderr, all_threads=False)
|
||||
|
||||
Register a user signal: install a handler for the *signum* signal to dump
|
||||
the traceback of the current thread, or of all threads if *all_threads* is
|
||||
``True``, into *file*.
|
||||
|
||||
Not available on Windows.
|
||||
|
||||
.. function:: unregister(signum)
|
||||
|
||||
Unregister a user signal: uninstall the handler of the *signum* signal
|
||||
installed by :func:`register`.
|
||||
|
||||
Not available on Windows.
|
||||
|
||||
|
||||
File descriptor issue
|
||||
---------------------
|
||||
|
||||
:func:`enable`, :func:`dump_traceback_later` and :func:`register` keep the
|
||||
file descriptor of their *file* argument. If the file is closed and its file
|
||||
descriptor is reused by a new file, or if :func:`os.dup2` is used to replace
|
||||
the file descriptor, the traceback will be written into a different file. Call
|
||||
these functions again each time that the file is replaced.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Example of a segmentation fault on Linux: ::
|
||||
|
||||
$ python -q -X faulthandler
|
||||
>>> import ctypes
|
||||
>>> ctypes.string_at(0)
|
||||
Fatal Python error: Segmentation fault
|
||||
|
||||
Traceback (most recent call first):
|
||||
File "/home/python/cpython/Lib/ctypes/__init__.py", line 486 in string_at
|
||||
File "<stdin>", line 1 in <module>
|
||||
Segmentation fault
|
||||
|
|
@ -498,6 +498,13 @@ These environment variables influence Python's behavior.
|
|||
separated string, it is equivalent to specifying :option:`-W` multiple
|
||||
times.
|
||||
|
||||
.. envvar:: PYTHONFAULTHANDLER
|
||||
|
||||
If this environment variable is set, :func:`faulthandler.enable` is called
|
||||
at startup: install a handler for :const:`SIGSEGV`, :const:`SIGFPE`,
|
||||
:const:`SIGBUS` and :const:`SIGILL` signals to dump the Python traceback.
|
||||
This is equivalent to :option:`-X` ``faulthandler`` option.
|
||||
|
||||
|
||||
Debug-mode variables
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -68,6 +68,14 @@ New, Improved, and Deprecated Modules
|
|||
|
||||
* Stub
|
||||
|
||||
faulthandler
|
||||
------------
|
||||
|
||||
New module: :mod:`faulthandler`.
|
||||
|
||||
* :envvar:`PYTHONFAULTHANDLER`
|
||||
* :option:`-X` ``faulthandler``
|
||||
|
||||
os
|
||||
--
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "pystate.h"
|
||||
|
||||
struct _frame;
|
||||
|
||||
/* Traceback interface */
|
||||
|
@ -28,6 +30,44 @@ PyAPI_FUNC(int) _Py_DisplaySourceLine(PyObject *, PyObject *, int, int);
|
|||
PyAPI_DATA(PyTypeObject) PyTraceBack_Type;
|
||||
#define PyTraceBack_Check(v) (Py_TYPE(v) == &PyTraceBack_Type)
|
||||
|
||||
/* Write the Python traceback into the file 'fd'. For example:
|
||||
|
||||
Traceback (most recent call first):
|
||||
File "xxx", line xxx in <xxx>
|
||||
File "xxx", line xxx in <xxx>
|
||||
...
|
||||
File "xxx", line xxx in <xxx>
|
||||
|
||||
Return 0 on success, -1 on error.
|
||||
|
||||
This function is written for debug purpose only, to dump the traceback in
|
||||
the worst case: after a segmentation fault, at fatal error, etc. That's why,
|
||||
it is very limited. Strings are truncated to 100 characters and encoded to
|
||||
ASCII with backslashreplace. It doesn't write the source code, only the
|
||||
function name, filename and line number of each frame. Write only the first
|
||||
100 frames: if the traceback is truncated, write the line " ...".
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
PyAPI_DATA(int) _Py_DumpTraceback(
|
||||
int fd,
|
||||
PyThreadState *tstate);
|
||||
|
||||
/* Write the traceback of all threads into the file 'fd'. current_thread can be
|
||||
NULL. Return NULL on success, or an error message on error.
|
||||
|
||||
This function is written for debug purpose only. It calls
|
||||
_Py_DumpTraceback() for each thread, and so has the same limitations. It
|
||||
only write the traceback of the first 100 threads: write "..." if there are
|
||||
more threads.
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
PyAPI_DATA(const char*) _Py_DumpTracebackThreads(
|
||||
int fd, PyInterpreterState *interp,
|
||||
PyThreadState *current_thread);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -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, verbose=0, quiet=False,
|
|||
next_single_test = alltests[alltests.index(selected[0])+1]
|
||||
except IndexError:
|
||||
next_single_test = None
|
||||
selected = ['test_faulthandler']
|
||||
# Remove all the tests that precede start if it's set.
|
||||
if start:
|
||||
try:
|
||||
|
@ -1551,6 +1553,9 @@ def _make_temp_dir_for_build(TEMPDIR):
|
|||
return TEMPDIR, TESTCWD
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Display the Python traceback on segfault and division by zero
|
||||
faulthandler.enable()
|
||||
|
||||
# Remove regrtest.py's own directory from the module search path. Despite
|
||||
# the elimination of implicit relative imports, this is still needed to
|
||||
# ensure that submodules of the test package do not inappropriately appear
|
||||
|
|
|
@ -56,11 +56,12 @@ def assert_python_failure(*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.extend(args)
|
||||
return subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
**kw)
|
||||
|
||||
def kill_python(p):
|
||||
p.stdin.close()
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -1086,6 +1086,10 @@
|
|||
RelativePath="..\Modules\errnomodule.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\Modules\faulthandler.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\Modules\gcmodule.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");
|
||||
|
||||
/* Initialize _warnings. */
|
||||
_PyWarnings_Init();
|
||||
|
||||
|
@ -454,6 +460,9 @@ Py_Finalize(void)
|
|||
/* Destroy the database used by _PyImport_{Fixup,Find}Extension */
|
||||
_PyImport_Fini();
|
||||
|
||||
/* unload faulthandler module */
|
||||
_PyFaulthandler_Fini();
|
||||
|
||||
/* Debugging stuff */
|
||||
#ifdef COUNT_ALLOCS
|
||||
dump_counts(stdout);
|
||||
|
@ -2100,11 +2109,23 @@ cleanup:
|
|||
void
|
||||
Py_FatalError(const char *msg)
|
||||
{
|
||||
const int fd = fileno(stderr);
|
||||
PyThreadState *tstate;
|
||||
|
||||
fprintf(stderr, "Fatal Python error: %s\n", msg);
|
||||
fflush(stderr); /* it helps in Windows debug build */
|
||||
if (PyErr_Occurred()) {
|
||||
PyErr_PrintEx(0);
|
||||
}
|
||||
else {
|
||||
tstate = _Py_atomic_load_relaxed(&_PyThreadState_Current);
|
||||
if (tstate != NULL) {
|
||||
fputc('\n', stderr);
|
||||
fflush(stderr);
|
||||
_Py_DumpTraceback(fd, tstate);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
{
|
||||
size_t len = strlen(msg);
|
||||
|
|
|
@ -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 *f)
|
|||
err = tb_printinternal((PyTracebackObject *)v, f, limit);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Reverse a string. For example, "abcd" becomes "dcba".
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
static void
|
||||
reverse_string(char *text, const size_t len)
|
||||
{
|
||||
char tmp;
|
||||
size_t i, j;
|
||||
if (len == 0)
|
||||
return;
|
||||
for (i=0, j=len-1; i < j; i++, j--) {
|
||||
tmp = text[i];
|
||||
text[i] = text[j];
|
||||
text[j] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Format an integer in range [0; 999999] to decimal,
|
||||
and write it into the file fd.
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
static void
|
||||
dump_decimal(int fd, int value)
|
||||
{
|
||||
char buffer[7];
|
||||
int len;
|
||||
if (value < 0 || 999999 < value)
|
||||
return;
|
||||
len = 0;
|
||||
do {
|
||||
buffer[len] = '0' + (value % 10);
|
||||
value /= 10;
|
||||
len++;
|
||||
} while (value);
|
||||
reverse_string(buffer, len);
|
||||
write(fd, buffer, len);
|
||||
}
|
||||
|
||||
/* Format an integer in range [0; 0xffffffff] to hexdecimal of 'width' digits,
|
||||
and write it into the file fd.
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
static void
|
||||
dump_hexadecimal(int width, unsigned long value, int fd)
|
||||
{
|
||||
const char *hexdigits = "0123456789abcdef";
|
||||
int len;
|
||||
char buffer[sizeof(unsigned long) * 2 + 1];
|
||||
len = 0;
|
||||
do {
|
||||
buffer[len] = hexdigits[value & 15];
|
||||
value >>= 4;
|
||||
len++;
|
||||
} while (len < width || value);
|
||||
reverse_string(buffer, len);
|
||||
write(fd, buffer, len);
|
||||
}
|
||||
|
||||
/* Write an unicode object into the file fd using ascii+backslashreplace.
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
static void
|
||||
dump_ascii(int fd, PyObject *text)
|
||||
{
|
||||
Py_ssize_t i, size;
|
||||
int truncated;
|
||||
Py_UNICODE *u;
|
||||
char c;
|
||||
|
||||
size = PyUnicode_GET_SIZE(text);
|
||||
u = PyUnicode_AS_UNICODE(text);
|
||||
|
||||
if (MAX_STRING_LENGTH < size) {
|
||||
size = MAX_STRING_LENGTH;
|
||||
truncated = 1;
|
||||
}
|
||||
else
|
||||
truncated = 0;
|
||||
|
||||
for (i=0; i < size; i++, u++) {
|
||||
if (*u < 128) {
|
||||
c = (char)*u;
|
||||
write(fd, &c, 1);
|
||||
}
|
||||
else if (*u < 256) {
|
||||
PUTS(fd, "\\x");
|
||||
dump_hexadecimal(2, *u, fd);
|
||||
}
|
||||
else
|
||||
#ifdef Py_UNICODE_WIDE
|
||||
if (*u < 65536)
|
||||
#endif
|
||||
{
|
||||
PUTS(fd, "\\u");
|
||||
dump_hexadecimal(4, *u, fd);
|
||||
#ifdef Py_UNICODE_WIDE
|
||||
}
|
||||
else {
|
||||
PUTS(fd, "\\U");
|
||||
dump_hexadecimal(8, *u, fd);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (truncated)
|
||||
PUTS(fd, "...");
|
||||
}
|
||||
|
||||
/* Write a frame into the file fd: "File "xxx", line xxx in xxx".
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
static void
|
||||
dump_frame(int fd, PyFrameObject *frame)
|
||||
{
|
||||
PyCodeObject *code;
|
||||
int lineno;
|
||||
|
||||
code = frame->f_code;
|
||||
PUTS(fd, " File ");
|
||||
if (code != NULL && code->co_filename != NULL
|
||||
&& PyUnicode_Check(code->co_filename))
|
||||
{
|
||||
write(fd, "\"", 1);
|
||||
dump_ascii(fd, code->co_filename);
|
||||
write(fd, "\"", 1);
|
||||
} else {
|
||||
PUTS(fd, "???");
|
||||
}
|
||||
|
||||
/* PyFrame_GetLineNumber() was introduced in Python 2.7.0 and 3.2.0 */
|
||||
lineno = PyCode_Addr2Line(frame->f_code, frame->f_lasti);
|
||||
PUTS(fd, ", line ");
|
||||
dump_decimal(fd, lineno);
|
||||
PUTS(fd, " in ");
|
||||
|
||||
if (code != NULL && code->co_name != NULL
|
||||
&& PyUnicode_Check(code->co_name))
|
||||
dump_ascii(fd, code->co_name);
|
||||
else
|
||||
PUTS(fd, "???");
|
||||
|
||||
write(fd, "\n", 1);
|
||||
}
|
||||
|
||||
static int
|
||||
dump_traceback(int fd, PyThreadState *tstate, int write_header)
|
||||
{
|
||||
PyFrameObject *frame;
|
||||
unsigned int depth;
|
||||
|
||||
frame = _PyThreadState_GetFrame(tstate);
|
||||
if (frame == NULL)
|
||||
return -1;
|
||||
|
||||
if (write_header)
|
||||
PUTS(fd, "Traceback (most recent call first):\n");
|
||||
depth = 0;
|
||||
while (frame != NULL) {
|
||||
if (MAX_FRAME_DEPTH <= depth) {
|
||||
PUTS(fd, " ...\n");
|
||||
break;
|
||||
}
|
||||
if (!PyFrame_Check(frame))
|
||||
break;
|
||||
dump_frame(fd, frame);
|
||||
frame = frame->f_back;
|
||||
depth++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
_Py_DumpTraceback(int fd, PyThreadState *tstate)
|
||||
{
|
||||
return dump_traceback(fd, tstate, 1);
|
||||
}
|
||||
|
||||
/* Write the thread identifier into the file 'fd': "Current thread 0xHHHH:\" if
|
||||
is_current is true, "Thread 0xHHHH:\n" otherwise.
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
static void
|
||||
write_thread_id(int fd, PyThreadState *tstate, int is_current)
|
||||
{
|
||||
if (is_current)
|
||||
PUTS(fd, "Current thread 0x");
|
||||
else
|
||||
PUTS(fd, "Thread 0x");
|
||||
dump_hexadecimal(sizeof(long)*2, (unsigned long)tstate->thread_id, fd);
|
||||
PUTS(fd, ":\n");
|
||||
}
|
||||
|
||||
const char*
|
||||
_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
|
||||
PyThreadState *current_thread)
|
||||
{
|
||||
PyThreadState *tstate;
|
||||
unsigned int nthreads;
|
||||
|
||||
/* Get the current interpreter from the current thread */
|
||||
tstate = PyInterpreterState_ThreadHead(interp);
|
||||
if (tstate == NULL)
|
||||
return "unable to get the thread head state";
|
||||
|
||||
/* Dump the traceback of each thread */
|
||||
tstate = PyInterpreterState_ThreadHead(interp);
|
||||
nthreads = 0;
|
||||
do
|
||||
{
|
||||
if (nthreads != 0)
|
||||
write(fd, "\n", 1);
|
||||
if (nthreads >= MAX_NTHREADS) {
|
||||
PUTS(fd, "...\n");
|
||||
break;
|
||||
}
|
||||
write_thread_id(fd, tstate, tstate == current_thread);
|
||||
dump_traceback(fd, tstate, 0);
|
||||
tstate = PyThreadState_Next(tstate);
|
||||
nthreads++;
|
||||
} while (tstate != NULL);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 \
|
||||
setgid sethostname \
|
||||
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
|
||||
sigaction siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync \
|
||||
sigaction sigaltstack siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync \
|
||||
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
|
||||
truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
|
||||
wcscoll wcsftime wcsxfrm writev _getpty
|
||||
|
|
|
@ -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 \
|
||||
setgid sethostname \
|
||||
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
|
||||
sigaction siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync \
|
||||
sigaction sigaltstack siginterrupt sigrelse snprintf strftime strlcpy symlinkat sync \
|
||||
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
|
||||
truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
|
||||
wcscoll wcsftime wcsxfrm writev _getpty)
|
||||
|
|
|
@ -710,6 +710,9 @@
|
|||
/* Define to 1 if you have the `sigaction' function. */
|
||||
#undef HAVE_SIGACTION
|
||||
|
||||
/* Define to 1 if you have the `sigaltstack' function. */
|
||||
#undef HAVE_SIGALTSTACK
|
||||
|
||||
/* Define to 1 if you have the `siginterrupt' function. */
|
||||
#undef HAVE_SIGINTERRUPT
|
||||
|
||||
|
|
Loading…
Reference in New Issue