diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index 6b1529aa173f01c..b318e51fea490c5 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -3772,6 +3772,14 @@ def test_get_stats(self): "batched_read_misses", "batched_read_segments_requested", "batched_read_segments_completed", + "alias_hits", + "alias_misses", + "alias_remap_failures", + "alias_validation_fails", + "alias_evictions", + "alias_identity_mismatches", + "alias_disabled_at_init", + "alias_disabled_at_runtime", "batched_read_success_rate", "batched_read_segment_completion_rate", ] diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index c3dd47a5e40a675..21ba51eb2e0b3b6 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -41,7 +41,7 @@ @MODULE__PICKLE_TRUE@_pickle _pickle.c @MODULE__QUEUE_TRUE@_queue _queuemodule.c @MODULE__RANDOM_TRUE@_random _randommodule.c -@MODULE__REMOTE_DEBUGGING_TRUE@_remote_debugging _remote_debugging/module.c _remote_debugging/gc_stats.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/frame_cache.c _remote_debugging/threads.c _remote_debugging/asyncio.c _remote_debugging/binary_io_writer.c _remote_debugging/binary_io_reader.c _remote_debugging/subprocess.c _remote_debugging/interpreters.c +@MODULE__REMOTE_DEBUGGING_TRUE@_remote_debugging _remote_debugging/module.c _remote_debugging/gc_stats.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/frame_cache.c _remote_debugging/threads.c _remote_debugging/asyncio.c _remote_debugging/alias_read.c _remote_debugging/binary_io_writer.c _remote_debugging/binary_io_reader.c _remote_debugging/subprocess.c _remote_debugging/interpreters.c @MODULE__STRUCT_TRUE@_struct _struct.c # build supports subinterpreters diff --git a/Modules/_remote_debugging/_remote_debugging.h b/Modules/_remote_debugging/_remote_debugging.h index 635e6e208902af5..2b2b92b4f987693 100644 --- a/Modules/_remote_debugging/_remote_debugging.h +++ b/Modules/_remote_debugging/_remote_debugging.h @@ -262,6 +262,44 @@ typedef struct { uintptr_t frame_addr; } RemoteReadPrefetch; +#if defined(__APPLE__) && TARGET_OS_OSX +typedef enum { + ALIAS_STABLE_RUNTIME, + ALIAS_TSTATE, + ALIAS_FRAME_PAGE +} AliasReadKind; + +#define MAX_ALIAS_PAGES 256 +#define ALIAS_PROBE_MASK 0x3ff +#define ALIAS_FAILURE_WINDOW 100 +#define ALIAS_FAILURE_THRESHOLD 10 + +typedef struct { + uintptr_t remote_page_base; + mach_vm_address_t local_page_base; + mach_vm_size_t size; + mach_port_t task_port; + uint64_t target_start_tvsec; + uint64_t target_start_tvusec; + uint64_t access_seq; + int valid; +} AliasPageEntry; + +typedef struct { + AliasPageEntry pages[MAX_ALIAS_PAGES]; + uint64_t access_seq; + uint64_t target_start_tvsec; + uint64_t target_start_tvusec; + uint32_t probe_counter; + uint32_t remap_failure_index; + uint32_t remap_failure_samples; + uint32_t remap_failure_count; + unsigned char remap_failure_window[ALIAS_FAILURE_WINDOW]; + int disabled_at_init; + int disabled_at_runtime; +} AliasReadCache; +#endif + /* Statistics for profiling performance analysis */ typedef struct { uint64_t total_samples; // Total number of get_stack_trace calls @@ -280,6 +318,14 @@ typedef struct { uint64_t batched_read_misses; // Attempts that fell back or partially read uint64_t batched_read_segments_requested; // Segments requested by batched reads uint64_t batched_read_segments_completed; // Segments completed by batched reads + uint64_t alias_hits; // macOS alias-cache hits + uint64_t alias_misses; // macOS alias-cache misses + uint64_t alias_remap_failures; // macOS remap/protect failures + uint64_t alias_validation_fails; // macOS alias snapshot validation failures + uint64_t alias_evictions; // macOS alias-cache LRU evictions + uint64_t alias_identity_mismatches; // macOS target identity mismatches + uint64_t alias_disabled_at_init; // macOS aliasing disabled during init (0/1) + uint64_t alias_disabled_at_runtime; // macOS aliasing disabled at runtime (0/1) } UnwinderStats; #if defined(__GNUC__) || defined(__clang__) @@ -382,6 +428,9 @@ typedef struct { #ifdef __APPLE__ uint64_t thread_id_offset; int thread_id_offset_initialized; +# if TARGET_OS_OSX + AliasReadCache alias_cache; +# endif #endif #ifdef MS_WINDOWS PVOID win_process_buffer; @@ -648,6 +697,38 @@ extern int collect_frames_with_cache( FrameWalkContext *ctx, uint64_t thread_id); +#if defined(__APPLE__) && TARGET_OS_OSX +extern int parse_frame_object_aliased( + RemoteUnwinderObject *unwinder, + uintptr_t expected_parent, + PyObject **result, + uintptr_t address, + uintptr_t *address_of_code_object, + uintptr_t *previous_frame +); + +extern int _Py_RemoteDebug_ValidateInterpreterSnapshot( + RemoteUnwinderObject *unwinder, + const char *interp_state_buffer +); +extern int _Py_RemoteDebug_ValidateThreadStateSnapshot( + RemoteUnwinderObject *unwinder, + const char *tstate_buffer, + uintptr_t tstate_addr, + uintptr_t current_interpreter +); +extern int _Py_RemoteDebug_AliasedRead( + RemoteUnwinderObject *unwinder, + AliasReadKind kind, + uintptr_t remote_addr, + size_t len, + void *dst); +extern void _Py_RemoteDebug_AliasCacheInit(RemoteUnwinderObject *unwinder); +extern void _Py_RemoteDebug_AliasCacheClear(RemoteUnwinderObject *unwinder); +extern void _Py_RemoteDebug_AliasCacheInvalidatePage(RemoteUnwinderObject *unwinder, uintptr_t remote_addr); +extern int _Py_RemoteDebug_AliasProbe(RemoteUnwinderObject *unwinder, uintptr_t probe_addr); +#endif + /* ============================================================================ * THREAD FUNCTION DECLARATIONS * ============================================================================ */ @@ -676,6 +757,7 @@ extern int get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint6 extern PyObject* unwind_stack_for_thread( RemoteUnwinderObject *unwinder, + uintptr_t current_interpreter, uintptr_t *current_tstate, uintptr_t gil_holder_tstate, uintptr_t gc_frame, diff --git a/Modules/_remote_debugging/alias_read.c b/Modules/_remote_debugging/alias_read.c new file mode 100644 index 000000000000000..b7882214ad03111 --- /dev/null +++ b/Modules/_remote_debugging/alias_read.c @@ -0,0 +1,359 @@ +#include "_remote_debugging.h" + +#if defined(__APPLE__) && TARGET_OS_OSX + +#ifndef VM_FLAGS_RETURN_DATA_ADDR +# define VM_FLAGS_RETURN_DATA_ADDR 0 +#endif + +static int +alias_direct_read(RemoteUnwinderObject *unwinder, + uintptr_t remote_addr, + size_t len, + void *dst) +{ + return _Py_RemoteDebug_ReadRemoteMemory( + &unwinder->handle, remote_addr, len, dst); +} + +static int +read_target_identity(RemoteUnwinderObject *unwinder, + uint64_t *start_tvsec, + uint64_t *start_tvusec) +{ + struct proc_bsdinfo info; + int n = proc_pidinfo(unwinder->handle.pid, PROC_PIDTBSDINFO, 0, + &info, sizeof(info)); + if (n != (int)sizeof(info)) { + return -1; + } + if (info.pbi_start_tvsec == 0 && info.pbi_start_tvusec == 0) { + return -1; + } + *start_tvsec = (uint64_t)info.pbi_start_tvsec; + *start_tvusec = (uint64_t)info.pbi_start_tvusec; + return 0; +} + +static void +alias_deallocate_entry(AliasPageEntry *entry) +{ + if (!entry->valid) { + return; + } + (void)mach_vm_deallocate(mach_task_self(), entry->local_page_base, + entry->size); + memset(entry, 0, sizeof(*entry)); +} + +void +_Py_RemoteDebug_AliasCacheClear(RemoteUnwinderObject *unwinder) +{ + AliasReadCache *cache = &unwinder->alias_cache; + for (int i = 0; i < MAX_ALIAS_PAGES; i++) { + alias_deallocate_entry(&cache->pages[i]); + } +} + +static void +alias_disable_runtime(RemoteUnwinderObject *unwinder) +{ + AliasReadCache *cache = &unwinder->alias_cache; + cache->disabled_at_runtime = 1; + unwinder->stats.alias_disabled_at_runtime = 1; + _Py_RemoteDebug_AliasCacheClear(unwinder); +} + +void +_Py_RemoteDebug_AliasCacheInvalidatePage(RemoteUnwinderObject *unwinder, + uintptr_t remote_addr) +{ + AliasReadCache *cache = &unwinder->alias_cache; + size_t page_size = (size_t)unwinder->handle.page_size; + uintptr_t page_base = remote_addr & ~(uintptr_t)(page_size - 1); + + for (int i = 0; i < MAX_ALIAS_PAGES; i++) { + AliasPageEntry *entry = &cache->pages[i]; + if (entry->valid + && entry->remote_page_base == page_base + && entry->task_port == unwinder->handle.task + && entry->target_start_tvsec == cache->target_start_tvsec + && entry->target_start_tvusec == cache->target_start_tvusec) { + alias_deallocate_entry(entry); + } + } +} + +void +_Py_RemoteDebug_AliasCacheInit(RemoteUnwinderObject *unwinder) +{ + AliasReadCache *cache = &unwinder->alias_cache; + memset(cache, 0, sizeof(*cache)); + + uint64_t start_tvsec = 0; + uint64_t start_tvusec = 0; + uint64_t reprobe_tvsec = 0; + uint64_t reprobe_tvusec = 0; + if (read_target_identity(unwinder, &start_tvsec, &start_tvusec) < 0 + || read_target_identity(unwinder, &reprobe_tvsec, + &reprobe_tvusec) < 0 + || start_tvsec != reprobe_tvsec + || start_tvusec != reprobe_tvusec) { + cache->disabled_at_init = 1; + } + else { + cache->target_start_tvsec = start_tvsec; + cache->target_start_tvusec = start_tvusec; + } + unwinder->stats.alias_disabled_at_init = + (uint64_t)cache->disabled_at_init; +} + +static int +alias_identity_matches(RemoteUnwinderObject *unwinder) +{ + AliasReadCache *cache = &unwinder->alias_cache; + uint64_t start_tvsec = 0; + uint64_t start_tvusec = 0; + if (read_target_identity(unwinder, &start_tvsec, &start_tvusec) < 0) { + return 0; + } + return start_tvsec == cache->target_start_tvsec + && start_tvusec == cache->target_start_tvusec; +} + +static int +alias_maybe_probe_identity(RemoteUnwinderObject *unwinder) +{ + AliasReadCache *cache = &unwinder->alias_cache; + if ((++cache->probe_counter & ALIAS_PROBE_MASK) != 0) { + return 1; + } + if (alias_identity_matches(unwinder)) { + return 1; + } + STATS_INC(unwinder, alias_identity_mismatches); + alias_disable_runtime(unwinder); + return 0; +} + +static void +alias_record_remap_outcome(RemoteUnwinderObject *unwinder, int failed) +{ + AliasReadCache *cache = &unwinder->alias_cache; + unsigned char old = cache->remap_failure_window[cache->remap_failure_index]; + if (old) { + cache->remap_failure_count--; + } + cache->remap_failure_window[cache->remap_failure_index] = + (unsigned char)(failed != 0); + if (failed) { + cache->remap_failure_count++; + } + cache->remap_failure_index = + (cache->remap_failure_index + 1) % ALIAS_FAILURE_WINDOW; + if (cache->remap_failure_samples < ALIAS_FAILURE_WINDOW) { + cache->remap_failure_samples++; + } + + if (cache->remap_failure_samples == ALIAS_FAILURE_WINDOW + && cache->remap_failure_count >= ALIAS_FAILURE_THRESHOLD) { + alias_disable_runtime(unwinder); + } +} + +static AliasPageEntry * +alias_find_entry(RemoteUnwinderObject *unwinder, uintptr_t page_base) +{ + AliasReadCache *cache = &unwinder->alias_cache; + for (int i = 0; i < MAX_ALIAS_PAGES; i++) { + AliasPageEntry *entry = &cache->pages[i]; + if (entry->valid + && entry->remote_page_base == page_base + && entry->task_port == unwinder->handle.task + && entry->target_start_tvsec == cache->target_start_tvsec + && entry->target_start_tvusec == cache->target_start_tvusec) { + return entry; + } + } + return NULL; +} + +static AliasPageEntry * +alias_alloc_entry(RemoteUnwinderObject *unwinder) +{ + AliasReadCache *cache = &unwinder->alias_cache; + AliasPageEntry *oldest = NULL; + + for (int i = 0; i < MAX_ALIAS_PAGES; i++) { + AliasPageEntry *entry = &cache->pages[i]; + if (!entry->valid) { + return entry; + } + if (oldest == NULL || entry->access_seq < oldest->access_seq) { + oldest = entry; + } + } + + assert(oldest != NULL); + alias_deallocate_entry(oldest); + STATS_INC(unwinder, alias_evictions); + return oldest; +} + +static int +alias_remap_page(RemoteUnwinderObject *unwinder, + uintptr_t page_base, + AliasPageEntry **entry_out) +{ + AliasReadCache *cache = &unwinder->alias_cache; + mach_vm_size_t page_size = (mach_vm_size_t)unwinder->handle.page_size; + mach_vm_address_t local_addr = 0; + vm_prot_t cur_protection = VM_PROT_NONE; + vm_prot_t max_protection = VM_PROT_NONE; + + kern_return_t kr = mach_vm_remap( + mach_task_self(), + &local_addr, + page_size, + 0, + VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, + unwinder->handle.task, + (mach_vm_address_t)page_base, + FALSE, + &cur_protection, + &max_protection, + VM_INHERIT_NONE); + if (kr != KERN_SUCCESS) { + STATS_INC(unwinder, alias_remap_failures); + alias_record_remap_outcome(unwinder, 1); + return -1; + } + if ((cur_protection & VM_PROT_READ) == 0) { + (void)mach_vm_deallocate(mach_task_self(), local_addr, page_size); + STATS_INC(unwinder, alias_remap_failures); + alias_record_remap_outcome(unwinder, 1); + return -1; + } + + kr = mach_vm_protect(mach_task_self(), local_addr, page_size, FALSE, + VM_PROT_READ); + if (kr != KERN_SUCCESS) { + (void)mach_vm_deallocate(mach_task_self(), local_addr, page_size); + STATS_INC(unwinder, alias_remap_failures); + alias_record_remap_outcome(unwinder, 1); + return -1; + } + + AliasPageEntry *entry = alias_alloc_entry(unwinder); + memset(entry, 0, sizeof(*entry)); + entry->remote_page_base = page_base; + entry->local_page_base = local_addr; + entry->size = page_size; + entry->task_port = unwinder->handle.task; + entry->target_start_tvsec = cache->target_start_tvsec; + entry->target_start_tvusec = cache->target_start_tvusec; + entry->access_seq = ++cache->access_seq; + entry->valid = 1; + + alias_record_remap_outcome(unwinder, 0); + *entry_out = entry; + return 0; +} + +int +_Py_RemoteDebug_AliasProbe(RemoteUnwinderObject *unwinder, + uintptr_t probe_addr) +{ + AliasReadCache *cache = &unwinder->alias_cache; + if (cache->disabled_at_init || cache->disabled_at_runtime) { + return -1; + } + + size_t page_size = (size_t)unwinder->handle.page_size; + uintptr_t page_base = probe_addr & ~(uintptr_t)(page_size - 1); + mach_vm_address_t local_addr = 0; + vm_prot_t cur_protection = VM_PROT_NONE; + vm_prot_t max_protection = VM_PROT_NONE; + kern_return_t kr = mach_vm_remap( + mach_task_self(), + &local_addr, + (mach_vm_size_t)page_size, + 0, + VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, + unwinder->handle.task, + (mach_vm_address_t)page_base, + FALSE, + &cur_protection, + &max_protection, + VM_INHERIT_NONE); + if (kr != KERN_SUCCESS || (cur_protection & VM_PROT_READ) == 0) { + if (kr == KERN_SUCCESS) { + (void)mach_vm_deallocate(mach_task_self(), local_addr, + (mach_vm_size_t)page_size); + } + STATS_INC(unwinder, alias_remap_failures); + alias_record_remap_outcome(unwinder, 1); + return -1; + } + + kr = mach_vm_protect(mach_task_self(), local_addr, + (mach_vm_size_t)page_size, FALSE, VM_PROT_READ); + (void)mach_vm_deallocate(mach_task_self(), local_addr, + (mach_vm_size_t)page_size); + if (kr != KERN_SUCCESS) { + STATS_INC(unwinder, alias_remap_failures); + alias_record_remap_outcome(unwinder, 1); + return -1; + } + + alias_record_remap_outcome(unwinder, 0); + return 0; +} + +int +_Py_RemoteDebug_AliasedRead(RemoteUnwinderObject *unwinder, + AliasReadKind kind, + uintptr_t remote_addr, + size_t len, + void *dst) +{ + (void)kind; + AliasReadCache *cache = &unwinder->alias_cache; + if (len == 0) { + return 0; + } + if (cache->disabled_at_init || cache->disabled_at_runtime) { + return alias_direct_read(unwinder, remote_addr, len, dst); + } + + size_t page_size = (size_t)unwinder->handle.page_size; + uintptr_t page_base = remote_addr & ~(uintptr_t)(page_size - 1); + size_t offset = (size_t)(remote_addr - page_base); + if (offset >= page_size || len > page_size - offset) { + return alias_direct_read(unwinder, remote_addr, len, dst); + } + + AliasPageEntry *entry = alias_find_entry(unwinder, page_base); + if (entry != NULL) { + if (!alias_maybe_probe_identity(unwinder)) { + return alias_direct_read(unwinder, remote_addr, len, dst); + } + entry->access_seq = ++cache->access_seq; + memcpy(dst, (const char *)entry->local_page_base + offset, len); + STATS_INC(unwinder, alias_hits); + return 0; + } + + STATS_INC(unwinder, alias_misses); + if (alias_remap_page(unwinder, page_base, &entry) < 0) { + return alias_direct_read(unwinder, remote_addr, len, dst); + } + if (cache->disabled_at_runtime) { + return alias_direct_read(unwinder, remote_addr, len, dst); + } + memcpy(dst, (const char *)entry->local_page_base + offset, len); + return 0; +} + +#endif /* defined(__APPLE__) && TARGET_OS_OSX */ diff --git a/Modules/_remote_debugging/frames.c b/Modules/_remote_debugging/frames.c index d73cd080dc477f3..d9ec8f9b5198acd 100644 --- a/Modules/_remote_debugging/frames.c +++ b/Modules/_remote_debugging/frames.c @@ -225,6 +225,148 @@ parse_frame_buffer( return parse_code_object(unwinder, result, &code_ctx); } +#if defined(__APPLE__) && TARGET_OS_OSX +static int +remote_pointer_plausible(RemoteUnwinderObject *unwinder, uintptr_t ptr) +{ + if (ptr == 0) { + return 1; + } + if ((ptr & (uintptr_t)(sizeof(void *) - 1)) != 0) { + return 0; + } + if (ptr < (uintptr_t)unwinder->handle.page_size) { + return 0; + } +#if UINTPTR_MAX > 0xffffffffU +# if defined(__x86_64__) + if (ptr >= (1ULL << 47)) { + return 0; + } +# elif defined(__arm64__) || defined(__aarch64__) + if ((ptr >> 56) != 0) { + return 0; + } +# else + if (ptr >= (UINTPTR_MAX >> 1)) { + return 0; + } +# endif +#endif + return 1; +} + +int +_Py_RemoteDebug_ValidateInterpreterSnapshot( + RemoteUnwinderObject *unwinder, + const char *interp_state_buffer) +{ + uintptr_t threads_head = GET_MEMBER(uintptr_t, interp_state_buffer, + unwinder->debug_offsets.interpreter_state.threads_head); + uintptr_t threads_main = GET_MEMBER(uintptr_t, interp_state_buffer, + unwinder->debug_offsets.interpreter_state.threads_main); + uintptr_t next = GET_MEMBER(uintptr_t, interp_state_buffer, + unwinder->debug_offsets.interpreter_state.next); + uintptr_t gil_holder_tstate = GET_MEMBER(uintptr_t, interp_state_buffer, + unwinder->debug_offsets.interpreter_state.gil_runtime_state_holder); + uintptr_t gc_frame = GET_MEMBER(uintptr_t, interp_state_buffer, + unwinder->debug_offsets.interpreter_state.gc + + unwinder->debug_offsets.gc.frame); + + return remote_pointer_plausible(unwinder, threads_head) + && remote_pointer_plausible(unwinder, threads_main) + && remote_pointer_plausible(unwinder, next) + && remote_pointer_plausible(unwinder, gil_holder_tstate) + && remote_pointer_plausible(unwinder, gc_frame); +} + +int +_Py_RemoteDebug_ValidateThreadStateSnapshot( + RemoteUnwinderObject *unwinder, + const char *tstate_buffer, + uintptr_t tstate_addr, + uintptr_t current_interpreter) +{ + uintptr_t interp = GET_MEMBER(uintptr_t, tstate_buffer, + unwinder->debug_offsets.thread_state.interp); + if (interp != current_interpreter) { + return 0; + } + + uintptr_t current_frame = GET_MEMBER(uintptr_t, tstate_buffer, + unwinder->debug_offsets.thread_state.current_frame); + uintptr_t base_frame = GET_MEMBER(uintptr_t, tstate_buffer, + unwinder->debug_offsets.thread_state.base_frame); + uintptr_t next = GET_MEMBER(uintptr_t, tstate_buffer, + unwinder->debug_offsets.thread_state.next); + uintptr_t last_profiled_frame = GET_MEMBER(uintptr_t, tstate_buffer, + unwinder->debug_offsets.thread_state.last_profiled_frame); + + return next != tstate_addr + && remote_pointer_plausible(unwinder, current_frame) + && remote_pointer_plausible(unwinder, base_frame) + && remote_pointer_plausible(unwinder, next) + && remote_pointer_plausible(unwinder, last_profiled_frame); +} + +static int +validate_frame_snapshot( + RemoteUnwinderObject *unwinder, + const char *frame, + uintptr_t expected_parent) +{ + int owner = (unsigned char)GET_MEMBER(char, frame, + unwinder->debug_offsets.interpreter_frame.owner); + if (owner < FRAME_OWNED_BY_THREAD || owner > FRAME_OWNED_BY_INTERPRETER) { + return 0; + } + + uintptr_t executable = GET_MEMBER_NO_TAG(uintptr_t, frame, + unwinder->debug_offsets.interpreter_frame.executable); + uintptr_t previous = GET_MEMBER(uintptr_t, frame, + unwinder->debug_offsets.interpreter_frame.previous); + if (!remote_pointer_plausible(unwinder, executable) + || !remote_pointer_plausible(unwinder, previous)) { + return 0; + } + if (expected_parent != 0 && previous != expected_parent) { + return 0; + } + return 1; +} + +int +parse_frame_object_aliased( + RemoteUnwinderObject *unwinder, + uintptr_t expected_parent, + PyObject **result, + uintptr_t address, + uintptr_t *address_of_code_object, + uintptr_t *previous_frame) +{ + char frame[SIZEOF_INTERP_FRAME]; + if (_Py_RemoteDebug_AliasedRead( + unwinder, ALIAS_FRAME_PAGE, address, SIZEOF_INTERP_FRAME, + frame) < 0) { + set_exception_cause(unwinder, PyExc_RuntimeError, + "Failed to read interpreter frame"); + return -1; + } + STATS_INC(unwinder, memory_reads); + STATS_ADD(unwinder, memory_bytes_read, SIZEOF_INTERP_FRAME); + + if (!validate_frame_snapshot(unwinder, frame, expected_parent)) { + STATS_INC(unwinder, alias_validation_fails); + _Py_RemoteDebug_AliasCacheInvalidatePage(unwinder, address); + return parse_frame_object(unwinder, result, address, + address_of_code_object, previous_frame); + } + + return parse_frame_buffer(unwinder, result, frame, + address_of_code_object, previous_frame); +} +#endif + int parse_frame_object( RemoteUnwinderObject *unwinder, @@ -340,9 +482,19 @@ process_frame_chain( &address_of_code_object, &next_frame_addr); } else { +#if defined(__APPLE__) && TARGET_OS_OSX + if (unwinder->cache_frames) { + parse_result = parse_frame_object_aliased( + unwinder, 0, &frame, frame_addr, + &address_of_code_object, &next_frame_addr); + } + else +#endif + { parse_result = parse_frame_object( unwinder, &frame, frame_addr, &address_of_code_object, &next_frame_addr); + } } if (parse_result < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in chain"); @@ -531,6 +683,7 @@ try_full_cache_hit( PyObject *current_frame = NULL; uintptr_t code_object_addr = 0; uintptr_t previous_frame = 0; + uintptr_t expected_parent = entry->num_addrs >= 2 ? entry->addrs[1] : 0; int parse_result; if (ctx->prefetch.frame && ctx->prefetch.frame_addr == ctx->frame_addr) { parse_result = parse_frame_buffer(unwinder, ¤t_frame, @@ -538,8 +691,18 @@ try_full_cache_hit( &code_object_addr, &previous_frame); } else { +#if defined(__APPLE__) && TARGET_OS_OSX + if (unwinder->cache_frames) { + parse_result = parse_frame_object_aliased( + unwinder, expected_parent, ¤t_frame, ctx->frame_addr, + &code_object_addr, &previous_frame); + } + else +#endif + { parse_result = parse_frame_object(unwinder, ¤t_frame, ctx->frame_addr, &code_object_addr, &previous_frame); + } } if (parse_result < 0) { return -1; diff --git a/Modules/_remote_debugging/module.c b/Modules/_remote_debugging/module.c index 984213d18817523..d2eec9e6cfa3ba0 100644 --- a/Modules/_remote_debugging/module.c +++ b/Modules/_remote_debugging/module.c @@ -383,6 +383,9 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, set_exception_cause(self, PyExc_RuntimeError, "Failed to initialize process handle"); return -1; } +#if defined(__APPLE__) && TARGET_OS_OSX + _Py_RemoteDebug_AliasCacheInit(self); +#endif self->runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(&self->handle); if (self->runtime_start_address == 0) { @@ -425,6 +428,9 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, set_exception_cause(self, PyExc_RuntimeError, "Failed to populate initial state data"); return -1; } +#if defined(__APPLE__) && TARGET_OS_OSX + (void)_Py_RemoteDebug_AliasProbe(self, self->interpreter_addr); +#endif self->code_object_cache = _Py_hashtable_new_full( _Py_hashtable_hash_ptr, @@ -609,6 +615,30 @@ read_interp_state_and_maybe_thread_frame( { prefetch->tstate = NULL; prefetch->frame = NULL; +#if defined(__APPLE__) && TARGET_OS_OSX + if (unwinder->cache_frames) { + if (_Py_RemoteDebug_AliasedRead( + unwinder, + ALIAS_STABLE_RUNTIME, + interpreter_addr, + INTERP_STATE_BUFFER_SIZE, + interp_state_buffer) < 0) { + return -1; + } + if (!_Py_RemoteDebug_ValidateInterpreterSnapshot( + unwinder, interp_state_buffer)) { + STATS_INC(unwinder, alias_validation_fails); + _Py_RemoteDebug_AliasCacheInvalidatePage(unwinder, + interpreter_addr); + return _Py_RemoteDebug_ReadRemoteMemory( + &unwinder->handle, + interpreter_addr, + INTERP_STATE_BUFFER_SIZE, + interp_state_buffer); + } + return 0; + } +#endif if (prefetch->tstate_addr != 0) { size_t tstate_size = (size_t)unwinder->debug_offsets.thread_state.size; _Py_RemoteReadSegment segments[3] = { @@ -802,7 +832,9 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self while (current_tstate != 0) { uintptr_t prev_tstate = current_tstate; - PyObject* frame_info = unwind_stack_for_thread(self, ¤t_tstate, + PyObject* frame_info = unwind_stack_for_thread(self, + current_interpreter, + ¤t_tstate, gil_holder_tstate, gc_frame, main_thread_tstate, @@ -1113,6 +1145,17 @@ RemoteUnwinder was created with stats=True. batched reads - batched_read_segments_completed: Segments completed by batched reads + - alias_hits: macOS alias-cache hits + - alias_misses: macOS alias-cache misses + - alias_remap_failures: macOS remap/protect failures + - alias_validation_fails: macOS alias snapshot validation + failures + - alias_evictions: macOS alias-cache LRU evictions + - alias_identity_mismatches: macOS target identity mismatches + - alias_disabled_at_init: Whether aliasing was disabled + during initialization + - alias_disabled_at_runtime: Whether aliasing was disabled + at runtime - frame_cache_hit_rate: Percentage of samples that hit the cache - code_object_cache_hit_rate: Percentage of code object @@ -1168,6 +1211,14 @@ _remote_debugging_RemoteUnwinder_get_stats_impl(RemoteUnwinderObject *self) ADD_STAT(batched_read_misses); ADD_STAT(batched_read_segments_requested); ADD_STAT(batched_read_segments_completed); + ADD_STAT(alias_hits); + ADD_STAT(alias_misses); + ADD_STAT(alias_remap_failures); + ADD_STAT(alias_validation_fails); + ADD_STAT(alias_evictions); + ADD_STAT(alias_identity_mismatches); + ADD_STAT(alias_disabled_at_init); + ADD_STAT(alias_disabled_at_runtime); #undef ADD_STAT @@ -1334,6 +1385,9 @@ RemoteUnwinder_dealloc(PyObject *op) if (self->tlbc_cache) { _Py_hashtable_destroy(self->tlbc_cache); } +#endif +#if defined(__APPLE__) && TARGET_OS_OSX + _Py_RemoteDebug_AliasCacheClear(self); #endif if (self->handle.pid != 0) { _Py_RemoteDebug_ClearCache(&self->handle); diff --git a/Modules/_remote_debugging/threads.c b/Modules/_remote_debugging/threads.c index 81735e85395ac9e..64ffdfa67956970 100644 --- a/Modules/_remote_debugging/threads.c +++ b/Modules/_remote_debugging/threads.c @@ -292,6 +292,7 @@ typedef struct { static int read_thread_state_and_maybe_frame( RemoteUnwinderObject *unwinder, + uintptr_t current_interpreter, uintptr_t tstate_addr, size_t tstate_size, char *tstate_buffer, @@ -300,6 +301,29 @@ read_thread_state_and_maybe_frame( int *frame_read) { *frame_read = 0; +#if defined(__APPLE__) && TARGET_OS_OSX + if (unwinder->cache_frames) { + if (_Py_RemoteDebug_AliasedRead( + unwinder, + ALIAS_TSTATE, + tstate_addr, + tstate_size, + tstate_buffer) < 0) { + return -1; + } + if (!_Py_RemoteDebug_ValidateThreadStateSnapshot( + unwinder, tstate_buffer, tstate_addr, + current_interpreter)) { + STATS_INC(unwinder, alias_validation_fails); + _Py_RemoteDebug_AliasCacheInvalidatePage(unwinder, tstate_addr); + return _Py_RemoteDebug_ReadRemoteMemory( + &unwinder->handle, tstate_addr, tstate_size, tstate_buffer); + } + return 0; + } +#else + (void)current_interpreter; +#endif if (predicted_frame_addr != 0) { _Py_RemoteReadSegment segments[2] = { {tstate_addr, tstate_buffer, tstate_size}, @@ -327,12 +351,16 @@ read_thread_state_and_maybe_frame( PyObject* unwind_stack_for_thread( RemoteUnwinderObject *unwinder, + uintptr_t current_interpreter, uintptr_t *current_tstate, uintptr_t gil_holder_tstate, uintptr_t gc_frame, uintptr_t main_thread_tstate, const RemoteReadPrefetch *prefetch ) { +#if !defined(__APPLE__) || !TARGET_OS_OSX + (void)current_interpreter; +#endif PyObject *frame_info = NULL; PyObject *thread_id = NULL; PyObject *result = NULL; @@ -359,6 +387,7 @@ unwind_stack_for_thread( int rc = read_thread_state_and_maybe_frame( unwinder, + current_interpreter, *current_tstate, (size_t)unwinder->debug_offsets.thread_state.size, local_ts,