Skip to content

Commit cbea7a2

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

3 files changed

Lines changed: 110 additions & 0 deletions

File tree

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

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

9421008
read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0);
9431009
if (read_bytes < 0) {
1010+
if (errno == ENOSYS) {
1011+
return read_remote_memory_fallback(handle, remote_address, len, dst);
1012+
}
9441013
PyErr_SetFromErrno(PyExc_OSError);
9451014
_set_debug_exception_cause(PyExc_OSError,
9461015
"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)