8000 bpo-42923: Dump extension modules on fatal error (GH-24207) · python/cpython@250035d · GitHub
[go: up one dir, main page]

Skip to content

Commit 250035d

Browse files
authored
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.
1 parent f7b5bac commit 250035d

File tree

8 files changed

+96
-0
lines changed

8 files changed

+96
-0
lines changed

Include/internal/pycore_pyerrors.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ PyAPI_FUNC(PyObject *) _PyErr_FormatFromCauseTstate(
8484

8585
PyAPI_FUNC(int) _PyErr_CheckSignalsTstate(PyThreadState *tstate);
8686

87+
PyAPI_FUNC(void) _Py_DumpExtensionModules(int fd, PyInterpreterState *interp);
88+
8789
#ifdef __cplusplus
8890
}
8991
#endif

Include/moduleobject.h

Lines changed: 6 additions & 0 deletions
Ori 8000 ginal file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ typedef struct PyModuleDef{
8484
freefunc m_free;
8585
} PyModuleDef;
8686

87+
88+
// Internal C API
89+
#ifdef Py_BUILD_CORE
90+
extern int _PyModule_IsExtension(PyObject *obj);
91+
#endif
92+
8793
#ifdef __cplusplus
8894
}
8995
#endif

Lib/test/test_capi.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,16 @@ def test_fatal_error(self):
556556
self.assertIn('Fatal Python error: test_fatal_error: MESSAGE\n',
557557
err)
558558

559+
match = re.search('^Extension modules:(.*)$', err, re.MULTILINE)
560+
if not match:
561+
self.fail(f"Cannot find 'Extension modules:' in {err!r}")
562+
modules = set(match.group(1).strip().split(', '))
563+
# Test _PyModule_IsExtension(): the list doesn't have to
564+
# be exhaustive.
565+
for name in ('sys', 'builtins', '_imp', '_thread', '_weakref',
566+
'_io', 'marshal', '_signal', '_abc', '_testcapi'):
567+
self.assertIn(name, modules)
568+
559569

560570
class TestPendingCalls(unittest.TestCase):
561571

Lib/test/test_faulthandler.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import datetime
33
import faulthandler
44
import os
5+
import re
56
import signal
67
import subprocess
78
import sys
@@ -329,6 +330,24 @@ def test_disable(self):
329330
"%r is present in %r" % (not_expected, stderr))
330331
self.assertNotEqual(exitcode, 0)
331332

333+
@skip_segfault_on_android
334+
def test_dump_ext_modules(self):
335+
code = """
336+
import faulthandler
337+
faulthandler.enable()
338+
faulthandler._sigsegv()
339+
"""
340+
stderr, exitcode = self.get_output(code)
341+
stderr = '\n'.join(stderr)
342+
match = re.search('^Extension modules:(.*)$', stderr, re.MULTILINE)
343+
if not match:
344+
self.fail(f"Cannot find 'Extension modules:' in {stderr!r}")
345+
modules = set(match.group(1).strip().split(', '))
346+
# Only check for a few extensions, the list doesn't have to be
347+
# exhaustive.
348+
for ext in ('sys', 'builtins', '_io', 'faulthandler'):
349+
self.assertIn(ext, modules)
350+
332351
def test_is_enabled(self):
333352
orig_stderr = sys.stderr
334353
try:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The :c:func:`Py_FatalError` function and the :mod:`faulthandler` module now
2+
dump the list of extension modules on a fatal error.

Modules/faulthandler.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "Python.h"
22
#include "pycore_initconfig.h" // _PyStatus_ERR
3+
#include "pycore_pyerrors.h" // _Py_DumpExtensionModules
34
#include "pycore_traceback.h" // _Py_DumpTracebackThreads
45
#include <signal.h>
56
#include <object.h>
@@ -349,6 +350,8 @@ faulthandler_fatal_error(int signum)
349350
faulthandler_dump_traceback(fd, fatal_error.all_threads,
350351
fatal_error.interp);
351352

353+
_Py_DumpExtensionModules(fd, fatal_error.interp);
354+
352355
errno = save_errno;
353356
#ifdef MS_WINDOWS
354357
if (signum == SIGSEGV) {

Objects/moduleobject.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ PyTypeObject PyModuleDef_Type = {
3535
};
3636

3737

38+
int
39+
_PyModule_IsExtension(PyObject *obj)
40+
{
41+
if (!PyModule_Check(obj)) {
42+
return 0;
43+
}
44+
PyModuleObject *module = (PyModuleObject*)obj;
45+
46+
struct PyModuleDef *def = module->md_def;
47+
return (def != NULL && def->m_methods != NULL);
48+
}
49+
50+
3851
PyObject*
3952
PyModuleDef_Init(struct PyModuleDef* def)
4053
{

Python/pylifecycle.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,6 +2496,45 @@ fatal_error_exit(int status)
24962496
}
24972497

24982498

2499+
// Dump the list of extension modules of sys.modules into fd file descriptor.
2500+
// This function is called by a signal handler in faulthandler: avoid memory
2501+
// allocations and keep the implementation simple. For example, the list
2502+
// is not sorted on purpose.
2503+
void
2504+
_Py_DumpExtensionModules(int fd, PyInterpreterState *interp)
2505+
{
2506+
if (interp == NULL) {
2507+
return;
2508+
}
2509+
PyObject *modules = interp->modules;
2510+
if (!PyDict_Check(modules)) {
2511+
return;
2512+
}
2513+
2514+
PUTS(fd, "\nExtension modules: ");
2515+
2516+
Py_ssize_t pos = 0;
2517+
PyObject *key, *value;
2518+
int comma = 0;
2519+
while (PyDict_Next(modules, &pos, &key, &value)) {
2520+
if (!PyUnicode_Check(key)) {
2521+
continue;
2522+
}
2523+
if (!_PyModule_IsExtension(value)) {
2524+
continue;
2525+
}
2526+
2527+
if (comma) {
2528+
PUTS(fd, ", ");
2529+
}
2530+
comma = 1;
2531+
2532+
_Py_DumpASCII(fd, key);
2533+
}
2534+
PUTS(fd, "\n");
2535+
}
2536+
2537+
24992538
static void _Py_NO_RETURN
25002539
fatal_error(int fd, int header, const char *prefix, const char *msg,
25012540
int status)
@@ -2557,6 +2596,8 @@ fatal_error(int fd, int header, const char *prefix, const char *msg,
25572596
_Py_FatalError_DumpTracebacks(fd, interp, tss_tstate);
25582597
}
25592598

2599+
_Py_DumpExtensionModules(fd, interp);
2600+
25602601
/* The main purpose of faulthandler is to display the traceback.
25612602
This function already did its best to display a traceback.
25622603
Disable faulthandler to prevent writing a second traceback

0 commit comments

Comments
 (0)
0