summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2025-06-07 18:56:38 (GMT)
committerGitHub <noreply@github.com>2025-06-07 18:56:38 (GMT)
commitef539654e682cc451ea68fa366a16e9020df89b8 (patch)
tree4483914c306b8b878827da8811f5575a76cc6029
parent3faf00e0031652cb067ddfce7c61574567e2742d (diff)
downloadcpython-ef539654e682cc451ea68fa366a16e9020df89b8.zip
cpython-ef539654e682cc451ea68fa366a16e9020df89b8.tar.gz
cpython-ef539654e682cc451ea68fa366a16e9020df89b8.tar.bz2
[3.14] gh-134876: Add fallback for when process_vm_readv fails with ENOSYS (GH-134878) (#135240)
gh-134876: Add fallback for when process_vm_readv fails with ENOSYS (GH-134878) (cherry picked from commit ac9c3431cc5916a795c42b3e2b965233ceffe6f0) Co-authored-by: Daniel Golding <goldingd89@gmail.com>
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst2
-rw-r--r--Python/remote_debug.h70
-rw-r--r--Python/remote_debugging.c39
4 files changed, 112 insertions, 0 deletions
diff --git a/Misc/ACKS b/Misc/ACKS
index 2b5748c..f5f5c2f 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/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 0000000..1da7656
--- /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.
diff --git a/Python/remote_debug.h b/Python/remote_debug.h
index 6cbf1c8..0a817bd 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,61 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
return address;
}
+#if defined(__linux__) && HAVE_PROCESS_VM_READV
+
+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;
+}
+
+// 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)
+{
+ 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;
+}
+
+#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 +992,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 +1008,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 dd55b78..7aee87e 100644
--- a/Python/remote_debugging.c
+++ b/Python/remote_debugging.c
@@ -24,6 +24,39 @@ read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* ds
return _Py_RemoteDebug_ReadRemoteMemory(handle, remote_address, len, dst);
}
+// 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)
+{
+ 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;
+}
+#endif // __linux__
+
static int
write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
{
@@ -39,6 +72,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 +88,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;
}