https://github.com/python/cpython/commit/ac9c3431cc5916a795c42b3e2b965233ceffe6f0
commit: ac9c3431cc5916a795c42b3e2b965233ceffe6f0
branch: main
author: Daniel Golding <golding...@gmail.com>
committer: pablogsal <pablog...@gmail.com>
date: 2025-06-07T19:32:06+01:00
summary:

gh-134876: Add fallback for when process_vm_readv fails with ENOSYS (#134878)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst
M Misc/ACKS
M Python/remote_debug.h
M Python/remote_debugging.c

diff --git a/Misc/ACKS b/Misc/ACKS
index 2435943f1bb2bd..0be31560387ccb 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 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.
diff --git a/Python/remote_debug.h b/Python/remote_debug.h
index 6cbf1c8deaaed9..0a817bdbd488e0 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 dd55b7812d4dee..7aee87ef05a407 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;
         }

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: arch...@mail-archive.com

Reply via email to