From 8847d02c027d09bec000001ecf6796a92fd13963 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 9 Jul 2025 00:50:14 +0100 Subject: [PATCH 1/2] gh-91048: Reintroduce the memory cache for remote debugging --- Modules/_remote_debugging_module.c | 6 +++ Python/remote_debug.h | 78 ++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c index ce7189637c2d69..0f9d618d9c5610 100644 --- a/Modules/_remote_debugging_module.c +++ b/Modules/_remote_debugging_module.c @@ -2762,6 +2762,7 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self } exit: + _Py_RemoteDebug_ClearCache(&self->handle); return result; } @@ -2885,9 +2886,11 @@ _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *s goto result_err; } + _Py_RemoteDebug_ClearCache(&self->handle); return result; result_err: + _Py_RemoteDebug_ClearCache(&self->handle); Py_XDECREF(result); return NULL; } @@ -2954,9 +2957,11 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject goto cleanup; } + _Py_RemoteDebug_ClearCache(&self->handle); return result; cleanup: + _Py_RemoteDebug_ClearCache(&self->handle); Py_XDECREF(result); return NULL; } @@ -2982,6 +2987,7 @@ RemoteUnwinder_dealloc(PyObject *op) } #endif if (self->handle.pid != 0) { + _Py_RemoteDebug_ClearCache(&self->handle); _Py_RemoteDebug_CleanupProcHandle(&self->handle); } PyObject_Del(self); diff --git a/Python/remote_debug.h b/Python/remote_debug.h index d1fcb478d2b035..8f9b6cd4c4960f 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -110,6 +110,14 @@ get_page_size(void) { return page_size; } +typedef struct page_cache_entry { + uintptr_t page_addr; // page-aligned base address + char *data; + int valid; + struct page_cache_entry *next; +} page_cache_entry_t; + +#define MAX_PAGES 1024 // Define a platform-independent process handle structure typedef struct { @@ -121,9 +129,27 @@ typedef struct { #elif defined(__linux__) int memfd; #endif + page_cache_entry_t pages[MAX_PAGES]; Py_ssize_t page_size; } proc_handle_t; +static void +_Py_RemoteDebug_FreePageCache(proc_handle_t *handle) +{ + for (int i = 0; i < MAX_PAGES; i++) { + PyMem_RawFree(handle->pages[i].data); + handle->pages[i].data = NULL; + handle->pages[i].valid = 0; + } +} + +UNUSED static void +_Py_RemoteDebug_ClearCache(proc_handle_t *handle) +{ + for (int i = 0; i < MAX_PAGES; i++) { + handle->pages[i].valid = 0; + } +} #if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX static mach_port_t pid_to_task(pid_t pid); @@ -152,6 +178,10 @@ _Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, pid_t pid) { handle->memfd = -1; #endif handle->page_size = get_page_size(); + for (int i = 0; i < MAX_PAGES; i++) { + handle->pages[i].data = NULL; + handle->pages[i].valid = 0; + } return 0; } @@ -170,6 +200,7 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) { } #endif handle->pid = 0; + _Py_RemoteDebug_FreePageCache(handle); } #if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX @@ -1035,6 +1066,53 @@ _Py_RemoteDebug_PagedReadRemoteMemory(proc_handle_t *handle, size_t size, void *out) { + size_t page_size = handle->page_size; + uintptr_t page_base = addr & ~(page_size - 1); + size_t offset_in_page = addr - page_base; + + if (offset_in_page + size > page_size) { + return _Py_RemoteDebug_ReadRemoteMemory(handle, addr, size, out); + } + + // Search for valid cached page + for (int i = 0; i < MAX_PAGES; i++) { + page_cache_entry_t *entry = &handle->pages[i]; + if (entry->valid && entry->page_addr == page_base) { + memcpy(out, entry->data + offset_in_page, size); + return 0; + } + } + + // Find reusable slot + for (int i = 0; i < MAX_PAGES; i++) { + page_cache_entry_t *entry = &handle->pages[i]; + if (!entry->valid) { + if (entry->data == NULL) { + entry->data = PyMem_RawMalloc(page_size); + if (entry->data == NULL) { + _set_debug_exception_cause(PyExc_MemoryError, + "Cannot allocate %zu bytes for page cache entry " + "during read from PID %d at address 0x%lx", + page_size, handle->pid, addr); + return -1; + } + } + + if (_Py_RemoteDebug_ReadRemoteMemory(handle, page_base, page_size, entry->data) < 0) { + // Try to just copy the exact ammount as a fallback + PyErr_Clear(); + goto fallback; + } + + entry->page_addr = page_base; + entry->valid = 1; + memcpy(out, entry->data + offset_in_page, size); + return 0; + } + } + +fallback: + // Cache full — fallback to uncached read return _Py_RemoteDebug_ReadRemoteMemory(handle, addr, size, out); } From b65bbf4933507a0fcf52073f20403ec9be9f66fb Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 9 Jul 2025 01:05:22 +0100 Subject: [PATCH 2/2] Fix crash when reading NULL name --- Modules/_remote_debugging_module.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c index 0f9d618d9c5610..b2d4d1ae650814 100644 --- a/Modules/_remote_debugging_module.c +++ b/Modules/_remote_debugging_module.c @@ -945,6 +945,10 @@ parse_coro_chain( return -1; } + if (name == NULL) { + return 0; + } + if (PyList_Append(render_to, name)) { Py_DECREF(name); set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to coro chain");