Skip to content

Commit 8847d02

Browse files
committed
gh-91048: Reintroduce the memory cache for remote debugging
1 parent da79ac9 commit 8847d02

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

Modules/_remote_debugging_module.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2762,6 +2762,7 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
27622762
}
27632763

27642764
exit:
2765+
_Py_RemoteDebug_ClearCache(&self->handle);
27652766
return result;
27662767
}
27672768

@@ -2885,9 +2886,11 @@ _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *s
28852886
goto result_err;
28862887
}
28872888

2889+
_Py_RemoteDebug_ClearCache(&self->handle);
28882890
return result;
28892891

28902892
result_err:
2893+
_Py_RemoteDebug_ClearCache(&self->handle);
28912894
Py_XDECREF(result);
28922895
return NULL;
28932896
}
@@ -2954,9 +2957,11 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject
29542957
goto cleanup;
29552958
}
29562959

2960+
_Py_RemoteDebug_ClearCache(&self->handle);
29572961
return result;
29582962

29592963
cleanup:
2964+
_Py_RemoteDebug_ClearCache(&self->handle);
29602965
Py_XDECREF(result);
29612966
return NULL;
29622967
}
@@ -2982,6 +2987,7 @@ RemoteUnwinder_dealloc(PyObject *op)
29822987
}
29832988
#endif
29842989
if (self->handle.pid != 0) {
2990+
_Py_RemoteDebug_ClearCache(&self->handle);
29852991
_Py_RemoteDebug_CleanupProcHandle(&self->handle);
29862992
}
29872993
PyObject_Del(self);

Python/remote_debug.h

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ get_page_size(void) {
110110
return page_size;
111111
}
112112

113+
typedef struct page_cache_entry {
114+
uintptr_t page_addr; // page-aligned base address
115+
char *data;
116+
int valid;
117+
struct page_cache_entry *next;
118+
} page_cache_entry_t;
119+
120+
#define MAX_PAGES 1024
113121

114122
// Define a platform-independent process handle structure
115123
typedef struct {
@@ -121,9 +129,27 @@ typedef struct {
121129
#elif defined(__linux__)
122130
int memfd;
123131
#endif
132+
page_cache_entry_t pages[MAX_PAGES];
124133
Py_ssize_t page_size;
125134
} proc_handle_t;
126135

136+
static void
137+
_Py_RemoteDebug_FreePageCache(proc_handle_t *handle)
138+
{
139+
for (int i = 0; i < MAX_PAGES; i++) {
140+
PyMem_RawFree(handle->pages[i].data);
141+
handle->pages[i].data = NULL;
142+
handle->pages[i].valid = 0;
143+
}
144+
}
145+
146+
UNUSED static void
147+
_Py_RemoteDebug_ClearCache(proc_handle_t *handle)
148+
{
149+
for (int i = 0; i < MAX_PAGES; i++) {
150+
handle->pages[i].valid = 0;
151+
}
152+
}
127153

128154
#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
129155
static mach_port_t pid_to_task(pid_t pid);
@@ -152,6 +178,10 @@ _Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, pid_t pid) {
152178
handle->memfd = -1;
153179
#endif
154180
handle->page_size = get_page_size();
181+
for (int i = 0; i < MAX_PAGES; i++) {
182+
handle->pages[i].data = NULL;
183+
handle->pages[i].valid = 0;
184+
}
155185
return 0;
156186
}
157187

@@ -170,6 +200,7 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) {
170200
}
171201
#endif
172202
handle->pid = 0;
203+
_Py_RemoteDebug_FreePageCache(handle);
173204
}
174205

175206
#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
@@ -1035,6 +1066,53 @@ _Py_RemoteDebug_PagedReadRemoteMemory(proc_handle_t *handle,
10351066
size_t size,
10361067
void *out)
10371068
{
1069+
size_t page_size = handle->page_size;
1070+
uintptr_t page_base = addr & ~(page_size - 1);
1071+
size_t offset_in_page = addr - page_base;
1072+
1073+
if (offset_in_page + size > page_size) {
1074+
return _Py_RemoteDebug_ReadRemoteMemory(handle, addr, size, out);
1075+
}
1076+
1077+
// Search for valid cached page
1078+
for (int i = 0; i < MAX_PAGES; i++) {
1079+
page_cache_entry_t *entry = &handle->pages[i];
1080+
if (entry->valid && entry->page_addr == page_base) {
1081+
memcpy(out, entry->data + offset_in_page, size);
1082+
return 0;
1083+
}
1084+
}
1085+
1086+
// Find reusable slot
1087+
for (int i = 0; i < MAX_PAGES; i++) {
1088+
page_cache_entry_t *entry = &handle->pages[i];
1089+
if (!entry->valid) {
1090+
if (entry->data == NULL) {
1091+
entry->data = PyMem_RawMalloc(page_size);
1092+
if (entry->data == NULL) {
1093+
_set_debug_exception_cause(PyExc_MemoryError,
1094+
"Cannot allocate %zu bytes for page cache entry "
1095+
"during read from PID %d at address 0x%lx",
1096+
page_size, handle->pid, addr);
1097+
return -1;
1098+
}
1099+
}
1100+
1101+
if (_Py_RemoteDebug_ReadRemoteMemory(handle, page_base, page_size, entry->data) < 0) {
1102+
// Try to just copy the exact ammount as a fallback
1103+
PyErr_Clear();
1104+
goto fallback;
1105+
}
1106+
1107+
entry->page_addr = page_base;
1108+
entry->valid = 1;
1109+
memcpy(out, entry->data + offset_in_page, size);
1110+
return 0;
1111+
}
1112+
}
1113+
1114+
fallback:
1115+
// Cache full — fallback to uncached read
10381116
return _Py_RemoteDebug_ReadRemoteMemory(handle, addr, size, out);
10391117
}
10401118

0 commit comments

Comments
 (0)