https://github.com/python/cpython/commit/114de198c0a05d8169cdbbd8f1e0976dc1319284
commit: 114de198c0a05d8169cdbbd8f1e0976dc1319284
branch: main
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-10-05T20:56:24+01:00
summary:

gh-156545, gh-158539: Fix deep flamegraph export on small C stacks and macOS 
runtime lookup (#158874)

files:
A Misc/NEWS.d/next/Library/2026-10-05-18-00-00.gh-issue-156545.z1mQp7.rst
A Misc/NEWS.d/next/Library/2026-10-05-18-00-01.gh-issue-158539.k8Lv2a.rst
M Lib/profiling/sampling/stack_collector.py
M Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
M Python/remote_debug.h

diff --git a/Lib/profiling/sampling/stack_collector.py 
b/Lib/profiling/sampling/stack_collector.py
index 72a8cae8807890..60061b4a2ecf11 100644
--- a/Lib/profiling/sampling/stack_collector.py
+++ b/Lib/profiling/sampling/stack_collector.py
@@ -68,8 +68,8 @@ def export(self, filename):
         return True
 
 
-# Bounded by the unwinder's maximum captured stack depth (MAX_FRAMES).
-_FLAMEGRAPH_RECURSION_MARGIN = 2000
+# Allow for tree conversion and the dict/list frames in the Python JSON 
encoder.
+_FLAMEGRAPH_RECURSION_MARGIN = 6000
 
 
 class FlamegraphCollector(StackTraceCollector):
@@ -498,7 +498,12 @@ def _get_source_lines(self, func):
             return None
 
     def _create_flamegraph_html(self, data):
-        data_json = json.dumps(data)
+        try:
+            data_json = json.dumps(data)
+        except RecursionError:
+            # The C encoder can exhaust the C stack independently of the
+            # Python recursion limit. iterencode() uses the Python encoder.
+            data_json = "".join(json.JSONEncoder().iterencode(data))
 
         template_dir = importlib.resources.files(__package__)
         vendor_dir = template_dir / "_vendor"
diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py 
b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
index 36713ff1314ff9..d4ba2df5044559 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
@@ -8,7 +8,7 @@
 import tempfile
 import unittest
 
-from test.support import is_emscripten, set_recursion_limit
+from test.support import is_emscripten, run_with_limited_c_stack, 
set_recursion_limit
 
 try:
     import _remote_debugging  # noqa: F401
@@ -611,6 +611,7 @@ def test_flamegraph_collector_empty_export_fails(self):
         self.assertFalse(export_ok)
         self.assertEqual(os.path.getsize(flamegraph_out.name), 0)
 
+    @run_with_limited_c_stack(size=1024 * 1024)
     def test_flamegraph_deep_stack_export(self):
         flamegraph_out = tempfile.NamedTemporaryFile(
             suffix=".html", delete=False
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-05-18-00-00.gh-issue-156545.z1mQp7.rst 
b/Misc/NEWS.d/next/Library/2026-10-05-18-00-00.gh-issue-156545.z1mQp7.rst
new file mode 100644
index 00000000000000..dd67abce09da80
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-05-18-00-00.gh-issue-156545.z1mQp7.rst
@@ -0,0 +1,2 @@
+Fix deep flamegraph exports on platforms with small C stacks by falling back
+to the Python JSON encoder when the C encoder runs out of stack space.
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-05-18-00-01.gh-issue-158539.k8Lv2a.rst 
b/Misc/NEWS.d/next/Library/2026-10-05-18-00-01.gh-issue-158539.k8Lv2a.rst
new file mode 100644
index 00000000000000..aadac290075abf
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-05-18-00-01.gh-issue-158539.k8Lv2a.rst
@@ -0,0 +1,2 @@
+Fix macOS runtime section lookup for remote debugging when virtual memory
+region sizes differ from Mach-O segment file sizes.
diff --git a/Python/remote_debug.h b/Python/remote_debug.h
index a279cd9dfd0305..a4a3a010926a7b 100644
--- a/Python/remote_debug.h
+++ b/Python/remote_debug.h
@@ -297,7 +297,6 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) {
 static uintptr_t
 return_section_address64(
     const char* section,
-    mach_port_t proc_ref,
     uintptr_t base,
     void* map
 ) {
@@ -307,11 +306,6 @@ return_section_address64(
     int cmd_cnt = 0;
     struct segment_command_64* cmd = map + sizeof(struct mach_header_64);
 
-    mach_vm_size_t size = 0;
-    mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
-    mach_vm_address_t address = (mach_vm_address_t)base;
-    vm_region_basic_info_data_64_t r_info;
-    mach_port_t object_name;
     uintptr_t vmaddr = 0;
 
     for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
@@ -319,25 +313,8 @@ return_section_address64(
             vmaddr = cmd->vmaddr;
         }
         if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__DATA") == 0) {
-            while (cmd->filesize != size) {
-                address += size;
-                kern_return_t ret = mach_vm_region(
-                    proc_ref,
-                    &address,
-                    &size,
-                    VM_REGION_BASIC_INFO_64,
-                    (vm_region_info_t)&r_info,  // cppcheck-suppress 
[uninitvar]
-                    &count,
-                    &object_name
-                );
-                if (ret != KERN_SUCCESS) {
-                    PyErr_Format(PyExc_RuntimeError,
-                        "mach_vm_region failed while parsing 64-bit Mach-O 
binary "
-                        "at base address 0x%lx (kern_return_t: %d)",
-                        base, ret);
-                    return 0;
-                }
-            }
+            // The section address only needs the image's ASLR slide.
+            // VM regions need not match the segment's on-disk size.
 
             int nsects = cmd->nsects;
             struct section_64* sec = (struct section_64*)(
@@ -360,7 +337,6 @@ return_section_address64(
 static uintptr_t
 return_section_address32(
     const char* section,
-    mach_port_t proc_ref,
     uintptr_t base,
     void* map
 ) {
@@ -370,11 +346,6 @@ return_section_address32(
     int cmd_cnt = 0;
     struct segment_command* cmd = map + sizeof(struct mach_header);
 
-    mach_vm_size_t size = 0;
-    mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_t);
-    mach_vm_address_t address = (mach_vm_address_t)base;
-    vm_region_basic_info_data_t r_info;
-    mach_port_t object_name;
     uintptr_t vmaddr = 0;
 
     for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
@@ -382,25 +353,8 @@ return_section_address32(
             vmaddr = cmd->vmaddr;
         }
         if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__DATA") == 0) {
-            while (cmd->filesize != size) {
-                address += size;
-                kern_return_t ret = mach_vm_region(
-                    proc_ref,
-                    &address,
-                    &size,
-                    VM_REGION_BASIC_INFO,
-                    (vm_region_info_t)&r_info,  // cppcheck-suppress 
[uninitvar]
-                    &count,
-                    &object_name
-                );
-                if (ret != KERN_SUCCESS) {
-                    PyErr_Format(PyExc_RuntimeError,
-                        "mach_vm_region failed while parsing 32-bit Mach-O 
binary "
-                        "at base address 0x%lx (kern_return_t: %d)",
-                        base, ret);
-                    return 0;
-                }
-            }
+            // The section address only needs the image's ASLR slide.
+            // VM regions need not match the segment's on-disk size.
 
             int nsects = cmd->nsects;
             struct section* sec = (struct section*)(
@@ -423,7 +377,6 @@ return_section_address32(
 static uintptr_t
 return_section_address_fat(
     const char* section,
-    mach_port_t proc_ref,
     uintptr_t base,
     void* map
 ) {
@@ -473,11 +426,11 @@ return_section_address_fat(
             switch (hdr->magic) {
                 case MH_MAGIC:
                 case MH_CIGAM:
-                    return return_section_address32(section, proc_ref, base, 
(void*)hdr);
+                    return return_section_address32(section, base, (void*)hdr);
 
                 case MH_MAGIC_64:
                 case MH_CIGAM_64:
-                    return return_section_address64(section, proc_ref, base, 
(void*)hdr);
+                    return return_section_address64(section, base, (void*)hdr);
 
                 default:
                     PyErr_Format(PyExc_RuntimeError,
@@ -496,7 +449,7 @@ return_section_address_fat(
 }
 
 static uintptr_t
-search_section_in_file(const char* secname, char* path, uintptr_t base, 
mach_vm_size_t size, mach_port_t proc_ref)
+search_section_in_file(const char* secname, char* path, uintptr_t base)
 {
     int fd = open(path, O_RDONLY);
     if (fd == -1) {
@@ -533,15 +486,15 @@ search_section_in_file(const char* secname, char* path, 
uintptr_t base, mach_vm_
     switch (magic) {
     case MH_MAGIC:
     case MH_CIGAM:
-        result = return_section_address32(secname, proc_ref, base, map);
+        result = return_section_address32(secname, base, map);
         break;
     case MH_MAGIC_64:
     case MH_CIGAM_64:
-        result = return_section_address64(secname, proc_ref, base, map);
+        result = return_section_address64(secname, base, map);
         break;
     case FAT_MAGIC:
     case FAT_CIGAM:
-        result = return_section_address_fat(secname, proc_ref, base, map);
+        result = return_section_address_fat(secname, base, map);
         break;
     default:
         PyErr_Format(PyExc_RuntimeError,
@@ -644,7 +597,7 @@ search_map_for_section(proc_handle_t *handle, const char* 
secname, const char* s
         if (strncmp(filename, substr, strlen(substr)) == 0) {
             PyErr_Clear();
             uintptr_t result = search_section_in_file(
-                secname, map_filename, address, size, proc_ref);
+                secname, map_filename, address);
             if (result != 0) {
                 if (validator == NULL || validator(handle, result)) {
                     return result;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to