Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
024e37ad
Commit
024e37ad
authored
Mar 31, 2011
by
Victor Stinner
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Issue #11393: Add the new faulthandler module
parent
d8545627
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
1907 additions
and
5 deletions
+1907
-5
Doc/library/debug.rst
Doc/library/debug.rst
+2
-1
Doc/library/faulthandler.rst
Doc/library/faulthandler.rst
+129
-0
Doc/using/cmdline.rst
Doc/using/cmdline.rst
+7
-0
Doc/whatsnew/3.3.rst
Doc/whatsnew/3.3.rst
+8
-0
Include/traceback.h
Include/traceback.h
+40
-0
Lib/test/regrtest.py
Lib/test/regrtest.py
+5
-0
Lib/test/script_helper.py
Lib/test/script_helper.py
+3
-2
Lib/test/test_faulthandler.py
Lib/test/test_faulthandler.py
+469
-0
Misc/NEWS
Misc/NEWS
+2
-0
Modules/Setup.dist
Modules/Setup.dist
+3
-0
Modules/faulthandler.c
Modules/faulthandler.c
+971
-0
Modules/main.c
Modules/main.c
+1
-0
PC/config.c
PC/config.c
+2
-0
PCbuild/pythoncore.vcproj
PCbuild/pythoncore.vcproj
+4
-0
Python/pythonrun.c
Python/pythonrun.c
+21
-0
Python/traceback.c
Python/traceback.c
+235
-0
configure
configure
+1
-1
configure.in
configure.in
+1
-1
pyconfig.h.in
pyconfig.h.in
+3
-0
No files found.
Doc/library/debug.rst
View file @
024e37ad
...
...
@@ -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
\ No newline at end of file
trace.rst
Doc/library/faulthandler.rst
0 → 100644
View file @
024e37ad
: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
Doc/using/cmdline.rst
View file @
024e37ad
...
...
@@ -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
~~~~~~~~~~~~~~~~~~~~
...
...
Doc/whatsnew/3.3.rst
View file @
024e37ad
...
...
@@ -68,6 +68,14 @@ New, Improved, and Deprecated Modules
* Stub
faulthandler
------------
New module: :mod:`faulthandler`.
* :envvar:`PYTHONFAULTHANDLER`
* :option:`-X` ``faulthandler``
os
--
...
...
Include/traceback.h
View file @
024e37ad
...
...
@@ -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
...
...
Lib/test/regrtest.py
View file @
024e37ad
...
...
@@ -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
...
...
Lib/test/script_helper.py
View file @
024e37ad
...
...
@@ -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
()
...
...
Lib/test/test_faulthandler.py
0 → 100644
View file @
024e37ad
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 "\
<s
tring\
>
", line %s in func\n'
%
lineno1
regex
+=
r' File "\
<s
tring\
>
", 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
\
(mos
t
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
()
Misc/NEWS
View file @
024e37ad
...
...
@@ -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
...
...
Modules/Setup.dist
View file @
024e37ad
...
...
@@ -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
...
...
Modules/faulthandler.c
0 → 100644
View file @
024e37ad
#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():
\n
cancel 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
}
Modules/main.c
View file @
024e37ad
...
...
@@ -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
...
...
PC/config.c
View file @
024e37ad
...
...
@@ -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 */
...
...
PCbuild/pythoncore.vcproj
View file @
024e37ad
...
...
@@ -1086,6 +1086,10 @@
RelativePath=
"..\Modules\errnomodule.c"
>
</File>
<File
RelativePath=
"..\Modules\faulthandler.c"
>
</File>
<File
RelativePath=
"..\Modules\gcmodule.c"
>
...
...
Python/pythonrun.c
View file @
024e37ad
...
...
@@ -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
);
...
...
Python/traceback.c
View file @
024e37ad
...
...
@@ -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
;
}
configure
View file @
024e37ad
...
...
@@ -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 sig
altstack sig
interrupt 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
...
...
configure.in
View file @
024e37ad
...
...
@@ -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 sig
altstack sig
interrupt 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)
...
...
pyconfig.h.in
View file @
024e37ad
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment