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.
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 0d5c97d..5e72ba9 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -556,6 +556,16 @@ def test_fatal_error(self):
         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 @@
 import datetime
 import faulthandler
 import os
+import re
 import signal
 import subprocess
 import sys
@@ -329,6 +330,24 @@ def test_disable(self):
                      "%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: