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]