From f1eb6c01a2fe571d07979f5ac1ebf83332742585 Mon Sep 17 00:00:00 2001 From: Daniel Golding Date: Thu, 29 May 2025 12:24:13 +0200 Subject: [PATCH 1/3] Add fallback for when process_vm_readv fails with ENOSYS --- Misc/ACKS | 1 + Python/remote_debug.h | 73 +++++++++++++++++++++++++++++++++++++++ Python/remote_debugging.c | 42 ++++++++++++++++++++++ 3 files changed, 116 insertions(+) diff --git a/Misc/ACKS b/Misc/ACKS index 571142e7e49763..faee05c1fb75de 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -658,6 +658,7 @@ Michael Goderbauer Karan Goel Jeroen Van Goey Christoph Gohlke +Daniel Golding Tim Golden Yonatan Goldschmidt Mark Gollahon diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 6cbf1c8deaaed9..362991953a3152 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -116,6 +116,8 @@ typedef struct { mach_port_t task; #elif defined(MS_WINDOWS) HANDLE hProcess; +#elif defined(__linux__) + int memfd; #endif page_cache_entry_t pages[MAX_PAGES]; Py_ssize_t page_size; @@ -162,6 +164,8 @@ _Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, pid_t pid) { _set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize Windows process handle"); return -1; } +#elif defined(__linux__) + handle->memfd = -1; #endif handle->page_size = get_page_size(); for (int i = 0; i < MAX_PAGES; i++) { @@ -179,6 +183,11 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) { CloseHandle(handle->hProcess); handle->hProcess = NULL; } +#elif defined(__linux__) + if (handle->memfd != -1) { + close(handle->memfd); + handle->memfd = -1; + } #endif handle->pid = 0; _Py_RemoteDebug_FreePageCache(handle); @@ -907,6 +916,64 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) return address; } +#if defined(__linux__) && (HAVE_PREADV || HAVE_PWRITEV) +static int +open_proc_mem_fd(proc_handle_t *handle) +{ + char mem_file_path[64]; + sprintf(mem_file_path, "/proc/%d/mem", handle->pid); + + handle->memfd = open(mem_file_path, O_RDWR); + if (handle->memfd == -1) { + PyErr_SetFromErrno(PyExc_OSError); + _set_debug_exception_cause(PyExc_OSError, + "failed to open file %s: %s", mem_file_path, strerror(errno)); + return -1; + } + return 0; +} +#endif // __linux__ + +#if defined(__linux__) +static int +read_remote_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst) +{ +#ifdef HAVE_PREADV + if (handle->memfd == -1) { + if (open_proc_mem_fd(handle) < 0) { + return -1; + } + } + + struct iovec local[1]; + Py_ssize_t result = 0; + Py_ssize_t read_bytes = 0; + + do { + local[0].iov_base = (char*)dst + result; + local[0].iov_len = len - result; + off_t offset = remote_address + result; + + read_bytes = preadv(handle->memfd, local, 1, offset); + if (read_bytes < 0) { + PyErr_SetFromErrno(PyExc_OSError); + _set_debug_exception_cause(PyExc_OSError, + "preadv failed for PID %d at address 0x%lx " + "(size %zu, partial read %zd bytes): %s", + handle->pid, remote_address + result, len - result, result, strerror(errno)); + return -1; + } + + result += read_bytes; + } while ((size_t)read_bytes != local[0].iov_len); + return 0; +#else + PyErr_SetFromErrno(PyExc_OSError); + return -1; +#endif // HAVE_PREADV +} +#endif // __linux__ + // Platform-independent memory read function static int _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst) @@ -928,6 +995,9 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address } while (result < len); return 0; #elif defined(__linux__) && HAVE_PROCESS_VM_READV + if (handle->memfd != -1) { + return read_remote_memory_fallback(handle, remote_address, len, dst); + } struct iovec local[1]; struct iovec remote[1]; Py_ssize_t result = 0; @@ -941,6 +1011,9 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0); if (read_bytes < 0) { + if (errno == ENOSYS) { + return read_remote_memory_fallback(handle, remote_address, len, dst); + } PyErr_SetFromErrno(PyExc_OSError); _set_debug_exception_cause(PyExc_OSError, "process_vm_readv failed for PID %d at address 0x%lx " diff --git a/Python/remote_debugging.c b/Python/remote_debugging.c index dd55b7812d4dee..30c3e0100f57e8 100644 --- a/Python/remote_debugging.c +++ b/Python/remote_debugging.c @@ -24,6 +24,42 @@ read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* ds return _Py_RemoteDebug_ReadRemoteMemory(handle, remote_address, len, dst); } +#if defined(__linux__) +static int +write_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src) +{ +#ifdef HAVE_PWRITEV + if (handle->memfd == -1) { + if (open_proc_mem_fd(handle) < 0) { + return -1; + } + } + + struct iovec local[1]; + Py_ssize_t result = 0; + Py_ssize_t written = 0; + + do { + local[0].iov_base = (char*)src + result; + local[0].iov_len = len - result; + off_t offset = remote_address + result; + + written = pwritev(handle->memfd, local, 1, offset); + if (written < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + result += written; + } while ((size_t)written != local[0].iov_len); + return 0; +#else + PyErr_SetFromErrno(PyExc_OSError); + return -1; +#endif // HAVE_PWRITEV +} +#endif // __linux__ + static int write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src) { @@ -39,6 +75,9 @@ write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const } while (result < len); return 0; #elif defined(__linux__) && HAVE_PROCESS_VM_READV + if (handle->memfd != -1) { + return write_memory_fallback(handle, remote_address, len, src); + } struct iovec local[1]; struct iovec remote[1]; Py_ssize_t result = 0; @@ -52,6 +91,9 @@ write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const written = process_vm_writev(handle->pid, local, 1, remote, 1, 0); if (written < 0) { + if (errno == ENOSYS) { + return write_memory_fallback(handle, remote_address, len, src); + } PyErr_SetFromErrno(PyExc_OSError); return -1; } From 26b864761554b57d5d233fa921c7d3bc66bd4d6c Mon Sep 17 00:00:00 2001 From: Daniel Golding Date: Sat, 31 May 2025 10:05:39 +0200 Subject: [PATCH 2/3] Subsume guards for preadv into those for process_vm_readv --- Python/remote_debug.h | 15 ++++++--------- Python/remote_debugging.c | 9 +++------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 362991953a3152..0a817bdbd488e0 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -916,7 +916,8 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) return address; } -#if defined(__linux__) && (HAVE_PREADV || HAVE_PWRITEV) +#if defined(__linux__) && HAVE_PROCESS_VM_READV + static int open_proc_mem_fd(proc_handle_t *handle) { @@ -927,18 +928,17 @@ open_proc_mem_fd(proc_handle_t *handle) if (handle->memfd == -1) { PyErr_SetFromErrno(PyExc_OSError); _set_debug_exception_cause(PyExc_OSError, - "failed to open file %s: %s", mem_file_path, strerror(errno)); + "failed to open file %s: %s", mem_file_path, strerror(errno)); return -1; } return 0; } -#endif // __linux__ -#if defined(__linux__) +// Why is pwritev not guarded? Except on Android API level 23 (no longer +// supported), HAVE_PROCESS_VM_READV is sufficient. static int read_remote_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst) { -#ifdef HAVE_PREADV if (handle->memfd == -1) { if (open_proc_mem_fd(handle) < 0) { return -1; @@ -967,11 +967,8 @@ read_remote_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, siz result += read_bytes; } while ((size_t)read_bytes != local[0].iov_len); return 0; -#else - PyErr_SetFromErrno(PyExc_OSError); - return -1; -#endif // HAVE_PREADV } + #endif // __linux__ // Platform-independent memory read function diff --git a/Python/remote_debugging.c b/Python/remote_debugging.c index 30c3e0100f57e8..7aee87ef05a407 100644 --- a/Python/remote_debugging.c +++ b/Python/remote_debugging.c @@ -24,11 +24,12 @@ read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* ds return _Py_RemoteDebug_ReadRemoteMemory(handle, remote_address, len, dst); } -#if defined(__linux__) +// Why is pwritev not guarded? Except on Android API level 23 (no longer +// supported), HAVE_PROCESS_VM_READV is sufficient. +#if defined(__linux__) && HAVE_PROCESS_VM_READV static int write_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src) { -#ifdef HAVE_PWRITEV if (handle->memfd == -1) { if (open_proc_mem_fd(handle) < 0) { return -1; @@ -53,10 +54,6 @@ write_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t le result += written; } while ((size_t)written != local[0].iov_len); return 0; -#else - PyErr_SetFromErrno(PyExc_OSError); - return -1; -#endif // HAVE_PWRITEV } #endif // __linux__ From 2ebfc1966208bc509cafbc267645840dbabcaa32 Mon Sep 17 00:00:00 2001 From: Daniel Golding Date: Sat, 31 May 2025 10:27:17 +0200 Subject: [PATCH 3/3] Add news entry --- .../2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst new file mode 100644 index 00000000000000..1da76561469a41 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst @@ -0,0 +1,2 @@ +Add support to :pep:`768` remote debugging for Linux kernels which don't +have CONFIG_CROSS_MEMORY_ATTACH configured.