Skip to content

Commit 5fd8ff6

Browse files
committed
Add fallback for when process_vm_readv fails with ENOSYS
1 parent e64395e commit 5fd8ff6

File tree

3 files changed

+112
-0
lines changed

3 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

Python/remote_debug.h

Lines changed: 71 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,62 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
907916
return address;
908917
}
909918

919+
#if defined(__linux__) && (HAVE_PREADV || HAVE_PWRITEV)
920+
static int
921+
open_proc_mem_fd(proc_handle_t *handle)
922+
{
923+
char mem_file_path[64];
924+
sprintf(mem_file_path, "/proc/%d/mem", handle->pid);
925+
926+
handle->memfd = open(mem_file_path, O_RDWR);
927+
if (handle->memfd == -1) {
928+
PyErr_SetFromErrno(PyExc_OSError);
929+
_set_debug_exception_cause(PyExc_OSError,
930+
"failed to open file %s: %s", mem_file_path, strerror(errno));
931+
return -1;
932+
}
933+
return 0;
934+
}
935+
#endif // __linux__
936+
937+
static int
938+
read_remote_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst)
939+
{
940+
#if defined(__linux__) && HAVE_PREADV
941+
if (handle->memfd == -1) {
942+
if (open_proc_mem_fd(handle) < 0) {
943+
return -1;
944+
}
945+
}
946+
947+
struct iovec local[1];
948+
Py_ssize_t result = 0;
949+
Py_ssize_t read_bytes = 0;
950+
951+
do {
952+
local[0].iov_base = (char*)dst + result;
953+
local[0].iov_len = len - result;
954+
off_t offset = remote_address + result;
955+
956+
read_bytes = preadv(handle->memfd, local, 1, offset);
957+
if (read_bytes < 0) {
958+
PyErr_SetFromErrno(PyExc_OSError);
959+
_set_debug_exception_cause(PyExc_OSError,
960+
"pread failed for PID %d at address 0x%lx "
961+
"(size %zu, partial read %zd bytes): %s",
962+
handle->pid, remote_address + result, len - result, result, strerror(errno));
963+
return -1;
964+
}
965+
966+
result += read_bytes;
967+
} while ((size_t)read_bytes != local[0].iov_len);
968+
return 0;
969+
#else
970+
PyErr_SetFromErrno(PyExc_OSError);
971+
return -1;
972+
#endif
973+
}
974+
910975
// Platform-independent memory read function
911976
static int
912977
_Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst)
@@ -928,6 +993,9 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
928993
} while (result < len);
929994
return 0;
930995
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
996+
if (handle->memfd != -1) {
997+
return read_remote_memory_fallback(handle, remote_address, len, dst);
998+
}
931999
struct iovec local[1];
9321000
struct iovec remote[1];
9331001
Py_ssize_t result = 0;
@@ -941,6 +1009,9 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
9411009

9421010
read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0);
9431011
if (read_bytes < 0) {
1012+
if (errno == ENOSYS) {
1013+
return read_remote_memory_fallback(handle, remote_address, len, dst);
1014+
}
9441015
PyErr_SetFromErrno(PyExc_OSError);
9451016
_set_debug_exception_cause(PyExc_OSError,
9461017
"process_vm_readv failed for PID %d at address 0x%lx "

Python/remote_debugging.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,40 @@ 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+
static int
28+
write_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
29+
{
30+
#if defined(__linux__) && HAVE_PWRITEV
31+
if (handle->memfd == -1) {
32+
if (open_proc_mem_fd(handle) < 0) {
33+
return -1;
34+
}
35+
}
36+
37+
struct iovec local[1];
38+
Py_ssize_t result = 0;
39+
Py_ssize_t written = 0;
40+
41+
do {
42+
local[0].iov_base = (char*)src + result;
43+
local[0].iov_len = len - result;
44+
off_t offset = remote_address + result;
45+
46+
written = pwritev(handle->memfd, local, 1, offset);
47+
if (written < 0) {
48+
PyErr_SetFromErrno(PyExc_OSError);
49+
return -1;
50+
}
51+
52+
result += written;
53+
} while ((size_t)written != local[0].iov_len);
54+
return 0;
55+
#else
56+
PyErr_SetFromErrno(PyExc_OSError);
57+
return -1;
58+
#endif
59+
}
60+
2761
static int
2862
write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
2963
{
@@ -39,6 +73,9 @@ write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const
3973
} while (result < len);
4074
return 0;
4175
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
76+
if (handle->memfd != -1) {
77+
return write_memory_fallback(handle, remote_address, len, src);
78+
}
4279
struct iovec local[1];
4380
struct iovec remote[1];
4481
Py_ssize_t result = 0;
@@ -52,6 +89,9 @@ write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const
5289

5390
written = process_vm_writev(handle->pid, local, 1, remote, 1, 0);
5491
if (written < 0) {
92+
if (errno == ENOSYS) {
93+
return write_memory_fallback(handle, remote_address, len, src);
94+
}
5595
PyErr_SetFromErrno(PyExc_OSError);
5696
return -1;
5797
}

0 commit comments

Comments
 (0)