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(int) _PyErr_CheckSignalsTstate(PyThreadState *tstate);
|
||||||
|
|
||||||
|
PyAPI_FUNC(void) _Py_DumpExtensionModules(int fd, PyInterpreterState *interp);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -84,6 +84,12 @@ typedef struct PyModuleDef{
|
||||||
freefunc m_free;
|
freefunc m_free;
|
||||||
} PyModuleDef;
|
} PyModuleDef;
|
||||||
|
|
||||||
|
|
||||||
|
// Internal C API
|
||||||
|
#ifdef Py_BUILD_CORE
|
||||||
|
extern int _PyModule_IsExtension(PyObject *obj);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -556,6 +556,16 @@ class CAPITest(unittest.TestCase):
|
||||||
self.assertIn('Fatal Python error: test_fatal_error: MESSAGE\n',
|
self.assertIn('Fatal Python error: test_fatal_error: MESSAGE\n',
|
||||||
err)
|
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):
|
class TestPendingCalls(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from contextlib import contextmanager
|
||||||
import datetime
|
import datetime
|
||||||
import faulthandler
|
import faulthandler
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
@ -329,6 +330,24 @@ class FaultHandlerTests(unittest.TestCase):
|
||||||
"%r is present in %r" % (not_expected, stderr))
|
"%r is present in %r" % (not_expected, stderr))
|
||||||
self.assertNotEqual(exitcode, 0)
|
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):
|
def test_is_enabled(self):
|
||||||
orig_stderr = sys.stderr
|
orig_stderr = sys.stderr
|
||||||
try:
|
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 "Python.h"
|
||||||
#include "pycore_initconfig.h" // _PyStatus_ERR
|
#include "pycore_initconfig.h" // _PyStatus_ERR
|
||||||
|
#include "pycore_pyerrors.h" // _Py_DumpExtensionModules
|
||||||
#include "pycore_traceback.h" // _Py_DumpTracebackThreads
|
#include "pycore_traceback.h" // _Py_DumpTracebackThreads
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <object.h>
|
#include <object.h>
|
||||||
|
@ -349,6 +350,8 @@ faulthandler_fatal_error(int signum)
|
||||||
faulthandler_dump_traceback(fd, fatal_error.all_threads,
|
faulthandler_dump_traceback(fd, fatal_error.all_threads,
|
||||||
fatal_error.interp);
|
fatal_error.interp);
|
||||||
|
|
||||||
|
_Py_DumpExtensionModules(fd, fatal_error.interp);
|
||||||
|
|
||||||
errno = save_errno;
|
errno = save_errno;
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
if (signum == SIGSEGV) {
|
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*
|
PyObject*
|
||||||
PyModuleDef_Init(struct PyModuleDef* def)
|
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
|
static void _Py_NO_RETURN
|
||||||
fatal_error(int fd, int header, const char *prefix, const char *msg,
|
fatal_error(int fd, int header, const char *prefix, const char *msg,
|
||||||
int status)
|
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_FatalError_DumpTracebacks(fd, interp, tss_tstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_Py_DumpExtensionModules(fd, interp);
|
||||||
|
|
||||||
/* The main purpose of faulthandler is to display the traceback.
|
/* The main purpose of faulthandler is to display the traceback.
|
||||||
This function already did its best to display a traceback.
|
This function already did its best to display a traceback.
|
||||||
Disable faulthandler to prevent writing a second traceback
|
Disable faulthandler to prevent writing a second traceback
|
||||||
|
|
Loading…
Reference in New Issue