mirror of https://github.com/python/cpython
bpo-42923: Dump extension modules on fatal error (GH-24207)
The Py_FatalError() function and the faulthandler module now dump the list of extension modules on a fatal error. Add _Py_DumpExtensionModules() and _PyModule_IsExtension() internal functions.
This commit is contained in:
parent
f7b5bacd7a
commit
250035d134
|
@ -84,6 +84,8 @@ PyAPI_FUNC(PyObject *) _PyErr_FormatFromCauseTstate(
|
|||
|
||||
PyAPI_FUNC(int) _PyErr_CheckSignalsTstate(PyThreadState *tstate);
|
||||
|
||||
PyAPI_FUNC(void) _Py_DumpExtensionModules(int fd, PyInterpreterState *interp);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -84,6 +84,12 @@ typedef struct PyModuleDef{
|
|||
freefunc m_free;
|
||||
} PyModuleDef;
|
||||
|
||||
|
||||
// Internal C API
|
||||
#ifdef Py_BUILD_CORE
|
||||
extern int _PyModule_IsExtension(PyObject *obj);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -556,6 +556,16 @@ class CAPITest(unittest.TestCase):
|
|||
self.assertIn('Fatal Python error: test_fatal_error: MESSAGE\n',
|
||||
err)
|
||||
|
||||
match = re.search('^Extension modules:(.*)$', err, re.MULTILINE)
|
||||
if not match:
|
||||
self.fail(f"Cannot find 'Extension modules:' in {err!r}")
|
||||
modules = set(match.group(1).strip().split(', '))
|
||||
# Test _PyModule_IsExtension(): the list doesn't have to
|
||||
# be exhaustive.
|
||||
for name in ('sys', 'builtins', '_imp', '_thread', '_weakref',
|
||||
'_io', 'marshal', '_signal', '_abc', '_testcapi'):
|
||||
self.assertIn(name, modules)
|
||||
|
||||
|
||||
class TestPendingCalls(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ from contextlib import contextmanager
|
|||
import datetime
|
||||
import faulthandler
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
|
@ -329,6 +330,24 @@ class FaultHandlerTests(unittest.TestCase):
|
|||
"%r is present in %r" % (not_expected, stderr))
|
||||
self.assertNotEqual(exitcode, 0)
|
||||
|
||||
@skip_segfault_on_android
|
||||
def test_dump_ext_modules(self):
|
||||
code = """
|
||||
import faulthandler
|
||||
faulthandler.enable()
|
||||
faulthandler._sigsegv()
|
||||
"""
|
||||
stderr, exitcode = self.get_output(code)
|
||||
stderr = '\n'.join(stderr)
|
||||
match = re.search('^Extension modules:(.*)$', stderr, re.MULTILINE)
|
||||
if not match:
|
||||
self.fail(f"Cannot find 'Extension modules:' in {stderr!r}")
|
||||
modules = set(match.group(1).strip().split(', '))
|
||||
# Only check for a few extensions, the list doesn't have to be
|
||||
# exhaustive.
|
||||
for ext in ('sys', 'builtins', '_io', 'faulthandler'):
|
||||
self.assertIn(ext, modules)
|
||||
|
||||
def test_is_enabled(self):
|
||||
orig_stderr = sys.stderr
|
||||
try:
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
The :c:func:`Py_FatalError` function and the :mod:`faulthandler` module now
|
||||
dump the list of extension modules on a fatal error.
|
|
@ -1,5 +1,6 @@
|
|||
#include "Python.h"
|
||||
#include "pycore_initconfig.h" // _PyStatus_ERR
|
||||
#include "pycore_pyerrors.h" // _Py_DumpExtensionModules
|
||||
#include "pycore_traceback.h" // _Py_DumpTracebackThreads
|
||||
#include <signal.h>
|
||||
#include <object.h>
|
||||
|
@ -349,6 +350,8 @@ faulthandler_fatal_error(int signum)
|
|||
faulthandler_dump_traceback(fd, fatal_error.all_threads,
|
||||
fatal_error.interp);
|
||||
|
||||
_Py_DumpExtensionModules(fd, fatal_error.interp);
|
||||
|
||||
errno = save_errno;
|
||||
#ifdef MS_WINDOWS
|
||||
if (signum == SIGSEGV) {
|
||||
|
|
|
@ -35,6 +35,19 @@ PyTypeObject PyModuleDef_Type = {
|
|||
};
|
||||
|
||||
|
||||
int
|
||||
_PyModule_IsExtension(PyObject *obj)
|
||||
{
|
||||
if (!PyModule_Check(obj)) {
|
||||
return 0;
|
||||
}
|
||||
PyModuleObject *module = (PyModuleObject*)obj;
|
||||
|
||||
struct PyModuleDef *def = module->md_def;
|
||||
return (def != NULL && def->m_methods != NULL);
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
PyModuleDef_Init(struct PyModuleDef* def)
|
||||
{
|
||||
|
|
|
@ -2496,6 +2496,45 @@ fatal_error_exit(int status)
|
|||
}
|
||||
|
||||
|
||||
// Dump the list of extension modules of sys.modules into fd file descriptor.
|
||||
// This function is called by a signal handler in faulthandler: avoid memory
|
||||
// allocations and keep the implementation simple. For example, the list
|
||||
// is not sorted on purpose.
|
||||
void
|
||||
_Py_DumpExtensionModules(int fd, PyInterpreterState *interp)
|
||||
{
|
||||
if (interp == NULL) {
|
||||
return;
|
||||
}
|
||||
PyObject *modules = interp->modules;
|
||||
if (!PyDict_Check(modules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PUTS(fd, "\nExtension modules: ");
|
||||
|
||||
Py_ssize_t pos = 0;
|
||||
PyObject *key, *value;
|
||||
int comma = 0;
|
||||
while (PyDict_Next(modules, &pos, &key, &value)) {
|
||||
if (!PyUnicode_Check(key)) {
|
||||
continue;
|
||||
}
|
||||
if (!_PyModule_IsExtension(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (comma) {
|
||||
PUTS(fd, ", ");
|
||||
}
|
||||
comma = 1;
|
||||
|
||||
_Py_DumpASCII(fd, key);
|
||||
}
|
||||
PUTS(fd, "\n");
|
||||
}
|
||||
|
||||
|
||||
static void _Py_NO_RETURN
|
||||
fatal_error(int fd, int header, const char *prefix, const char *msg,
|
||||
int status)
|
||||
|
@ -2557,6 +2596,8 @@ fatal_error(int fd, int header, const char *prefix, const char *msg,
|
|||
_Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
|
||||
}
|
||||
|
||||
_Py_DumpExtensionModules(fd, interp);
|
||||
|
||||
/* The main purpose of faulthandler is to display the traceback.
|
||||
This function already did its best to display a traceback.
|
||||
Disable faulthandler to prevent writing a second traceback
|
||||
|
|
Loading…
Reference in New Issue