Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Lib/profiling/sampling/stack_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix macOS runtime section lookup for remote debugging when virtual memory
region sizes differ from Mach-O segment file sizes.
69 changes: 11 additions & 58 deletions Python/remote_debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand All @@ -307,37 +306,15 @@ 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++) {
if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__TEXT") == 0) {
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*)(
Expand All @@ -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
) {
Expand All @@ -370,37 +346,15 @@ 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++) {
if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__TEXT") == 0) {
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*)(
Expand All @@ -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
) {
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
Loading