Commit 404cdc5a authored by Victor Stinner's avatar Victor Stinner

faulthandler: add Windows exception handler

Issue #23848: On Windows, faulthandler.enable() now also installs an exception
handler to dump the traceback of all Python threads on any Windows exception,
not only on UNIX signals (SIGSEGV, SIGFPE, SIGABRT).
parent bd31b7c4
...@@ -68,6 +68,9 @@ Fault handler state ...@@ -68,6 +68,9 @@ Fault handler state
.. versionchanged:: 3.5 .. versionchanged:: 3.5
Added support for passing file descriptor to this function. Added support for passing file descriptor to this function.
.. versionchanged:: 3.6
On Windows, a handler for Windows exception is also installed.
.. function:: disable() .. function:: disable()
Disable the fault handler: uninstall the signal handlers installed by Disable the fault handler: uninstall the signal handlers installed by
......
...@@ -199,6 +199,14 @@ directives ``%G``, ``%u`` and ``%V``. ...@@ -199,6 +199,14 @@ directives ``%G``, ``%u`` and ``%V``.
(Contributed by Ashley Anderson in :issue:`12006`.) (Contributed by Ashley Anderson in :issue:`12006`.)
faulthandler
------------
On Windows, the :mod:`faulthandler` module now installs an handler for Windows
exceptions: see :func:`faulthandler.enable`. (Contributed by Victor Stinner in
:issue:`23848`.)
os os
-- --
......
...@@ -23,6 +23,7 @@ except ImportError: ...@@ -23,6 +23,7 @@ except ImportError:
_testcapi = None _testcapi = None
TIMEOUT = 0.5 TIMEOUT = 0.5
MS_WINDOWS = (os.name == 'nt')
def expected_traceback(lineno1, lineno2, header, min_count=1): def expected_traceback(lineno1, lineno2, header, min_count=1):
regex = header regex = header
...@@ -76,9 +77,9 @@ class FaultHandlerTests(unittest.TestCase): ...@@ -76,9 +77,9 @@ class FaultHandlerTests(unittest.TestCase):
output = output.decode('ascii', 'backslashreplace') output = output.decode('ascii', 'backslashreplace')
return output.splitlines(), exitcode return output.splitlines(), exitcode
def check_fatal_error(self, code, line_number, name_regex, def check_error(self, code, line_number, fatal_error, *,
filename=None, all_threads=True, other_regex=None, filename=None, all_threads=True, other_regex=None,
fd=None, know_current_thread=True): fd=None, know_current_thread=True):
""" """
Check that the fault handler for fatal errors is enabled and check the Check that the fault handler for fatal errors is enabled and check the
traceback from the child process output. traceback from the child process output.
...@@ -93,14 +94,14 @@ class FaultHandlerTests(unittest.TestCase): ...@@ -93,14 +94,14 @@ class FaultHandlerTests(unittest.TestCase):
else: else:
header = 'Stack' header = 'Stack'
regex = """ regex = """
^Fatal Python error: {name} ^{fatal_error}
{header} \(most recent call first\): {header} \(most recent call first\):
File "<string>", line {lineno} in <module> File "<string>", line {lineno} in <module>
""" """
regex = dedent(regex.format( regex = dedent(regex.format(
lineno=line_number, lineno=line_number,
name=name_regex, fatal_error=fatal_error,
header=header)).strip() header=header)).strip()
if other_regex: if other_regex:
regex += '|' + other_regex regex += '|' + other_regex
...@@ -109,17 +110,36 @@ class FaultHandlerTests(unittest.TestCase): ...@@ -109,17 +110,36 @@ class FaultHandlerTests(unittest.TestCase):
self.assertRegex(output, regex) self.assertRegex(output, regex)
self.assertNotEqual(exitcode, 0) self.assertNotEqual(exitcode, 0)
def check_fatal_error(self, code, line_number, name_regex, **kw):
fatal_error = 'Fatal Python error: %s' % name_regex
self.check_error(code, line_number, fatal_error, **kw)
def check_windows_exception(self, code, line_number, name_regex, **kw):
fatal_error = 'Windows exception: %s' % name_regex
self.check_error(code, line_number, fatal_error, **kw)
@unittest.skipIf(sys.platform.startswith('aix'), @unittest.skipIf(sys.platform.startswith('aix'),
"the first page of memory is a mapped read-only on AIX") "the first page of memory is a mapped read-only on AIX")
def test_read_null(self): def test_read_null(self):
self.check_fatal_error(""" if not MS_WINDOWS:
import faulthandler self.check_fatal_error("""
faulthandler.enable() import faulthandler
faulthandler._read_null() faulthandler.enable()
""", faulthandler._read_null()
3, """,
# Issue #12700: Read NULL raises SIGILL on Mac OS X Lion 3,
'(?:Segmentation fault|Bus error|Illegal instruction)') # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion
'(?:Segmentation fault'
'|Bus error'
'|Illegal instruction)')
else:
self.check_windows_exception("""
import faulthandler
faulthandler.enable()
faulthandler._read_null()
""",
3,
'access violation')
def test_sigsegv(self): def test_sigsegv(self):
self.check_fatal_error(""" self.check_fatal_error("""
...@@ -708,6 +728,22 @@ class FaultHandlerTests(unittest.TestCase): ...@@ -708,6 +728,22 @@ class FaultHandlerTests(unittest.TestCase):
with self.check_stderr_none(): with self.check_stderr_none():
faulthandler.register(signal.SIGUSR1) faulthandler.register(signal.SIGUSR1)
@unittest.skipUnless(MS_WINDOWS, 'specific to Windows')
def test_raise_exception(self):
for exc, name in (
('EXCEPTION_ACCESS_VIOLATION', 'access violation'),
('EXCEPTION_INT_DIVIDE_BY_ZERO', 'int divide by zero'),
('EXCEPTION_STACK_OVERFLOW', 'stack overflow'),
):
self.check_windows_exception(f"""
import faulthandler
faulthandler.enable()
faulthandler._raise_exception(faulthandler._{exc})
""",
3,
name)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
...@@ -232,6 +232,10 @@ Core and Builtins ...@@ -232,6 +232,10 @@ Core and Builtins
Library Library
------- -------
- Issue #23848: On Windows, faulthandler.enable() now also installs an
exception handler to dump the traceback of all Python threads on any Windows
exception, not only on UNIX signals (SIGSEGV, SIGFPE, SIGABRT).
- Issue #26530: Add C functions :c:func:`_PyTraceMalloc_Track` and - Issue #26530: Add C functions :c:func:`_PyTraceMalloc_Track` and
:c:func:`_PyTraceMalloc_Untrack` to track memory blocks using the :c:func:`_PyTraceMalloc_Untrack` to track memory blocks using the
:mod:`tracemalloc` module. Add :c:func:`_PyTraceMalloc_GetTraceback` to get :mod:`tracemalloc` module. Add :c:func:`_PyTraceMalloc_GetTraceback` to get
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment