Skip to content

Commit 00b33ec

Browse files
committed
Refactor section validation to use a generic callback
Replace the cookie/cookie_size parameter pair in search_*_map_for_section functions with an optional section_validator_t callback. This makes the search functions agnostic to the validation strategy while keeping the same behavior: PyRuntime searches pass _Py_RemoteDebug_ValidatePyRuntimeCookie, AsyncioDebug searches pass NULL (no validation needed). https://claude.ai/code/session_01PAfGNYfkqzWePF2ejbJHNo
1 parent c60daba commit 00b33ec

File tree

2 files changed

+26
-24
lines changed

2 files changed

+26
-24
lines changed

Modules/_remote_debugging/asyncio.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle)
1919
#ifdef MS_WINDOWS
2020
// On Windows, search for asyncio debug in executable or DLL
2121
address = search_windows_map_for_section(handle, "AsyncioD", L"_asyncio",
22-
NULL, 0);
22+
NULL);
2323
if (address == 0) {
2424
// Error out: 'python' substring covers both executable and DLL
2525
PyObject *exc = PyErr_GetRaisedException();
@@ -29,7 +29,7 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle)
2929
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
3030
// On Linux, search for asyncio debug in executable or DLL
3131
address = search_linux_map_for_section(handle, "AsyncioDebug", "python",
32-
NULL, 0);
32+
NULL);
3333
if (address == 0) {
3434
// Error out: 'python' substring covers both executable and DLL
3535
PyObject *exc = PyErr_GetRaisedException();
@@ -39,11 +39,11 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle)
3939
#elif defined(__APPLE__) && TARGET_OS_OSX
4040
// On macOS, try libpython first, then fall back to python
4141
address = search_map_for_section(handle, "AsyncioDebug", "libpython",
42-
NULL, 0);
42+
NULL);
4343
if (address == 0) {
4444
PyErr_Clear();
4545
address = search_map_for_section(handle, "AsyncioDebug", "python",
46-
NULL, 0);
46+
NULL);
4747
}
4848
if (address == 0) {
4949
// Error out: 'python' substring covers both executable and DLL

Python/remote_debug.h

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -154,23 +154,25 @@ typedef struct {
154154
static int
155155
_Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst);
156156

157-
// Validate that a candidate section address starts with the expected cookie.
158-
// This is used to skip duplicate/stale mappings (e.g. from ctypes dlopen)
159-
// whose sections were never initialized. Pass cookie=NULL to skip validation.
157+
// Optional callback to validate a candidate section address found during
158+
// memory map searches. Returns 1 if the address is valid, 0 to skip it.
159+
// This allows callers to filter out duplicate/stale mappings (e.g. from
160+
// ctypes dlopen) whose sections were never initialized.
161+
typedef int (*section_validator_t)(proc_handle_t *handle, uintptr_t address);
162+
163+
// Validate that a candidate address starts with _Py_Debug_Cookie.
160164
static int
161-
_Py_RemoteDebug_ValidateCookie(proc_handle_t *handle, uintptr_t address,
162-
const char *expected_cookie, size_t cookie_size)
165+
_Py_RemoteDebug_ValidatePyRuntimeCookie(proc_handle_t *handle, uintptr_t address)
163166
{
164-
if (expected_cookie == NULL || address == 0) {
165-
return address != 0;
167+
if (address == 0) {
168+
return 0;
166169
}
167-
char buf[16]; // Large enough for any cookie we use
168-
assert(cookie_size <= sizeof(buf));
169-
if (_Py_RemoteDebug_ReadRemoteMemory(handle, address, cookie_size, buf) != 0) {
170+
char buf[sizeof(_Py_Debug_Cookie) - 1];
171+
if (_Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(buf), buf) != 0) {
170172
PyErr_Clear();
171173
return 0;
172174
}
173-
return memcmp(buf, expected_cookie, cookie_size) == 0;
175+
return memcmp(buf, _Py_Debug_Cookie, sizeof(buf)) == 0;
174176
}
175177

176178
static void
@@ -533,7 +535,7 @@ pid_to_task(pid_t pid)
533535

534536
static uintptr_t
535537
search_map_for_section(proc_handle_t *handle, const char* secname, const char* substr,
536-
const char *cookie, size_t cookie_size) {
538+
section_validator_t validator) {
537539
mach_vm_address_t address = 0;
538540
mach_vm_size_t size = 0;
539541
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
@@ -586,7 +588,7 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* s
586588
uintptr_t result = search_section_in_file(
587589
secname, map_filename, address, size, proc_ref);
588590
if (result != 0
589-
&& _Py_RemoteDebug_ValidateCookie(handle, result, cookie, cookie_size))
591+
&& (validator == NULL || validator(handle, result)))
590592
{
591593
return result;
592594
}
@@ -705,7 +707,7 @@ search_elf_file_for_section(
705707

706708
static uintptr_t
707709
search_linux_map_for_section(proc_handle_t *handle, const char* secname, const char* substr,
708-
const char *cookie, size_t cookie_size)
710+
section_validator_t validator)
709711
{
710712
char maps_file_path[64];
711713
sprintf(maps_file_path, "/proc/%d/maps", handle->pid);
@@ -781,7 +783,7 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c
781783
if (strstr(filename, substr)) {
782784
retval = search_elf_file_for_section(handle, secname, start, path);
783785
if (retval
784-
&& _Py_RemoteDebug_ValidateCookie(handle, retval, cookie, cookie_size))
786+
&& (validator == NULL || validator(handle, retval)))
785787
{
786788
break;
787789
}
@@ -890,7 +892,7 @@ static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char*
890892

891893
static uintptr_t
892894
search_windows_map_for_section(proc_handle_t* handle, const char* secname, const wchar_t* substr,
893-
const char *cookie, size_t cookie_size) {
895+
section_validator_t validator) {
894896
HANDLE hProcSnap;
895897
do {
896898
hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, handle->pid);
@@ -915,7 +917,7 @@ search_windows_map_for_section(proc_handle_t* handle, const char* secname, const
915917
if (wcsstr(moduleEntry.szModule, substr)) {
916918
void *candidate = analyze_pe(moduleEntry.szExePath, moduleEntry.modBaseAddr, secname);
917919
if (candidate != NULL
918-
&& _Py_RemoteDebug_ValidateCookie(handle, (uintptr_t)candidate, cookie, cookie_size))
920+
&& (validator == NULL || validator(handle, (uintptr_t)candidate)))
919921
{
920922
runtime_addr = candidate;
921923
break;
@@ -939,7 +941,7 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
939941
#ifdef MS_WINDOWS
940942
// On Windows, search for 'python' in executable or DLL
941943
address = search_windows_map_for_section(handle, "PyRuntime", L"python",
942-
_Py_Debug_Cookie, sizeof(_Py_Debug_Cookie) - 1);
944+
_Py_RemoteDebug_ValidatePyRuntimeCookie);
943945
if (address == 0) {
944946
// Error out: 'python' substring covers both executable and DLL
945947
PyObject *exc = PyErr_GetRaisedException();
@@ -951,7 +953,7 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
951953
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
952954
// On Linux, search for 'python' in executable or DLL
953955
address = search_linux_map_for_section(handle, "PyRuntime", "python",
954-
_Py_Debug_Cookie, sizeof(_Py_Debug_Cookie) - 1);
956+
_Py_RemoteDebug_ValidatePyRuntimeCookie);
955957
if (address == 0) {
956958
// Error out: 'python' substring covers both executable and DLL
957959
PyObject *exc = PyErr_GetRaisedException();
@@ -966,7 +968,7 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
966968
for (const char** candidate = candidates; *candidate; candidate++) {
967969
PyErr_Clear();
968970
address = search_map_for_section(handle, "PyRuntime", *candidate,
969-
_Py_Debug_Cookie, sizeof(_Py_Debug_Cookie) - 1);
971+
_Py_RemoteDebug_ValidatePyRuntimeCookie);
970972
if (address != 0) {
971973
break;
972974
}

0 commit comments

Comments
 (0)