PyAPI_FUNC(int) _PyErr_CheckSignalsTstate(PyThreadState *tstate);
+PyAPI_FUNC(void) _Py_DumpExtensionModules(int fd, PyInterpreterState *interp);
+
#ifdef __cplusplus
}
#endif
freefunc m_free;
} PyModuleDef;
+
+// Internal C API
+#ifdef Py_BUILD_CORE
+extern int _PyModule_IsExtension(PyObject *obj);
+#endif
+
#ifdef __cplusplus
}
#endif
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):
import datetime
import faulthandler
import os
+import re
import signal
import subprocess
import sys
"%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:
--- /dev/null
+The :c:func:`Py_FatalError` function and the :mod:`faulthandler` module now
+dump the list of extension modules on a fatal error.
#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>
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) {
};
+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)
{
}
+// 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)
_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