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

This commit is contained in:
Daniel Golding 2025-06-07 20:32:06 +02:00 committed by GitHub
parent 24069fbca8
commit ac9c3431cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 112 additions and 0 deletions

View File

@ -658,6 +658,7 @@ Michael Goderbauer
Karan Goel Karan Goel
Jeroen Van Goey Jeroen Van Goey
Christoph Gohlke Christoph Gohlke
Daniel Golding
Tim Golden Tim Golden
Yonatan Goldschmidt Yonatan Goldschmidt
Mark Gollahon Mark Gollahon

View File

@ -0,0 +1,2 @@
Add support to :pep:`768` remote debugging for Linux kernels which don't
have CONFIG_CROSS_MEMORY_ATTACH configured.

View File

@ -116,6 +116,8 @@ typedef struct {
mach_port_t task; mach_port_t task;
#elif defined(MS_WINDOWS) #elif defined(MS_WINDOWS)
HANDLE hProcess; HANDLE hProcess;
#elif defined(__linux__)
int memfd;
#endif #endif
page_cache_entry_t pages[MAX_PAGES]; page_cache_entry_t pages[MAX_PAGES];
Py_ssize_t page_size; 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"); _set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize Windows process handle");
return -1; return -1;
} }
#elif defined(__linux__)
handle->memfd = -1;
#endif #endif
handle->page_size = get_page_size(); handle->page_size = get_page_size();
for (int i = 0; i < MAX_PAGES; i++) { for (int i = 0; i < MAX_PAGES; i++) {
@ -179,6 +183,11 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) {
CloseHandle(handle->hProcess); CloseHandle(handle->hProcess);
handle->hProcess = NULL; handle->hProcess = NULL;
} }
#elif defined(__linux__)
if (handle->memfd != -1) {
close(handle->memfd);
handle->memfd = -1;
}
#endif #endif
handle->pid = 0; handle->pid = 0;
_Py_RemoteDebug_FreePageCache(handle); _Py_RemoteDebug_FreePageCache(handle);
@ -907,6 +916,61 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
return address; 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 // Platform-independent memory read function
static int static int
_Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst) _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); } while (result < len);
return 0; return 0;
#elif defined(__linux__) && HAVE_PROCESS_VM_READV #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 local[1];
struct iovec remote[1]; struct iovec remote[1];
Py_ssize_t result = 0; 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); read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0);
if (read_bytes < 0) { if (read_bytes < 0) {
if (errno == ENOSYS) {
return read_remote_memory_fallback(handle, remote_address, len, dst);
}
PyErr_SetFromErrno(PyExc_OSError); PyErr_SetFromErrno(PyExc_OSError);
_set_debug_exception_cause(PyExc_OSError, _set_debug_exception_cause(PyExc_OSError,
"process_vm_readv failed for PID %d at address 0x%lx " "process_vm_readv failed for PID %d at address 0x%lx "

View File

@ -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); 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 static int
write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src) 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); } while (result < len);
return 0; return 0;
#elif defined(__linux__) && HAVE_PROCESS_VM_READV #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 local[1];
struct iovec remote[1]; struct iovec remote[1];
Py_ssize_t result = 0; 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); written = process_vm_writev(handle->pid, local, 1, remote, 1, 0);
if (written < 0) { if (written < 0) {
if (errno == ENOSYS) {
return write_memory_fallback(handle, remote_address, len, src);
}
PyErr_SetFromErrno(PyExc_OSError); PyErr_SetFromErrno(PyExc_OSError);
return -1; return -1;
} }