Skip to content

Commit ef53965

Browse files
[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 ac9c343) Co-authored-by: Daniel Golding <goldingd89@gmail.com>
1 parent 3faf00e commit ef53965

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,7 @@ Michael Goderbauer
658658
Karan Goel
659659
Jeroen Van Goey
660660
Christoph Gohlke
661+
Daniel Golding
661662
Tim Golden
662663
Yonatan Goldschmidt
663664
Mark Gollahon
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add support to :pep:`768` remote debugging for Linux kernels which don't
2+
have CONFIG_CROSS_MEMORY_ATTACH configured.

Python/remote_debug.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ typedef struct {
116116
mach_port_t task;
117117
#elif defined(MS_WINDOWS)
118118
HANDLE hProcess;
119+
#elif defined(__linux__)
120+
int memfd;
119121
#endif
120122
page_cache_entry_t pages[MAX_PAGES];
121123
Py_ssize_t page_size;
@@ -162,6 +164,8 @@ _Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, pid_t pid) {
162164
_set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize Windows process handle");
163165
return -1;
164166
}
167+
#elif defined(__linux__)
168+
handle->memfd = -1;
165169
#endif
166170
handle->page_size = get_page_size();
167171
for (int i = 0; i < MAX_PAGES; i++) {
@@ -179,6 +183,11 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) {
179183
CloseHandle(handle->hProcess);
180184
handle->hProcess = NULL;
181185
}
186+
#elif defined(__linux__)
187+
if (handle->memfd != -1) {
188+
close(handle->memfd);
189+
handle->memfd = -1;
190+
}
182191
#endif
183192
handle->pid = 0;
184193
_Py_RemoteDebug_FreePageCache(handle);
@@ -907,6 +916,61 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
907916
return address;
908917
}
909918

919+
#if defined(__linux__) && HAVE_PROCESS_VM_READV
920+
921+
static int
922+
open_proc_mem_fd(proc_handle_t *handle)
923+
{
924+
char mem_file_path[64];
925+
sprintf(mem_file_path, "/proc/%d/mem", handle->pid);
926+
927+
handle->memfd = open(mem_file_path, O_RDWR);
928+
if (handle->memfd == -1) {
929+
PyErr_SetFromErrno(PyExc_OSError);
930+
_set_debug_exception_cause(PyExc_OSError,
931+
"failed to open file %s: %s", mem_file_path, strerror(errno));
932+
return -1;
933+
}
934+
return 0;
935+
}
936+
937+
// Why is pwritev not guarded? Except on Android API level 23 (no longer
938+
// supported), HAVE_PROCESS_VM_READV is sufficient.
939+
static int
940+
read_remote_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst)
941+
{
942+
if (handle->memfd == -1) {
943+
if (open_proc_mem_fd(handle) < 0) {
944+
return -1;
945+
}
946+
}
947+
948+
struct iovec local[1];
949+
Py_ssize_t result = 0;
950+
Py_ssize_t read_bytes = 0;
951+
952+
do {
953+
local[0].iov_base = (char*)dst + result;
954+
local[0].iov_len = len - result;
955+
off_t offset = remote_address + result;
956+
957+
read_bytes = preadv(handle->memfd, local, 1, offset);
958+
if (read_bytes < 0) {
959+
PyErr_SetFromErrno(PyExc_OSError);
960+
_set_debug_exception_cause(PyExc_OSError,
961+
"preadv failed for PID %d at address 0x%lx "
962+
"(size %zu, partial read %zd bytes): %s",
963+
handle->pid, remote_address + result, len - result, result, strerror(errno));
964+
return -1;
965+
}
966+
967+
result += read_bytes;
968+
} while ((size_t)read_bytes != local[0].iov_len);
969+
return 0;
970+
}
971+
972+
#endif // __linux__
973+
910974
// Platform-independent memory read function
911975
static int
912976
_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
928992
} while (result < len);
929993
return 0;
930994
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
995+
if (handle->memfd != -1) {
996+
return read_remote_memory_fallback(handle, remote_address, len, dst);
997+
}
931998
struct iovec local[1];
932999
struct iovec remote[1];
9331000
Py_ssize_t result = 0;
@@ -941,6 +1008,9 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
9411008

9421009
read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0);
9431010
if (read_bytes < 0) {
1011+
if (errno == ENOSYS) {
1012+
return read_remote_memory_fallback(handle, remote_address, len, dst);
1013+
}
9441014
PyErr_SetFromErrno(PyExc_OSError);
9451015
_set_debug_exception_cause(PyExc_OSError,
9461016
"process_vm_readv failed for PID %d at address 0x%lx "

Python/remote_debugging.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,39 @@ read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* ds
2424
return _Py_RemoteDebug_ReadRemoteMemory(handle, remote_address, len, dst);
2525
}
2626

27+
// Why is pwritev not guarded? Except on Android API level 23 (no longer
28+
// supported), HAVE_PROCESS_VM_READV is sufficient.
29+
#if defined(__linux__) && HAVE_PROCESS_VM_READV
30+
static int
31+
write_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
32+
{
33+
if (handle->memfd == -1) {
34+
if (open_proc_mem_fd(handle) < 0) {
35+
return -1;
36+
}
37+
}
38+
39+
struct iovec local[1];
40+
Py_ssize_t result = 0;
41+
Py_ssize_t written = 0;
42+
43+
do {
44+
local[0].iov_base = (char*)src + result;
45+
local[0].iov_len = len - result;
46+
off_t offset = remote_address + result;
47+
48+
written = pwritev(handle->memfd, local, 1, offset);
49+
if (written < 0) {
50+
PyErr_SetFromErrno(PyExc_OSError);
51+
return -1;
52+
}
53+
54+
result += written;
55+
} while ((size_t)written != local[0].iov_len);
56+
return 0;
57+
}
58+
#endif // __linux__
59+
2760
static int
2861
write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
2962
{
@@ -39,6 +72,9 @@ write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const
3972
} while (result < len);
4073
return 0;
4174
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
75+
if (handle->memfd != -1) {
76+
return write_memory_fallback(handle, remote_address, len, src);
77+
}
4278
struct iovec local[1];
4379
struct iovec remote[1];
4480
Py_ssize_t result = 0;
@@ -52,6 +88,9 @@ write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const
5288

5389
written = process_vm_writev(handle->pid, local, 1, remote, 1, 0);
5490
if (written < 0) {
91+
if (errno == ENOSYS) {
92+
return write_memory_fallback(handle, remote_address, len, src);
93+
}
5594
PyErr_SetFromErrno(PyExc_OSError);
5695
return -1;
5796
}

0 commit comments

Comments
 (0)