Skip to content

gh-91048: Revert the memory cache removal for remote debugging #136440

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 9, 2025
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
10 changes: 10 additions & 0 deletions Modules/_remote_debugging_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -2762,6 +2766,7 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
}

exit:
_Py_RemoteDebug_ClearCache(&self->handle);
return result;
}

Expand Down Expand Up @@ -2885,9 +2890,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;
}
Expand Down Expand Up @@ -2954,9 +2961,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;
}
Expand All @@ -2982,6 +2991,7 @@ RemoteUnwinder_dealloc(PyObject *op)
}
#endif
if (self->handle.pid != 0) {
_Py_RemoteDebug_ClearCache(&self->handle);
_Py_RemoteDebug_CleanupProcHandle(&self->handle);
}
PyObject_Del(self);
Expand Down
78 changes: 78 additions & 0 deletions Python/remote_debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}

Expand All @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down
Loading