summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Include/internal/pycore_pyerrors.h2
-rw-r--r--Include/moduleobject.h6
-rw-r--r--Lib/test/test_capi.py10
-rw-r--r--Lib/test/test_faulthandler.py19
-rw-r--r--Misc/NEWS.d/next/Library/2021-01-13-12-15-13.bpo-42923.zBiNls.rst2
-rw-r--r--Modules/faulthandler.c3
-rw-r--r--Objects/moduleobject.c13
-rw-r--r--Python/pylifecycle.c41
8 files changed, 96 insertions, 0 deletions
diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h
index 2cf1160..9dd66ae 100644
--- a/Include/internal/pycore_pyerrors.h
+++ b/Include/internal/pycore_pyerrors.h
@@ -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
diff --git a/Include/moduleobject.h b/Include/moduleobject.h
index cf9ad40..49b116c 100644
--- a/Include/moduleobject.h
+++ b/Include/moduleobject.h
@@ -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
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 0d5c97dcc..5e72ba9 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -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):
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index bc61aab..c6b763a 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -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:
diff --git a/Misc/NEWS.d/next/Library/2021-01-13-12-15-13.bpo-42923.zBiNls.rst b/Misc/NEWS.d/next/Library/2021-01-13-12-15-13.bpo-42923.zBiNls.rst
new file mode 100644
index 0000000..bb566f9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-01-13-12-15-13.bpo-42923.zBiNls.rst
@@ -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.
diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c
index fe5dbc1..da8b774 100644
--- a/Modules/faulthandler.c
+++ b/Modules/faulthandler.c
@@ -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) {
diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c
index 6590387..e57ea86 100644
--- a/Objects/moduleobject.c
+++ b/Objects/moduleobject.c
@@ -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)
{
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index c020717..ee64b0f 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -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