Skip to content

Commit 6c5e662

Browse files
committed
Move write_memory implementation to shared remote_debug.h header
The _Py_RemoteDebug_WriteRemoteMemory function was duplicated across modules. Moving it to the shared header (alongside the existing read functions) allows both the sys.remote_exec machinery and the _remote_debugging module to use the same implementation. This is needed for the upcoming frame cache which writes to remote process memory from _remote_debugging.
1 parent 62a459d commit 6c5e662

File tree

2 files changed

+111
-95
lines changed

2 files changed

+111
-95
lines changed

Python/remote_debug.h

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,115 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
11021102
#endif
11031103
}
11041104

1105+
#if defined(__linux__) && HAVE_PROCESS_VM_READV
1106+
// Fallback write using /proc/pid/mem
1107+
static int
1108+
_Py_RemoteDebug_WriteRemoteMemoryFallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
1109+
{
1110+
if (handle->memfd == -1) {
1111+
if (open_proc_mem_fd(handle) < 0) {
1112+
return -1;
1113+
}
1114+
}
1115+
1116+
struct iovec local[1];
1117+
Py_ssize_t result = 0;
1118+
Py_ssize_t written = 0;
1119+
1120+
do {
1121+
local[0].iov_base = (char*)src + result;
1122+
local[0].iov_len = len - result;
1123+
off_t offset = remote_address + result;
1124+
1125+
written = pwritev(handle->memfd, local, 1, offset);
1126+
if (written < 0) {
1127+
PyErr_SetFromErrno(PyExc_OSError);
1128+
return -1;
1129+
}
1130+
1131+
result += written;
1132+
} while ((size_t)written != local[0].iov_len);
1133+
return 0;
1134+
}
1135+
#endif // __linux__
1136+
1137+
// Platform-independent memory write function
1138+
UNUSED static int
1139+
_Py_RemoteDebug_WriteRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
1140+
{
1141+
#ifdef MS_WINDOWS
1142+
SIZE_T written = 0;
1143+
SIZE_T result = 0;
1144+
do {
1145+
if (!WriteProcessMemory(handle->hProcess, (LPVOID)(remote_address + result), (const char*)src + result, len - result, &written)) {
1146+
PyErr_SetFromWindowsErr(0);
1147+
DWORD error = GetLastError();
1148+
_set_debug_exception_cause(PyExc_OSError,
1149+
"WriteProcessMemory failed for PID %d at address 0x%lx "
1150+
"(size %zu, partial write %zu bytes): Windows error %lu",
1151+
handle->pid, remote_address + result, len - result, result, error);
1152+
return -1;
1153+
}
1154+
result += written;
1155+
} while (result < len);
1156+
return 0;
1157+
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
1158+
if (handle->memfd != -1) {
1159+
return _Py_RemoteDebug_WriteRemoteMemoryFallback(handle, remote_address, len, src);
1160+
}
1161+
struct iovec local[1];
1162+
struct iovec remote[1];
1163+
Py_ssize_t result = 0;
1164+
Py_ssize_t written = 0;
1165+
1166+
do {
1167+
local[0].iov_base = (void*)((char*)src + result);
1168+
local[0].iov_len = len - result;
1169+
remote[0].iov_base = (void*)((char*)remote_address + result);
1170+
remote[0].iov_len = len - result;
1171+
1172+
written = process_vm_writev(handle->pid, local, 1, remote, 1, 0);
1173+
if (written < 0) {
1174+
if (errno == ENOSYS) {
1175+
return _Py_RemoteDebug_WriteRemoteMemoryFallback(handle, remote_address, len, src);
1176+
}
1177+
PyErr_SetFromErrno(PyExc_OSError);
1178+
_set_debug_exception_cause(PyExc_OSError,
1179+
"process_vm_writev failed for PID %d at address 0x%lx "
1180+
"(size %zu, partial write %zd bytes): %s",
1181+
handle->pid, remote_address + result, len - result, result, strerror(errno));
1182+
return -1;
1183+
}
1184+
1185+
result += written;
1186+
} while ((size_t)written != local[0].iov_len);
1187+
return 0;
1188+
#elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
1189+
kern_return_t kr = mach_vm_write(
1190+
handle->task,
1191+
(mach_vm_address_t)remote_address,
1192+
(vm_offset_t)src,
1193+
(mach_msg_type_number_t)len);
1194+
1195+
if (kr != KERN_SUCCESS) {
1196+
switch (kr) {
1197+
case KERN_PROTECTION_FAILURE:
1198+
PyErr_SetString(PyExc_PermissionError, "Not enough permissions to write memory");
1199+
break;
1200+
case KERN_INVALID_ARGUMENT:
1201+
PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_write");
1202+
break;
1203+
default:
1204+
PyErr_Format(PyExc_RuntimeError, "Unknown error writing memory: %d", (int)kr);
1205+
}
1206+
return -1;
1207+
}
1208+
return 0;
1209+
#else
1210+
Py_UNREACHABLE();
1211+
#endif
1212+
}
1213+
11051214
UNUSED static int
11061215
_Py_RemoteDebug_PagedReadRemoteMemory(proc_handle_t *handle,
11071216
uintptr_t addr,

Python/remote_debugging.c

Lines changed: 2 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -24,104 +24,11 @@ read_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* d
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-
27+
// Use the shared write function from remote_debug.h
6028
static int
6129
write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
6230
{
63-
#ifdef MS_WINDOWS
64-
SIZE_T written = 0;
65-
SIZE_T result = 0;
66-
do {
67-
if (!WriteProcessMemory(handle->hProcess, (LPVOID)(remote_address + result), (const char*)src + result, len - result, &written)) {
68-
PyErr_SetFromWindowsErr(0);
69-
return -1;
70-
}
71-
result += written;
72-
} while (result < len);
73-
return 0;
74-
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
75-
if (handle->memfd != -1) {
76-
return write_memory_fallback(handle, remote_address, len, src);
77-
}
78-
struct iovec local[1];
79-
struct iovec remote[1];
80-
Py_ssize_t result = 0;
81-
Py_ssize_t written = 0;
82-
83-
do {
84-
local[0].iov_base = (void*)((char*)src + result);
85-
local[0].iov_len = len - result;
86-
remote[0].iov_base = (void*)((char*)remote_address + result);
87-
remote[0].iov_len = len - result;
88-
89-
written = process_vm_writev(handle->pid, local, 1, remote, 1, 0);
90-
if (written < 0) {
91-
if (errno == ENOSYS) {
92-
return write_memory_fallback(handle, remote_address, len, src);
93-
}
94-
PyErr_SetFromErrno(PyExc_OSError);
95-
return -1;
96-
}
97-
98-
result += written;
99-
} while ((size_t)written != local[0].iov_len);
100-
return 0;
101-
#elif defined(__APPLE__) && TARGET_OS_OSX
102-
kern_return_t kr = mach_vm_write(
103-
pid_to_task(handle->pid),
104-
(mach_vm_address_t)remote_address,
105-
(vm_offset_t)src,
106-
(mach_msg_type_number_t)len);
107-
108-
if (kr != KERN_SUCCESS) {
109-
switch (kr) {
110-
case KERN_PROTECTION_FAILURE:
111-
PyErr_SetString(PyExc_PermissionError, "Not enough permissions to write memory");
112-
break;
113-
case KERN_INVALID_ARGUMENT:
114-
PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_write");
115-
break;
116-
default:
117-
PyErr_Format(PyExc_RuntimeError, "Unknown error writing memory: %d", (int)kr);
118-
}
119-
return -1;
120-
}
121-
return 0;
122-
#else
123-
Py_UNREACHABLE();
124-
#endif
31+
return _Py_RemoteDebug_WriteRemoteMemory(handle, remote_address, len, src);
12532
}
12633

12734
static int

0 commit comments

Comments
 (0)