/src/cpython3/Python/remote_debug.h
Line | Count | Source |
1 | | /* |
2 | | IMPORTANT: This header file is full of static functions that are not exported. |
3 | | |
4 | | The reason is that we don't want to export these functions to the Python API |
5 | | and they can be used both for the interpreter and some shared libraries. The |
6 | | reason we don't want to export them is to avoid having them participating in |
7 | | return-oriented programming attacks. |
8 | | |
9 | | If you need to add a new function ensure that is declared 'static'. |
10 | | */ |
11 | | |
12 | | #ifdef __cplusplus |
13 | | extern "C" { |
14 | | #endif |
15 | | |
16 | | #ifdef __clang__ |
17 | | #define UNUSED __attribute__((unused)) |
18 | | #elif defined(__GNUC__) |
19 | | #define UNUSED __attribute__((unused)) |
20 | | #elif defined(_MSC_VER) |
21 | | #define UNUSED __pragma(warning(suppress: 4505)) |
22 | | #else |
23 | | #define UNUSED |
24 | | #endif |
25 | | |
26 | | #if !defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) |
27 | | # error "this header requires Py_BUILD_CORE or Py_BUILD_CORE_MODULE define" |
28 | | #endif |
29 | | |
30 | | #include "pyconfig.h" |
31 | | #include "internal/pycore_ceval.h" |
32 | | |
33 | | #ifdef __linux__ |
34 | | # include <elf.h> |
35 | | # include <sys/uio.h> |
36 | | # include <sys/ptrace.h> |
37 | | # include <sys/wait.h> |
38 | | # include <dirent.h> |
39 | | # if INTPTR_MAX == INT64_MAX |
40 | 0 | # define Elf_Ehdr Elf64_Ehdr |
41 | 0 | # define Elf_Shdr Elf64_Shdr |
42 | 0 | # define Elf_Phdr Elf64_Phdr |
43 | | # else |
44 | | # define Elf_Ehdr Elf32_Ehdr |
45 | | # define Elf_Shdr Elf32_Shdr |
46 | | # define Elf_Phdr Elf32_Phdr |
47 | | # endif |
48 | | # include <sys/mman.h> |
49 | | |
50 | | // PTRACE options - define if not available |
51 | | # ifndef PTRACE_SEIZE |
52 | | # define PTRACE_SEIZE 0x4206 |
53 | | # endif |
54 | | # ifndef PTRACE_INTERRUPT |
55 | | # define PTRACE_INTERRUPT 0x4207 |
56 | | # endif |
57 | | # ifndef PTRACE_EVENT_STOP |
58 | | # define PTRACE_EVENT_STOP 128 |
59 | | # endif |
60 | | #endif |
61 | | |
62 | | #if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX |
63 | | # include <libproc.h> |
64 | | # include <mach-o/fat.h> |
65 | | # include <mach-o/loader.h> |
66 | | # include <mach-o/nlist.h> |
67 | | # include <mach/error.h> |
68 | | # include <mach/mach.h> |
69 | | # include <mach/mach_vm.h> |
70 | | # include <mach/machine.h> |
71 | | # include <mach/task_info.h> |
72 | | # include <mach/thread_act.h> |
73 | | # include <sys/mman.h> |
74 | | # include <sys/proc.h> |
75 | | # include <sys/sysctl.h> |
76 | | #endif |
77 | | |
78 | | #ifdef MS_WINDOWS |
79 | | // Windows includes and definitions |
80 | | #include <windows.h> |
81 | | #include <psapi.h> |
82 | | #include <tlhelp32.h> |
83 | | #endif |
84 | | |
85 | | #include <errno.h> |
86 | | #include <fcntl.h> |
87 | | #include <stddef.h> |
88 | | #include <stdint.h> |
89 | | #include <stdio.h> |
90 | | #include <stdlib.h> |
91 | | #include <string.h> |
92 | | #ifndef MS_WINDOWS |
93 | | #include <sys/param.h> |
94 | | #include <sys/stat.h> |
95 | | #include <sys/types.h> |
96 | | #include <unistd.h> |
97 | | #endif |
98 | | |
99 | | #ifndef HAVE_PROCESS_VM_READV |
100 | | # define HAVE_PROCESS_VM_READV 0 |
101 | | #endif |
102 | | |
103 | | static inline int |
104 | | _Py_RemoteDebug_HasPermissionError(void) |
105 | 0 | { |
106 | 0 | return PyErr_Occurred() |
107 | 0 | && PyErr_ExceptionMatches(PyExc_PermissionError); |
108 | 0 | } |
109 | | |
110 | | #define _set_debug_exception_cause(exception, format, ...) \ |
111 | 0 | do { \ |
112 | 0 | if (!_Py_RemoteDebug_HasPermissionError()) { \ |
113 | 0 | PyThreadState *tstate = _PyThreadState_GET(); \ |
114 | 0 | if (!_PyErr_Occurred(tstate)) { \ |
115 | 0 | _PyErr_Format(tstate, exception, format, ##__VA_ARGS__); \ |
116 | 0 | } else { \ |
117 | 0 | _PyErr_FormatFromCause(exception, format, ##__VA_ARGS__); \ |
118 | 0 | } \ |
119 | 0 | } \ |
120 | 0 | } while (0) |
121 | | |
122 | | #define _set_debug_oserror_from_errno(err, format, ...) \ |
123 | | do { \ |
124 | | errno = (err); \ |
125 | | PyErr_SetFromErrno(PyExc_OSError); \ |
126 | | _set_debug_exception_cause(PyExc_OSError, format, ##__VA_ARGS__); \ |
127 | | } while (0) |
128 | | |
129 | | #define _set_debug_oserror_from_errno_with_filename(err, filename, format, ...) \ |
130 | 0 | do { \ |
131 | 0 | errno = (err); \ |
132 | 0 | PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename); \ |
133 | 0 | _set_debug_exception_cause(PyExc_OSError, format, ##__VA_ARGS__); \ |
134 | 0 | } while (0) |
135 | | |
136 | | static inline size_t |
137 | 0 | get_page_size(void) { |
138 | 0 | size_t page_size = 0; |
139 | 0 | if (page_size == 0) { |
140 | | #ifdef MS_WINDOWS |
141 | | SYSTEM_INFO si; |
142 | | GetSystemInfo(&si); |
143 | | page_size = si.dwPageSize; |
144 | | #else |
145 | 0 | long n = sysconf(_SC_PAGESIZE); |
146 | 0 | page_size = (n > 0) ? (size_t)n : 4096; |
147 | 0 | #endif |
148 | 0 | } |
149 | 0 | return page_size; |
150 | 0 | } |
151 | | |
152 | | typedef struct page_cache_entry { |
153 | | uintptr_t page_addr; // page-aligned base address |
154 | | char *data; |
155 | | int valid; |
156 | | struct page_cache_entry *next; |
157 | | } page_cache_entry_t; |
158 | | |
159 | 0 | #define MAX_PAGES 1024 |
160 | | |
161 | | // Define a platform-independent process handle structure |
162 | | typedef struct { |
163 | | pid_t pid; |
164 | | #if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX |
165 | | mach_port_t task; |
166 | | #elif defined(MS_WINDOWS) |
167 | | HANDLE hProcess; |
168 | | #elif defined(__linux__) |
169 | | int memfd; |
170 | | #endif |
171 | | page_cache_entry_t pages[MAX_PAGES]; |
172 | | int page_cache_count; |
173 | | Py_ssize_t page_size; |
174 | | } proc_handle_t; |
175 | | |
176 | | // Forward declaration for use in validation function |
177 | | static int |
178 | | _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst); |
179 | | |
180 | | // Optional callback to validate a candidate section address found during |
181 | | // memory map searches. Returns 1 if the address is valid, 0 to skip it. |
182 | | // This allows callers to filter out duplicate/stale mappings (e.g. from |
183 | | // ctypes dlopen) whose sections were never initialized. |
184 | | typedef int (*section_validator_t)(proc_handle_t *handle, uintptr_t address); |
185 | | |
186 | | // Validate that a candidate address starts with _Py_Debug_Cookie. |
187 | | UNUSED static int |
188 | | _Py_RemoteDebug_ValidatePyRuntimeCookie(proc_handle_t *handle, uintptr_t address) |
189 | 0 | { |
190 | 0 | if (address == 0) { |
191 | 0 | return 0; |
192 | 0 | } |
193 | 0 | char buf[sizeof(_Py_Debug_Cookie) - 1]; |
194 | 0 | if (_Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(buf), buf) != 0) { |
195 | 0 | if (!_Py_RemoteDebug_HasPermissionError()) { |
196 | 0 | PyErr_Clear(); |
197 | 0 | } |
198 | 0 | return 0; |
199 | 0 | } |
200 | 0 | return memcmp(buf, _Py_Debug_Cookie, sizeof(buf)) == 0; |
201 | 0 | } |
202 | | |
203 | | static void |
204 | | _Py_RemoteDebug_FreePageCache(proc_handle_t *handle) |
205 | 0 | { |
206 | 0 | for (int i = 0; i < MAX_PAGES; i++) { |
207 | 0 | if (handle->pages[i].data) { |
208 | 0 | PyMem_RawFree(handle->pages[i].data); |
209 | 0 | } |
210 | 0 | handle->pages[i].data = NULL; |
211 | 0 | handle->pages[i].valid = 0; |
212 | 0 | } |
213 | 0 | handle->page_cache_count = 0; |
214 | 0 | } |
215 | | |
216 | | UNUSED static void |
217 | | _Py_RemoteDebug_ClearCache(proc_handle_t *handle) |
218 | 0 | { |
219 | 0 | for (int i = 0; i < handle->page_cache_count; i++) { |
220 | 0 | handle->pages[i].valid = 0; |
221 | 0 | } |
222 | 0 | handle->page_cache_count = 0; |
223 | 0 | } |
224 | | |
225 | | #if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX |
226 | | static mach_port_t pid_to_task(pid_t pid); |
227 | | #endif |
228 | | |
229 | | // Initialize the process handle |
230 | | UNUSED static int |
231 | 0 | _Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, pid_t pid) { |
232 | 0 | handle->pid = 0; |
233 | | #if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX |
234 | | handle->task = 0; |
235 | | #elif defined(MS_WINDOWS) |
236 | | handle->hProcess = NULL; |
237 | | #elif defined(__linux__) |
238 | | handle->memfd = -1; |
239 | 0 | #endif |
240 | 0 | handle->page_size = get_page_size(); |
241 | 0 | handle->page_cache_count = 0; |
242 | 0 | for (int i = 0; i < MAX_PAGES; i++) { |
243 | 0 | handle->pages[i].data = NULL; |
244 | 0 | handle->pages[i].valid = 0; |
245 | 0 | } |
246 | |
|
247 | 0 | handle->pid = pid; |
248 | | #if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX |
249 | | handle->task = pid_to_task(handle->pid); |
250 | | if (handle->task == 0) { |
251 | | _set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize macOS process handle"); |
252 | | return -1; |
253 | | } |
254 | | #elif defined(MS_WINDOWS) |
255 | | handle->hProcess = OpenProcess( |
256 | | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION | PROCESS_SUSPEND_RESUME, |
257 | | FALSE, pid); |
258 | | if (handle->hProcess == NULL) { |
259 | | DWORD error = GetLastError(); |
260 | | PyErr_SetFromWindowsErr(error); |
261 | | _set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize Windows process handle"); |
262 | | return -1; |
263 | | } |
264 | | #endif |
265 | 0 | return 0; |
266 | 0 | } |
267 | | |
268 | | // Clean up the process handle |
269 | | UNUSED static void |
270 | 0 | _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) { |
271 | | #ifdef MS_WINDOWS |
272 | | if (handle->hProcess != NULL) { |
273 | | CloseHandle(handle->hProcess); |
274 | | handle->hProcess = NULL; |
275 | | } |
276 | | #elif defined(__linux__) |
277 | 0 | if (handle->memfd != -1) { |
278 | 0 | close(handle->memfd); |
279 | 0 | handle->memfd = -1; |
280 | 0 | } |
281 | 0 | #endif |
282 | 0 | handle->pid = 0; |
283 | 0 | _Py_RemoteDebug_FreePageCache(handle); |
284 | 0 | } |
285 | | |
286 | | #if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX |
287 | | |
288 | | static uintptr_t |
289 | | return_section_address64( |
290 | | const char* section, |
291 | | mach_port_t proc_ref, |
292 | | uintptr_t base, |
293 | | void* map |
294 | | ) { |
295 | | struct mach_header_64* hdr = (struct mach_header_64*)map; |
296 | | int ncmds = hdr->ncmds; |
297 | | |
298 | | int cmd_cnt = 0; |
299 | | struct segment_command_64* cmd = map + sizeof(struct mach_header_64); |
300 | | |
301 | | mach_vm_size_t size = 0; |
302 | | mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t); |
303 | | mach_vm_address_t address = (mach_vm_address_t)base; |
304 | | vm_region_basic_info_data_64_t r_info; |
305 | | mach_port_t object_name; |
306 | | uintptr_t vmaddr = 0; |
307 | | |
308 | | for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) { |
309 | | if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__TEXT") == 0) { |
310 | | vmaddr = cmd->vmaddr; |
311 | | } |
312 | | if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__DATA") == 0) { |
313 | | while (cmd->filesize != size) { |
314 | | address += size; |
315 | | kern_return_t ret = mach_vm_region( |
316 | | proc_ref, |
317 | | &address, |
318 | | &size, |
319 | | VM_REGION_BASIC_INFO_64, |
320 | | (vm_region_info_t)&r_info, // cppcheck-suppress [uninitvar] |
321 | | &count, |
322 | | &object_name |
323 | | ); |
324 | | if (ret != KERN_SUCCESS) { |
325 | | PyErr_Format(PyExc_RuntimeError, |
326 | | "mach_vm_region failed while parsing 64-bit Mach-O binary " |
327 | | "at base address 0x%lx (kern_return_t: %d)", |
328 | | base, ret); |
329 | | return 0; |
330 | | } |
331 | | } |
332 | | |
333 | | int nsects = cmd->nsects; |
334 | | struct section_64* sec = (struct section_64*)( |
335 | | (void*)cmd + sizeof(struct segment_command_64) |
336 | | ); |
337 | | for (int j = 0; j < nsects; j++) { |
338 | | if (strcmp(sec[j].sectname, section) == 0) { |
339 | | return base + sec[j].addr - vmaddr; |
340 | | } |
341 | | } |
342 | | cmd_cnt++; |
343 | | } |
344 | | |
345 | | cmd = (struct segment_command_64*)((void*)cmd + cmd->cmdsize); |
346 | | } |
347 | | |
348 | | return 0; |
349 | | } |
350 | | |
351 | | static uintptr_t |
352 | | return_section_address32( |
353 | | const char* section, |
354 | | mach_port_t proc_ref, |
355 | | uintptr_t base, |
356 | | void* map |
357 | | ) { |
358 | | struct mach_header* hdr = (struct mach_header*)map; |
359 | | int ncmds = hdr->ncmds; |
360 | | |
361 | | int cmd_cnt = 0; |
362 | | struct segment_command* cmd = map + sizeof(struct mach_header); |
363 | | |
364 | | mach_vm_size_t size = 0; |
365 | | mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_t); |
366 | | mach_vm_address_t address = (mach_vm_address_t)base; |
367 | | vm_region_basic_info_data_t r_info; |
368 | | mach_port_t object_name; |
369 | | uintptr_t vmaddr = 0; |
370 | | |
371 | | for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) { |
372 | | if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__TEXT") == 0) { |
373 | | vmaddr = cmd->vmaddr; |
374 | | } |
375 | | if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__DATA") == 0) { |
376 | | while (cmd->filesize != size) { |
377 | | address += size; |
378 | | kern_return_t ret = mach_vm_region( |
379 | | proc_ref, |
380 | | &address, |
381 | | &size, |
382 | | VM_REGION_BASIC_INFO, |
383 | | (vm_region_info_t)&r_info, // cppcheck-suppress [uninitvar] |
384 | | &count, |
385 | | &object_name |
386 | | ); |
387 | | if (ret != KERN_SUCCESS) { |
388 | | PyErr_Format(PyExc_RuntimeError, |
389 | | "mach_vm_region failed while parsing 32-bit Mach-O binary " |
390 | | "at base address 0x%lx (kern_return_t: %d)", |
391 | | base, ret); |
392 | | return 0; |
393 | | } |
394 | | } |
395 | | |
396 | | int nsects = cmd->nsects; |
397 | | struct section* sec = (struct section*)( |
398 | | (void*)cmd + sizeof(struct segment_command) |
399 | | ); |
400 | | for (int j = 0; j < nsects; j++) { |
401 | | if (strcmp(sec[j].sectname, section) == 0) { |
402 | | return base + sec[j].addr - vmaddr; |
403 | | } |
404 | | } |
405 | | cmd_cnt++; |
406 | | } |
407 | | |
408 | | cmd = (struct segment_command*)((void*)cmd + cmd->cmdsize); |
409 | | } |
410 | | |
411 | | return 0; |
412 | | } |
413 | | |
414 | | static uintptr_t |
415 | | return_section_address_fat( |
416 | | const char* section, |
417 | | mach_port_t proc_ref, |
418 | | uintptr_t base, |
419 | | void* map |
420 | | ) { |
421 | | struct fat_header* fat_hdr = (struct fat_header*)map; |
422 | | |
423 | | // Determine host CPU type for architecture selection |
424 | | cpu_type_t cpu; |
425 | | int is_abi64; |
426 | | size_t cpu_size = sizeof(cpu), abi64_size = sizeof(is_abi64); |
427 | | |
428 | | if (sysctlbyname("hw.cputype", &cpu, &cpu_size, NULL, 0) != 0) { |
429 | | int err = errno; |
430 | | _set_debug_oserror_from_errno(err, |
431 | | "Failed to determine CPU type via sysctlbyname " |
432 | | "for fat binary analysis at 0x%lx: %s", |
433 | | base, strerror(err)); |
434 | | return 0; |
435 | | } |
436 | | if (sysctlbyname("hw.cpu64bit_capable", &is_abi64, &abi64_size, NULL, 0) != 0) { |
437 | | int err = errno; |
438 | | _set_debug_oserror_from_errno(err, |
439 | | "Failed to determine CPU ABI capability via sysctlbyname " |
440 | | "for fat binary analysis at 0x%lx: %s", |
441 | | base, strerror(err)); |
442 | | return 0; |
443 | | } |
444 | | |
445 | | cpu |= is_abi64 * CPU_ARCH_ABI64; |
446 | | |
447 | | // Check endianness |
448 | | int swap = fat_hdr->magic == FAT_CIGAM; |
449 | | struct fat_arch* arch = (struct fat_arch*)(map + sizeof(struct fat_header)); |
450 | | |
451 | | // Get number of architectures in fat binary |
452 | | uint32_t nfat_arch = swap ? __builtin_bswap32(fat_hdr->nfat_arch) : fat_hdr->nfat_arch; |
453 | | |
454 | | // Search for matching architecture |
455 | | for (uint32_t i = 0; i < nfat_arch; i++) { |
456 | | cpu_type_t arch_cpu = swap ? __builtin_bswap32(arch[i].cputype) : arch[i].cputype; |
457 | | |
458 | | if (arch_cpu == cpu) { |
459 | | // Found matching architecture, now process it |
460 | | uint32_t offset = swap ? __builtin_bswap32(arch[i].offset) : arch[i].offset; |
461 | | struct mach_header_64* hdr = (struct mach_header_64*)(map + offset); |
462 | | |
463 | | // Determine which type of Mach-O it is and process accordingly |
464 | | switch (hdr->magic) { |
465 | | case MH_MAGIC: |
466 | | case MH_CIGAM: |
467 | | return return_section_address32(section, proc_ref, base, (void*)hdr); |
468 | | |
469 | | case MH_MAGIC_64: |
470 | | case MH_CIGAM_64: |
471 | | return return_section_address64(section, proc_ref, base, (void*)hdr); |
472 | | |
473 | | default: |
474 | | PyErr_Format(PyExc_RuntimeError, |
475 | | "Unknown Mach-O magic number 0x%x in fat binary architecture %u at base 0x%lx", |
476 | | hdr->magic, i, base); |
477 | | return 0; |
478 | | } |
479 | | } |
480 | | } |
481 | | |
482 | | PyErr_Format(PyExc_RuntimeError, |
483 | | "No matching architecture found for CPU type 0x%x " |
484 | | "in fat binary at base 0x%lx (%u architectures examined)", |
485 | | cpu, base, nfat_arch); |
486 | | return 0; |
487 | | } |
488 | | |
489 | | static uintptr_t |
490 | | search_section_in_file(const char* secname, char* path, uintptr_t base, mach_vm_size_t size, mach_port_t proc_ref) |
491 | | { |
492 | | int fd = open(path, O_RDONLY); |
493 | | if (fd == -1) { |
494 | | int err = errno; |
495 | | _set_debug_oserror_from_errno_with_filename(err, path, |
496 | | "Cannot open binary file '%s' for section '%s' search: %s", |
497 | | path, secname, strerror(err)); |
498 | | return 0; |
499 | | } |
500 | | |
501 | | struct stat fs; |
502 | | if (fstat(fd, &fs) == -1) { |
503 | | int err = errno; |
504 | | _set_debug_oserror_from_errno_with_filename(err, path, |
505 | | "Cannot get file size for binary '%s' during section '%s' search: %s", |
506 | | path, secname, strerror(err)); |
507 | | close(fd); |
508 | | return 0; |
509 | | } |
510 | | |
511 | | void* map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0); |
512 | | if (map == MAP_FAILED) { |
513 | | int err = errno; |
514 | | _set_debug_oserror_from_errno_with_filename(err, path, |
515 | | "Cannot memory map binary file '%s' (size: %lld bytes) for section '%s' search: %s", |
516 | | path, (long long)fs.st_size, secname, strerror(err)); |
517 | | close(fd); |
518 | | return 0; |
519 | | } |
520 | | |
521 | | uintptr_t result = 0; |
522 | | uint32_t magic = *(uint32_t*)map; |
523 | | |
524 | | switch (magic) { |
525 | | case MH_MAGIC: |
526 | | case MH_CIGAM: |
527 | | result = return_section_address32(secname, proc_ref, base, map); |
528 | | break; |
529 | | case MH_MAGIC_64: |
530 | | case MH_CIGAM_64: |
531 | | result = return_section_address64(secname, proc_ref, base, map); |
532 | | break; |
533 | | case FAT_MAGIC: |
534 | | case FAT_CIGAM: |
535 | | result = return_section_address_fat(secname, proc_ref, base, map); |
536 | | break; |
537 | | default: |
538 | | PyErr_Format(PyExc_RuntimeError, |
539 | | "Unrecognized Mach-O magic number 0x%x in binary file '%s' for section '%s' search", |
540 | | magic, path, secname); |
541 | | break; |
542 | | } |
543 | | |
544 | | if (munmap(map, fs.st_size) != 0) { |
545 | | if (!PyErr_Occurred()) { |
546 | | int err = errno; |
547 | | _set_debug_oserror_from_errno_with_filename(err, path, |
548 | | "Failed to unmap binary file '%s' (size: %lld bytes): %s", |
549 | | path, (long long)fs.st_size, strerror(err)); |
550 | | } |
551 | | result = 0; |
552 | | } |
553 | | if (close(fd) != 0) { |
554 | | if (!PyErr_Occurred()) { |
555 | | int err = errno; |
556 | | _set_debug_oserror_from_errno_with_filename(err, path, |
557 | | "Failed to close binary file '%s': %s", |
558 | | path, strerror(err)); |
559 | | } |
560 | | result = 0; |
561 | | } |
562 | | return result; |
563 | | } |
564 | | |
565 | | |
566 | | static mach_port_t |
567 | | pid_to_task(pid_t pid) |
568 | | { |
569 | | mach_port_t task; |
570 | | kern_return_t result; |
571 | | |
572 | | result = task_for_pid(mach_task_self(), pid, &task); |
573 | | if (result != KERN_SUCCESS) { |
574 | | PyErr_Format(PyExc_PermissionError, |
575 | | "Cannot get task port for PID %d (kern_return_t: %d). " |
576 | | "This typically requires running as root or having the 'com.apple.system-task-ports' entitlement.", |
577 | | pid, result); |
578 | | return 0; |
579 | | } |
580 | | return task; |
581 | | } |
582 | | |
583 | | static uintptr_t |
584 | | search_map_for_section(proc_handle_t *handle, const char* secname, const char* substr, |
585 | | section_validator_t validator) { |
586 | | mach_vm_address_t address = 0; |
587 | | mach_vm_size_t size = 0; |
588 | | mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t); |
589 | | vm_region_basic_info_data_64_t region_info; |
590 | | mach_port_t object_name; |
591 | | |
592 | | mach_port_t proc_ref = pid_to_task(handle->pid); |
593 | | if (proc_ref == 0) { |
594 | | if (!PyErr_Occurred()) { |
595 | | PyErr_Format(PyExc_PermissionError, |
596 | | "Cannot get task port for PID %d during section search", |
597 | | handle->pid); |
598 | | } |
599 | | return 0; |
600 | | } |
601 | | |
602 | | char map_filename[MAXPATHLEN + 1]; |
603 | | |
604 | | kern_return_t kr; |
605 | | while ((kr = mach_vm_region( |
606 | | proc_ref, |
607 | | &address, |
608 | | &size, |
609 | | VM_REGION_BASIC_INFO_64, |
610 | | (vm_region_info_t)®ion_info, |
611 | | &count, |
612 | | &object_name)) == KERN_SUCCESS) |
613 | | { |
614 | | |
615 | | if ((region_info.protection & VM_PROT_READ) == 0 |
616 | | || (region_info.protection & VM_PROT_EXECUTE) == 0) { |
617 | | address += size; |
618 | | continue; |
619 | | } |
620 | | |
621 | | int path_len = proc_regionfilename( |
622 | | handle->pid, address, map_filename, MAXPATHLEN); |
623 | | if (path_len == 0) { |
624 | | address += size; |
625 | | continue; |
626 | | } |
627 | | |
628 | | char* filename = strrchr(map_filename, '/'); |
629 | | if (filename != NULL) { |
630 | | filename++; // Move past the '/' |
631 | | } else { |
632 | | filename = map_filename; // No path, use the whole string |
633 | | } |
634 | | |
635 | | if (strncmp(filename, substr, strlen(substr)) == 0) { |
636 | | PyErr_Clear(); |
637 | | uintptr_t result = search_section_in_file( |
638 | | secname, map_filename, address, size, proc_ref); |
639 | | if (result != 0) { |
640 | | if (validator == NULL || validator(handle, result)) { |
641 | | return result; |
642 | | } |
643 | | if (_Py_RemoteDebug_HasPermissionError()) { |
644 | | return 0; |
645 | | } |
646 | | } |
647 | | else if (_Py_RemoteDebug_HasPermissionError()) { |
648 | | return 0; |
649 | | } |
650 | | } |
651 | | |
652 | | address += size; |
653 | | } |
654 | | |
655 | | if (kr != KERN_INVALID_ADDRESS && !PyErr_Occurred()) { |
656 | | PyErr_Format(PyExc_RuntimeError, |
657 | | "mach_vm_region failed while searching PID %d for section '%s' " |
658 | | "(kern_return_t: %d)", |
659 | | handle->pid, secname, kr); |
660 | | } |
661 | | |
662 | | return 0; |
663 | | } |
664 | | |
665 | | #endif // (__APPLE__ && defined(TARGET_OS_OSX) && TARGET_OS_OSX) |
666 | | |
667 | | #if defined(__linux__) && HAVE_PROCESS_VM_READV |
668 | | static uintptr_t |
669 | | search_elf_file_for_section( |
670 | | proc_handle_t *handle, |
671 | | const char* secname, |
672 | | uintptr_t start_address, |
673 | | const char *elf_file) |
674 | 0 | { |
675 | 0 | if (start_address == 0) { |
676 | 0 | return 0; |
677 | 0 | } |
678 | | |
679 | 0 | uintptr_t result = 0; |
680 | 0 | void* file_memory = NULL; |
681 | |
|
682 | 0 | int fd = open(elf_file, O_RDONLY); |
683 | 0 | if (fd < 0) { |
684 | 0 | int err = errno; |
685 | 0 | _set_debug_oserror_from_errno_with_filename(err, elf_file, |
686 | 0 | "Cannot open ELF file '%s' for section '%s' search: %s", |
687 | 0 | elf_file, secname, strerror(err)); |
688 | 0 | goto exit; |
689 | 0 | } |
690 | | |
691 | 0 | struct stat file_stats; |
692 | 0 | if (fstat(fd, &file_stats) != 0) { |
693 | 0 | int err = errno; |
694 | 0 | _set_debug_oserror_from_errno_with_filename(err, elf_file, |
695 | 0 | "Cannot get file size for ELF file '%s' during section '%s' search: %s", |
696 | 0 | elf_file, secname, strerror(err)); |
697 | 0 | goto exit; |
698 | 0 | } |
699 | | |
700 | 0 | file_memory = mmap(NULL, file_stats.st_size, PROT_READ, MAP_PRIVATE, fd, 0); |
701 | 0 | if (file_memory == MAP_FAILED) { |
702 | 0 | int err = errno; |
703 | 0 | _set_debug_oserror_from_errno_with_filename(err, elf_file, |
704 | 0 | "Cannot memory map ELF file '%s' (size: %lld bytes) for section '%s' search: %s", |
705 | 0 | elf_file, (long long)file_stats.st_size, secname, strerror(err)); |
706 | 0 | file_memory = NULL; |
707 | 0 | goto exit; |
708 | 0 | } |
709 | | |
710 | 0 | Elf_Ehdr* elf_header = (Elf_Ehdr*)file_memory; |
711 | | |
712 | | // Validate ELF header |
713 | 0 | if (elf_header->e_shstrndx >= elf_header->e_shnum) { |
714 | 0 | PyErr_Format(PyExc_RuntimeError, |
715 | 0 | "Invalid ELF file '%s': string table index %u >= section count %u", |
716 | 0 | elf_file, elf_header->e_shstrndx, elf_header->e_shnum); |
717 | 0 | goto exit; |
718 | 0 | } |
719 | | |
720 | 0 | Elf_Shdr* section_header_table = (Elf_Shdr*)(file_memory + elf_header->e_shoff); |
721 | |
|
722 | 0 | Elf_Shdr* shstrtab_section = §ion_header_table[elf_header->e_shstrndx]; |
723 | 0 | char* shstrtab = (char*)(file_memory + shstrtab_section->sh_offset); |
724 | |
|
725 | 0 | Elf_Shdr* section = NULL; |
726 | 0 | for (int i = 0; i < elf_header->e_shnum; i++) { |
727 | 0 | char* this_sec_name = shstrtab + section_header_table[i].sh_name; |
728 | | // Move 1 character to account for the leading "." |
729 | 0 | this_sec_name += 1; |
730 | 0 | if (strcmp(secname, this_sec_name) == 0) { |
731 | 0 | section = §ion_header_table[i]; |
732 | 0 | break; |
733 | 0 | } |
734 | 0 | } |
735 | |
|
736 | 0 | if (section == NULL) { |
737 | 0 | goto exit; |
738 | 0 | } |
739 | | |
740 | 0 | Elf_Phdr* program_header_table = (Elf_Phdr*)(file_memory + elf_header->e_phoff); |
741 | | // Find the first PT_LOAD segment |
742 | 0 | Elf_Phdr* first_load_segment = NULL; |
743 | 0 | for (int i = 0; i < elf_header->e_phnum; i++) { |
744 | 0 | if (program_header_table[i].p_type == PT_LOAD) { |
745 | 0 | first_load_segment = &program_header_table[i]; |
746 | 0 | break; |
747 | 0 | } |
748 | 0 | } |
749 | |
|
750 | 0 | if (first_load_segment == NULL) { |
751 | 0 | PyErr_Format(PyExc_RuntimeError, |
752 | 0 | "No PT_LOAD segment found in ELF file '%s' (%u program headers examined)", |
753 | 0 | elf_file, elf_header->e_phnum); |
754 | 0 | goto exit; |
755 | 0 | } |
756 | | |
757 | 0 | uintptr_t elf_load_addr = first_load_segment->p_vaddr |
758 | 0 | - (first_load_segment->p_vaddr % first_load_segment->p_align); |
759 | 0 | result = start_address + (uintptr_t)section->sh_addr - elf_load_addr; |
760 | |
|
761 | 0 | exit: |
762 | 0 | if (file_memory != NULL) { |
763 | 0 | if (munmap(file_memory, file_stats.st_size) != 0) { |
764 | 0 | if (!PyErr_Occurred()) { |
765 | 0 | int err = errno; |
766 | 0 | _set_debug_oserror_from_errno_with_filename(err, elf_file, |
767 | 0 | "Failed to unmap ELF file '%s' (size: %lld bytes): %s", |
768 | 0 | elf_file, (long long)file_stats.st_size, strerror(err)); |
769 | 0 | } |
770 | 0 | result = 0; |
771 | 0 | } |
772 | 0 | } |
773 | 0 | if (fd >= 0 && close(fd) != 0) { |
774 | 0 | if (!PyErr_Occurred()) { |
775 | 0 | int err = errno; |
776 | 0 | _set_debug_oserror_from_errno_with_filename(err, elf_file, |
777 | 0 | "Failed to close ELF file '%s': %s", |
778 | 0 | elf_file, strerror(err)); |
779 | 0 | } |
780 | 0 | result = 0; |
781 | 0 | } |
782 | 0 | return result; |
783 | 0 | } |
784 | | |
785 | | static const char * |
786 | | find_debug_cookie(const char *buffer, size_t len) |
787 | 0 | { |
788 | 0 | const char *cookie = _Py_Debug_Cookie; |
789 | 0 | const size_t cookie_len = sizeof(_Py_Debug_Cookie) - 1; |
790 | 0 | if (len < cookie_len) { |
791 | 0 | return NULL; |
792 | 0 | } |
793 | | |
794 | 0 | size_t pos = 0; |
795 | 0 | size_t last = len - cookie_len; |
796 | 0 | while (pos <= last) { |
797 | 0 | const char *candidate = memchr( |
798 | 0 | buffer + pos, cookie[0], last - pos + 1); |
799 | 0 | if (candidate == NULL) { |
800 | 0 | return NULL; |
801 | 0 | } |
802 | 0 | pos = (size_t)(candidate - buffer); |
803 | 0 | if (memcmp(candidate, cookie, cookie_len) == 0) { |
804 | 0 | return candidate; |
805 | 0 | } |
806 | 0 | pos++; |
807 | 0 | } |
808 | 0 | return NULL; |
809 | 0 | } |
810 | | |
811 | | static int |
812 | | linux_map_path_is_deleted(const char *path) |
813 | 0 | { |
814 | 0 | static const char deleted_suffix[] = " (deleted)"; |
815 | 0 | size_t path_len = strlen(path); |
816 | 0 | size_t suffix_len = sizeof(deleted_suffix) - 1; |
817 | 0 | return path_len >= suffix_len |
818 | 0 | && strcmp(path + path_len - suffix_len, deleted_suffix) == 0; |
819 | 0 | } |
820 | | |
821 | | static int |
822 | | linux_map_perms_are_readwrite(const char *perms) |
823 | 0 | { |
824 | 0 | return perms[0] == 'r' && perms[1] == 'w'; |
825 | 0 | } |
826 | | |
827 | | static uintptr_t |
828 | | scan_linux_mapping_for_pyruntime_cookie( |
829 | | proc_handle_t *handle, |
830 | | uintptr_t start, |
831 | | uintptr_t end) |
832 | 0 | { |
833 | 0 | if (end <= start) { |
834 | 0 | return 0; |
835 | 0 | } |
836 | | |
837 | 0 | const size_t cookie_len = sizeof(_Py_Debug_Cookie) - 1; |
838 | 0 | const size_t overlap = cookie_len - 1; |
839 | 0 | const size_t chunk_size = 1024 * 1024; |
840 | 0 | char *buffer = PyMem_Malloc(chunk_size); |
841 | 0 | if (buffer == NULL) { |
842 | 0 | PyErr_NoMemory(); |
843 | 0 | _set_debug_exception_cause(PyExc_MemoryError, |
844 | 0 | "Cannot allocate memory while scanning PID %d for PyRuntime cookie", |
845 | 0 | handle->pid); |
846 | 0 | return 0; |
847 | 0 | } |
848 | | |
849 | 0 | uintptr_t retval = 0; |
850 | 0 | uintptr_t mapping_size = end - start; |
851 | 0 | uintptr_t offset = 0; |
852 | 0 | while (offset < mapping_size) { |
853 | 0 | uintptr_t remaining = mapping_size - offset; |
854 | 0 | size_t wanted = remaining > chunk_size |
855 | 0 | ? chunk_size : (size_t)remaining; |
856 | 0 | if (_Py_RemoteDebug_ReadRemoteMemory( |
857 | 0 | handle, start + offset, wanted, buffer) < 0) { |
858 | 0 | if (_Py_RemoteDebug_HasPermissionError()) { |
859 | 0 | goto exit; |
860 | 0 | } |
861 | | // A candidate mapping can disappear or contain unreadable holes while |
862 | | // the target process keeps running. Treat those as non-matches and |
863 | | // keep scanning other candidate mappings. |
864 | 0 | PyErr_Clear(); |
865 | 0 | } |
866 | 0 | else { |
867 | 0 | const char *hit = find_debug_cookie(buffer, wanted); |
868 | 0 | if (hit != NULL) { |
869 | 0 | retval = start + offset + (uintptr_t)(hit - buffer); |
870 | 0 | goto exit; |
871 | 0 | } |
872 | 0 | } |
873 | | |
874 | 0 | if (wanted <= overlap) { |
875 | 0 | break; |
876 | 0 | } |
877 | 0 | offset += wanted - overlap; |
878 | 0 | } |
879 | | |
880 | 0 | exit: |
881 | 0 | PyMem_Free(buffer); |
882 | 0 | return retval; |
883 | 0 | } |
884 | | |
885 | | static uintptr_t |
886 | | search_linux_map_for_section(proc_handle_t *handle, const char* secname, const char* substr, |
887 | | section_validator_t validator) |
888 | 0 | { |
889 | 0 | char maps_file_path[64]; |
890 | 0 | sprintf(maps_file_path, "/proc/%d/maps", handle->pid); |
891 | |
|
892 | 0 | FILE* maps_file = fopen(maps_file_path, "r"); |
893 | 0 | if (maps_file == NULL) { |
894 | 0 | int err = errno; |
895 | 0 | _set_debug_oserror_from_errno_with_filename(err, maps_file_path, |
896 | 0 | "Cannot open process memory map file '%s' for PID %d section search: %s", |
897 | 0 | maps_file_path, handle->pid, strerror(err)); |
898 | 0 | return 0; |
899 | 0 | } |
900 | | |
901 | 0 | size_t linelen = 0; |
902 | 0 | size_t linesz = PATH_MAX; |
903 | 0 | char *line = PyMem_Malloc(linesz); |
904 | 0 | if (!line) { |
905 | 0 | fclose(maps_file); |
906 | 0 | _set_debug_exception_cause(PyExc_MemoryError, |
907 | 0 | "Cannot allocate memory for reading process map file '%s'", |
908 | 0 | maps_file_path); |
909 | 0 | return 0; |
910 | 0 | } |
911 | | |
912 | 0 | uintptr_t retval = 0; |
913 | |
|
914 | 0 | while (fgets(line + linelen, linesz - linelen, maps_file) != NULL) { |
915 | 0 | linelen = strlen(line); |
916 | 0 | if (line[linelen - 1] != '\n') { |
917 | | // Read a partial line: realloc and keep reading where we left off. |
918 | | // Note that even the last line will be terminated by a newline. |
919 | 0 | linesz *= 2; |
920 | 0 | char *biggerline = PyMem_Realloc(line, linesz); |
921 | 0 | if (!biggerline) { |
922 | 0 | PyMem_Free(line); |
923 | 0 | fclose(maps_file); |
924 | 0 | _set_debug_exception_cause(PyExc_MemoryError, |
925 | 0 | "Cannot reallocate memory while reading process map file '%s' (attempted size: %zu)", |
926 | 0 | maps_file_path, linesz); |
927 | 0 | return 0; |
928 | 0 | } |
929 | 0 | line = biggerline; |
930 | 0 | continue; |
931 | 0 | } |
932 | | |
933 | | // Read a full line: strip the newline |
934 | 0 | line[linelen - 1] = '\0'; |
935 | | // and prepare to read the next line into the start of the buffer. |
936 | 0 | linelen = 0; |
937 | |
|
938 | 0 | unsigned long start = 0; |
939 | 0 | unsigned long end = 0; |
940 | 0 | int path_pos = 0; |
941 | 0 | char perms[5] = ""; |
942 | 0 | int fields = sscanf(line, "%lx-%lx %4s %*s %*s %*s %n", |
943 | 0 | &start, &end, perms, &path_pos); |
944 | |
|
945 | 0 | if (fields < 3 || !path_pos) { |
946 | | // Line didn't match our format string. This shouldn't be |
947 | | // possible, but let's be defensive and skip the line. |
948 | 0 | continue; |
949 | 0 | } |
950 | | |
951 | 0 | const char *path = line + path_pos; |
952 | 0 | if (path[0] == '\0') { |
953 | 0 | continue; |
954 | 0 | } |
955 | 0 | if (path[0] == '[' && path[strlen(path)-1] == ']') { |
956 | | // Skip [heap], [stack], [anon:cpython:pymalloc], etc. |
957 | 0 | continue; |
958 | 0 | } |
959 | | |
960 | 0 | const char *filename = strrchr(path, '/'); |
961 | 0 | if (filename) { |
962 | 0 | filename++; // Move past the '/' |
963 | 0 | } else { |
964 | 0 | filename = path; // No directories, or an empty string |
965 | 0 | } |
966 | |
|
967 | 0 | if (strstr(filename, substr)) { |
968 | 0 | int deleted_pyruntime_mapping = |
969 | 0 | strcmp(secname, "PyRuntime") == 0 |
970 | 0 | && linux_map_path_is_deleted(path); |
971 | 0 | if (deleted_pyruntime_mapping |
972 | 0 | && linux_map_perms_are_readwrite(perms)) { |
973 | 0 | PyErr_Clear(); |
974 | 0 | retval = scan_linux_mapping_for_pyruntime_cookie( |
975 | 0 | handle, (uintptr_t)start, (uintptr_t)end); |
976 | 0 | } |
977 | 0 | if (!deleted_pyruntime_mapping |
978 | 0 | && retval == 0 && !PyErr_Occurred()) { |
979 | 0 | PyErr_Clear(); |
980 | 0 | retval = search_elf_file_for_section( |
981 | 0 | handle, secname, start, path); |
982 | 0 | } |
983 | 0 | if (retval) { |
984 | 0 | if (validator == NULL || validator(handle, retval)) { |
985 | 0 | break; |
986 | 0 | } |
987 | 0 | if (_Py_RemoteDebug_HasPermissionError()) { |
988 | 0 | retval = 0; |
989 | 0 | break; |
990 | 0 | } |
991 | 0 | } |
992 | 0 | else if (_Py_RemoteDebug_HasPermissionError()) { |
993 | 0 | break; |
994 | 0 | } |
995 | 0 | retval = 0; |
996 | 0 | } |
997 | 0 | } |
998 | | |
999 | 0 | if (retval == 0 && !PyErr_Occurred() && ferror(maps_file)) { |
1000 | 0 | int err = errno; |
1001 | 0 | _set_debug_oserror_from_errno_with_filename(err, maps_file_path, |
1002 | 0 | "Failed to read process map file '%s' for PID %d section search: %s", |
1003 | 0 | maps_file_path, handle->pid, strerror(err)); |
1004 | 0 | } |
1005 | |
|
1006 | 0 | PyMem_Free(line); |
1007 | 0 | if (fclose(maps_file) != 0) { |
1008 | 0 | if (!PyErr_Occurred()) { |
1009 | 0 | int err = errno; |
1010 | 0 | _set_debug_oserror_from_errno_with_filename(err, maps_file_path, |
1011 | 0 | "Failed to close process map file '%s': %s", |
1012 | 0 | maps_file_path, strerror(err)); |
1013 | 0 | } |
1014 | 0 | retval = 0; |
1015 | 0 | } |
1016 | |
|
1017 | 0 | return retval; |
1018 | 0 | } |
1019 | | |
1020 | | |
1021 | | #endif // __linux__ |
1022 | | |
1023 | | #ifdef MS_WINDOWS |
1024 | | |
1025 | | static int is_process_alive(HANDLE hProcess) { |
1026 | | DWORD exitCode; |
1027 | | if (GetExitCodeProcess(hProcess, &exitCode)) { |
1028 | | return exitCode == STILL_ACTIVE; |
1029 | | } |
1030 | | return 0; |
1031 | | } |
1032 | | |
1033 | | static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char* secname) { |
1034 | | HANDLE hFile = CreateFileW(mod_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
1035 | | if (hFile == INVALID_HANDLE_VALUE) { |
1036 | | DWORD error = GetLastError(); |
1037 | | PyErr_SetFromWindowsErr(error); |
1038 | | _set_debug_exception_cause(PyExc_OSError, |
1039 | | "Cannot open PE file for section '%s' analysis (error %lu)", |
1040 | | secname, error); |
1041 | | return NULL; |
1042 | | } |
1043 | | |
1044 | | HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0); |
1045 | | if (!hMap) { |
1046 | | DWORD error = GetLastError(); |
1047 | | PyErr_SetFromWindowsErr(error); |
1048 | | _set_debug_exception_cause(PyExc_OSError, |
1049 | | "Cannot create file mapping for PE file section '%s' analysis (error %lu)", |
1050 | | secname, error); |
1051 | | CloseHandle(hFile); |
1052 | | return NULL; |
1053 | | } |
1054 | | |
1055 | | BYTE* mapView = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); |
1056 | | if (!mapView) { |
1057 | | DWORD error = GetLastError(); |
1058 | | PyErr_SetFromWindowsErr(error); |
1059 | | _set_debug_exception_cause(PyExc_OSError, |
1060 | | "Cannot map view of PE file for section '%s' analysis (error %lu)", |
1061 | | secname, error); |
1062 | | CloseHandle(hMap); |
1063 | | CloseHandle(hFile); |
1064 | | return NULL; |
1065 | | } |
1066 | | |
1067 | | IMAGE_DOS_HEADER* pDOSHeader = (IMAGE_DOS_HEADER*)mapView; |
1068 | | if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) { |
1069 | | PyErr_Format(PyExc_RuntimeError, |
1070 | | "Invalid DOS signature (0x%x) in PE file for section '%s' analysis (expected 0x%x)", |
1071 | | pDOSHeader->e_magic, secname, IMAGE_DOS_SIGNATURE); |
1072 | | UnmapViewOfFile(mapView); |
1073 | | CloseHandle(hMap); |
1074 | | CloseHandle(hFile); |
1075 | | return NULL; |
1076 | | } |
1077 | | |
1078 | | IMAGE_NT_HEADERS* pNTHeaders = (IMAGE_NT_HEADERS*)(mapView + pDOSHeader->e_lfanew); |
1079 | | if (pNTHeaders->Signature != IMAGE_NT_SIGNATURE) { |
1080 | | PyErr_Format(PyExc_RuntimeError, |
1081 | | "Invalid NT signature (0x%lx) in PE file for section '%s' analysis (expected 0x%lx)", |
1082 | | pNTHeaders->Signature, secname, IMAGE_NT_SIGNATURE); |
1083 | | UnmapViewOfFile(mapView); |
1084 | | CloseHandle(hMap); |
1085 | | CloseHandle(hFile); |
1086 | | return NULL; |
1087 | | } |
1088 | | |
1089 | | IMAGE_SECTION_HEADER* pSection_header = (IMAGE_SECTION_HEADER*)(mapView + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)); |
1090 | | void* runtime_addr = NULL; |
1091 | | |
1092 | | for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) { |
1093 | | const char* name = (const char*)pSection_header[i].Name; |
1094 | | if (strncmp(name, secname, IMAGE_SIZEOF_SHORT_NAME) == 0) { |
1095 | | runtime_addr = remote_base + pSection_header[i].VirtualAddress; |
1096 | | break; |
1097 | | } |
1098 | | } |
1099 | | |
1100 | | UnmapViewOfFile(mapView); |
1101 | | CloseHandle(hMap); |
1102 | | CloseHandle(hFile); |
1103 | | |
1104 | | return runtime_addr; |
1105 | | } |
1106 | | |
1107 | | |
1108 | | static uintptr_t |
1109 | | search_windows_map_for_section(proc_handle_t* handle, const char* secname, const wchar_t* substr, |
1110 | | section_validator_t validator) { |
1111 | | HANDLE hProcSnap; |
1112 | | do { |
1113 | | hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, handle->pid); |
1114 | | } while (hProcSnap == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH); |
1115 | | |
1116 | | if (hProcSnap == INVALID_HANDLE_VALUE) { |
1117 | | DWORD error = GetLastError(); |
1118 | | PyErr_SetFromWindowsErr(error); |
1119 | | _set_debug_exception_cause(PyExc_OSError, |
1120 | | "Unable to create module snapshot for PID %d section '%s' " |
1121 | | "search (error %lu). Check permissions or PID validity", |
1122 | | handle->pid, secname, error); |
1123 | | return 0; |
1124 | | } |
1125 | | |
1126 | | MODULEENTRY32W moduleEntry; |
1127 | | moduleEntry.dwSize = sizeof(moduleEntry); |
1128 | | void* runtime_addr = NULL; |
1129 | | |
1130 | | if (!Module32FirstW(hProcSnap, &moduleEntry)) { |
1131 | | DWORD error = GetLastError(); |
1132 | | PyErr_SetFromWindowsErr(error); |
1133 | | _set_debug_exception_cause(PyExc_OSError, |
1134 | | "Unable to enumerate modules for PID %d section '%s' " |
1135 | | "search (error %lu)", |
1136 | | handle->pid, secname, error); |
1137 | | CloseHandle(hProcSnap); |
1138 | | return 0; |
1139 | | } |
1140 | | |
1141 | | do { |
1142 | | // Look for either python executable or DLL |
1143 | | if (wcsstr(moduleEntry.szModule, substr)) { |
1144 | | PyErr_Clear(); |
1145 | | void *candidate = analyze_pe(moduleEntry.szExePath, moduleEntry.modBaseAddr, secname); |
1146 | | if (candidate != NULL) { |
1147 | | if (validator == NULL || validator(handle, (uintptr_t)candidate)) { |
1148 | | runtime_addr = candidate; |
1149 | | break; |
1150 | | } |
1151 | | if (_Py_RemoteDebug_HasPermissionError()) { |
1152 | | break; |
1153 | | } |
1154 | | } |
1155 | | else if (_Py_RemoteDebug_HasPermissionError()) { |
1156 | | break; |
1157 | | } |
1158 | | } |
1159 | | } while (Module32NextW(hProcSnap, &moduleEntry)); |
1160 | | |
1161 | | if (runtime_addr == NULL && !PyErr_Occurred()) { |
1162 | | DWORD error = GetLastError(); |
1163 | | if (error != ERROR_NO_MORE_FILES) { |
1164 | | PyErr_SetFromWindowsErr(error); |
1165 | | _set_debug_exception_cause(PyExc_OSError, |
1166 | | "Module enumeration failed for PID %d section '%s' " |
1167 | | "search (error %lu)", |
1168 | | handle->pid, secname, error); |
1169 | | } |
1170 | | } |
1171 | | |
1172 | | CloseHandle(hProcSnap); |
1173 | | |
1174 | | return (uintptr_t)runtime_addr; |
1175 | | } |
1176 | | |
1177 | | #endif // MS_WINDOWS |
1178 | | |
1179 | | // Get the PyRuntime section address for any platform |
1180 | | UNUSED static uintptr_t |
1181 | | _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) |
1182 | 0 | { |
1183 | 0 | uintptr_t address; |
1184 | |
|
1185 | | #ifdef MS_WINDOWS |
1186 | | // On Windows, search for 'python' in executable or DLL |
1187 | | address = search_windows_map_for_section(handle, "PyRuntime", L"python", |
1188 | | _Py_RemoteDebug_ValidatePyRuntimeCookie); |
1189 | | if (address == 0) { |
1190 | | if (!_Py_RemoteDebug_HasPermissionError()) { |
1191 | | // Error out: 'python' substring covers both executable and DLL |
1192 | | PyObject *exc = PyErr_GetRaisedException(); |
1193 | | PyErr_Format(PyExc_RuntimeError, |
1194 | | "Failed to find the PyRuntime section in process %d on Windows platform", |
1195 | | handle->pid); |
1196 | | _PyErr_ChainExceptions1(exc); |
1197 | | } |
1198 | | } |
1199 | | #elif defined(__linux__) && HAVE_PROCESS_VM_READV |
1200 | | // On Linux, search for 'python' in executable or DLL |
1201 | 0 | address = search_linux_map_for_section(handle, "PyRuntime", "python", |
1202 | 0 | _Py_RemoteDebug_ValidatePyRuntimeCookie); |
1203 | 0 | if (address == 0) { |
1204 | 0 | if (!_Py_RemoteDebug_HasPermissionError()) { |
1205 | | // Error out: 'python' substring covers both executable and DLL |
1206 | 0 | PyObject *exc = PyErr_GetRaisedException(); |
1207 | 0 | PyErr_Format(PyExc_RuntimeError, |
1208 | 0 | "Failed to find the PyRuntime section in process %d on Linux platform", |
1209 | 0 | handle->pid); |
1210 | 0 | _PyErr_ChainExceptions1(exc); |
1211 | 0 | } |
1212 | 0 | } |
1213 | | #elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX |
1214 | | // On macOS, try libpython first, then fall back to python |
1215 | | const char* candidates[] = {"libpython", "python", "Python", NULL}; |
1216 | | for (const char** candidate = candidates; *candidate; candidate++) { |
1217 | | PyErr_Clear(); |
1218 | | address = search_map_for_section(handle, "PyRuntime", *candidate, |
1219 | | _Py_RemoteDebug_ValidatePyRuntimeCookie); |
1220 | | if (address != 0 || _Py_RemoteDebug_HasPermissionError()) { |
1221 | | break; |
1222 | | } |
1223 | | } |
1224 | | if (address == 0) { |
1225 | | if (!_Py_RemoteDebug_HasPermissionError()) { |
1226 | | PyObject *exc = PyErr_GetRaisedException(); |
1227 | | PyErr_Format(PyExc_RuntimeError, |
1228 | | "Failed to find the PyRuntime section in process %d " |
1229 | | "on macOS platform (tried both libpython and python)", |
1230 | | handle->pid); |
1231 | | _PyErr_ChainExceptions1(exc); |
1232 | | } |
1233 | | } |
1234 | | #else |
1235 | | _set_debug_exception_cause(PyExc_RuntimeError, |
1236 | | "Reading the PyRuntime section is not supported on this platform"); |
1237 | | return 0; |
1238 | | #endif |
1239 | |
|
1240 | 0 | return address; |
1241 | 0 | } |
1242 | | |
1243 | | #if defined(__linux__) && HAVE_PROCESS_VM_READV |
1244 | | |
1245 | | static int |
1246 | | open_proc_mem_fd(proc_handle_t *handle) |
1247 | 0 | { |
1248 | 0 | char mem_file_path[64]; |
1249 | 0 | sprintf(mem_file_path, "/proc/%d/mem", handle->pid); |
1250 | |
|
1251 | 0 | handle->memfd = open(mem_file_path, O_RDWR); |
1252 | 0 | if (handle->memfd == -1) { |
1253 | 0 | int err = errno; |
1254 | 0 | _set_debug_oserror_from_errno_with_filename(err, mem_file_path, |
1255 | 0 | "failed to open file %s: %s", mem_file_path, strerror(err)); |
1256 | 0 | return -1; |
1257 | 0 | } |
1258 | 0 | return 0; |
1259 | 0 | } |
1260 | | |
1261 | | // Why is pwritev not guarded? Except on Android API level 23 (no longer |
1262 | | // supported), HAVE_PROCESS_VM_READV is sufficient. |
1263 | | static int |
1264 | | read_remote_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst) |
1265 | 0 | { |
1266 | 0 | if (len == 0) { |
1267 | 0 | return 0; |
1268 | 0 | } |
1269 | 0 | if (handle->memfd == -1) { |
1270 | 0 | if (open_proc_mem_fd(handle) < 0) { |
1271 | 0 | return -1; |
1272 | 0 | } |
1273 | 0 | } |
1274 | | |
1275 | 0 | struct iovec local[1]; |
1276 | 0 | Py_ssize_t result = 0; |
1277 | 0 | Py_ssize_t read_bytes = 0; |
1278 | |
|
1279 | 0 | do { |
1280 | 0 | local[0].iov_base = (char*)dst + result; |
1281 | 0 | local[0].iov_len = len - result; |
1282 | 0 | off_t offset = remote_address + result; |
1283 | |
|
1284 | 0 | read_bytes = preadv(handle->memfd, local, 1, offset); |
1285 | 0 | if (read_bytes < 0) { |
1286 | 0 | int err = errno; |
1287 | 0 | errno = err; |
1288 | 0 | PyErr_SetFromErrno(PyExc_OSError); |
1289 | 0 | _set_debug_exception_cause(PyExc_OSError, |
1290 | 0 | "preadv failed for PID %d at address 0x%lx " |
1291 | 0 | "(size %zu, partial read %zd bytes): %s", |
1292 | 0 | handle->pid, remote_address + result, len - result, result, strerror(err)); |
1293 | 0 | return -1; |
1294 | 0 | } |
1295 | | |
1296 | 0 | if (read_bytes == 0) { |
1297 | 0 | PyErr_Format(PyExc_OSError, |
1298 | 0 | "preadv returned 0 bytes for PID %d at address 0x%lx " |
1299 | 0 | "(size %zu, partial read %zd bytes)", |
1300 | 0 | handle->pid, remote_address + result, len - result, result); |
1301 | 0 | return -1; |
1302 | 0 | } |
1303 | 0 | result += read_bytes; |
1304 | 0 | } while ((size_t)read_bytes != local[0].iov_len); |
1305 | 0 | return 0; |
1306 | 0 | } |
1307 | | |
1308 | | #endif // __linux__ |
1309 | | |
1310 | | // Platform-independent memory read function |
1311 | | static int |
1312 | | _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst) |
1313 | 0 | { |
1314 | 0 | if (len == 0) { |
1315 | 0 | return 0; |
1316 | 0 | } |
1317 | | #ifdef MS_WINDOWS |
1318 | | SIZE_T read_bytes = 0; |
1319 | | SIZE_T result = 0; |
1320 | | do { |
1321 | | if (!ReadProcessMemory(handle->hProcess, (LPCVOID)(remote_address + result), (char*)dst + result, len - result, &read_bytes)) { |
1322 | | DWORD error = GetLastError(); |
1323 | | // Check if the process is still alive: we need to be able to tell our caller |
1324 | | // that the process is dead and not just that the read failed. |
1325 | | if (!is_process_alive(handle->hProcess)) { |
1326 | | _set_errno(ESRCH); |
1327 | | PyErr_SetFromErrno(PyExc_OSError); |
1328 | | return -1; |
1329 | | } |
1330 | | PyErr_SetFromWindowsErr(error); |
1331 | | _set_debug_exception_cause(PyExc_OSError, |
1332 | | "ReadProcessMemory failed for PID %d at address 0x%lx " |
1333 | | "(size %zu, partial read %zu bytes): Windows error %lu", |
1334 | | handle->pid, remote_address + result, len - result, result, error); |
1335 | | return -1; |
1336 | | } |
1337 | | if (read_bytes == 0) { |
1338 | | PyErr_Format(PyExc_OSError, |
1339 | | "ReadProcessMemory returned 0 bytes for PID %d at address 0x%lx " |
1340 | | "(size %zu, partial read %zu bytes)", |
1341 | | handle->pid, remote_address + result, len - result, result); |
1342 | | return -1; |
1343 | | } |
1344 | | result += read_bytes; |
1345 | | } while (result < len); |
1346 | | return 0; |
1347 | | #elif defined(__linux__) && HAVE_PROCESS_VM_READV |
1348 | 0 | if (handle->memfd != -1) { |
1349 | 0 | return read_remote_memory_fallback(handle, remote_address, len, dst); |
1350 | 0 | } |
1351 | 0 | struct iovec local[1]; |
1352 | 0 | struct iovec remote[1]; |
1353 | 0 | Py_ssize_t result = 0; |
1354 | 0 | Py_ssize_t read_bytes = 0; |
1355 | |
|
1356 | 0 | do { |
1357 | 0 | local[0].iov_base = (char*)dst + result; |
1358 | 0 | local[0].iov_len = len - result; |
1359 | 0 | remote[0].iov_base = (void*)(remote_address + result); |
1360 | 0 | remote[0].iov_len = len - result; |
1361 | |
|
1362 | 0 | read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0); |
1363 | 0 | if (read_bytes < 0) { |
1364 | 0 | int err = errno; |
1365 | 0 | if (err == ENOSYS) { |
1366 | 0 | return read_remote_memory_fallback(handle, remote_address, len, dst); |
1367 | 0 | } |
1368 | 0 | errno = err; |
1369 | 0 | PyErr_SetFromErrno(PyExc_OSError); |
1370 | 0 | if (err == ESRCH) { |
1371 | 0 | return -1; |
1372 | 0 | } |
1373 | 0 | _set_debug_exception_cause(PyExc_OSError, |
1374 | 0 | "process_vm_readv failed for PID %d at address 0x%lx " |
1375 | 0 | "(size %zu, partial read %zd bytes): %s", |
1376 | 0 | handle->pid, remote_address + result, len - result, result, strerror(err)); |
1377 | 0 | return -1; |
1378 | 0 | } |
1379 | | |
1380 | 0 | if (read_bytes == 0) { |
1381 | 0 | PyErr_Format(PyExc_OSError, |
1382 | 0 | "process_vm_readv returned 0 bytes for PID %d at address 0x%lx " |
1383 | 0 | "(size %zu, partial read %zd bytes)", |
1384 | 0 | handle->pid, remote_address + result, len - result, result); |
1385 | 0 | return -1; |
1386 | 0 | } |
1387 | 0 | result += read_bytes; |
1388 | 0 | } while ((size_t)read_bytes != local[0].iov_len); |
1389 | 0 | return 0; |
1390 | | #elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX |
1391 | | mach_vm_size_t bytes_read = 0; |
1392 | | kern_return_t kr = mach_vm_read_overwrite( |
1393 | | handle->task, |
1394 | | (mach_vm_address_t)remote_address, |
1395 | | len, |
1396 | | (mach_vm_address_t)dst, |
1397 | | &bytes_read); |
1398 | | |
1399 | | if (kr != KERN_SUCCESS) { |
1400 | | switch (err_get_code(kr)) { |
1401 | | case KERN_PROTECTION_FAILURE: |
1402 | | PyErr_Format(PyExc_PermissionError, |
1403 | | "Memory protection failure reading from PID %d at address " |
1404 | | "0x%lx (size %zu): insufficient permissions", |
1405 | | handle->pid, remote_address, len); |
1406 | | break; |
1407 | | case KERN_INVALID_ARGUMENT: { |
1408 | | // Perform a task_info check to see if the invalid argument is due |
1409 | | // to the process being terminated |
1410 | | task_basic_info_data_t task_basic_info; |
1411 | | mach_msg_type_number_t task_info_count = TASK_BASIC_INFO_COUNT; |
1412 | | kern_return_t task_valid_check = task_info(handle->task, TASK_BASIC_INFO, |
1413 | | (task_info_t)&task_basic_info, |
1414 | | &task_info_count); |
1415 | | if (task_valid_check == KERN_INVALID_ARGUMENT) { |
1416 | | PyErr_Format(PyExc_ProcessLookupError, |
1417 | | "Process %d is no longer accessible (process terminated)", |
1418 | | handle->pid); |
1419 | | } else { |
1420 | | PyErr_Format(PyExc_ValueError, |
1421 | | "Invalid argument to mach_vm_read_overwrite for PID %d at " |
1422 | | "address 0x%lx (size %zu) - check memory permissions", |
1423 | | handle->pid, remote_address, len); |
1424 | | } |
1425 | | break; |
1426 | | } |
1427 | | case KERN_NO_SPACE: |
1428 | | case KERN_MEMORY_ERROR: |
1429 | | PyErr_Format(PyExc_ProcessLookupError, |
1430 | | "Process %d memory space no longer available (process terminated)", |
1431 | | handle->pid); |
1432 | | break; |
1433 | | default: |
1434 | | PyErr_Format(PyExc_RuntimeError, |
1435 | | "mach_vm_read_overwrite failed for PID %d at address 0x%lx " |
1436 | | "(size %zu): kern_return_t %d", |
1437 | | handle->pid, remote_address, len, kr); |
1438 | | } |
1439 | | return -1; |
1440 | | } |
1441 | | if (bytes_read != (mach_vm_size_t)len) { |
1442 | | PyErr_Format(PyExc_OSError, |
1443 | | "mach_vm_read_overwrite read %llu of %zu bytes for PID %d at " |
1444 | | "address 0x%lx", |
1445 | | (unsigned long long)bytes_read, len, handle->pid, remote_address); |
1446 | | return -1; |
1447 | | } |
1448 | | return 0; |
1449 | | #else |
1450 | | Py_UNREACHABLE(); |
1451 | | #endif |
1452 | 0 | } |
1453 | | |
1454 | | #if defined(__linux__) && HAVE_PROCESS_VM_READV |
1455 | | // Fallback write using /proc/pid/mem |
1456 | | static int |
1457 | | _Py_RemoteDebug_WriteRemoteMemoryFallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src) |
1458 | 0 | { |
1459 | 0 | if (len == 0) { |
1460 | 0 | return 0; |
1461 | 0 | } |
1462 | 0 | if (handle->memfd == -1) { |
1463 | 0 | if (open_proc_mem_fd(handle) < 0) { |
1464 | 0 | return -1; |
1465 | 0 | } |
1466 | 0 | } |
1467 | | |
1468 | 0 | struct iovec local[1]; |
1469 | 0 | Py_ssize_t result = 0; |
1470 | 0 | Py_ssize_t written = 0; |
1471 | |
|
1472 | 0 | do { |
1473 | 0 | local[0].iov_base = (char*)src + result; |
1474 | 0 | local[0].iov_len = len - result; |
1475 | 0 | off_t offset = remote_address + result; |
1476 | |
|
1477 | 0 | written = pwritev(handle->memfd, local, 1, offset); |
1478 | 0 | if (written < 0) { |
1479 | 0 | int err = errno; |
1480 | 0 | errno = err; |
1481 | 0 | PyErr_SetFromErrno(PyExc_OSError); |
1482 | 0 | return -1; |
1483 | 0 | } |
1484 | | |
1485 | 0 | if (written == 0) { |
1486 | 0 | PyErr_Format(PyExc_OSError, |
1487 | 0 | "pwritev wrote 0 bytes for PID %d at address 0x%lx " |
1488 | 0 | "(size %zu, partial write %zd bytes)", |
1489 | 0 | handle->pid, remote_address + result, len - result, result); |
1490 | 0 | return -1; |
1491 | 0 | } |
1492 | 0 | result += written; |
1493 | 0 | } while ((size_t)written != local[0].iov_len); |
1494 | 0 | return 0; |
1495 | 0 | } |
1496 | | #endif // __linux__ |
1497 | | |
1498 | | // Platform-independent memory write function |
1499 | | UNUSED static int |
1500 | | _Py_RemoteDebug_WriteRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src) |
1501 | 0 | { |
1502 | 0 | if (len == 0) { |
1503 | 0 | return 0; |
1504 | 0 | } |
1505 | | #ifdef MS_WINDOWS |
1506 | | SIZE_T written = 0; |
1507 | | SIZE_T result = 0; |
1508 | | do { |
1509 | | if (!WriteProcessMemory(handle->hProcess, (LPVOID)(remote_address + result), (const char*)src + result, len - result, &written)) { |
1510 | | DWORD error = GetLastError(); |
1511 | | PyErr_SetFromWindowsErr(error); |
1512 | | _set_debug_exception_cause(PyExc_OSError, |
1513 | | "WriteProcessMemory failed for PID %d at address 0x%lx " |
1514 | | "(size %zu, partial write %zu bytes): Windows error %lu", |
1515 | | handle->pid, remote_address + result, len - result, result, error); |
1516 | | return -1; |
1517 | | } |
1518 | | if (written == 0) { |
1519 | | PyErr_Format(PyExc_OSError, |
1520 | | "WriteProcessMemory wrote 0 bytes for PID %d at address 0x%lx " |
1521 | | "(size %zu, partial write %zu bytes)", |
1522 | | handle->pid, remote_address + result, len - result, result); |
1523 | | return -1; |
1524 | | } |
1525 | | result += written; |
1526 | | } while (result < len); |
1527 | | return 0; |
1528 | | #elif defined(__linux__) && HAVE_PROCESS_VM_READV |
1529 | 0 | if (handle->memfd != -1) { |
1530 | 0 | return _Py_RemoteDebug_WriteRemoteMemoryFallback(handle, remote_address, len, src); |
1531 | 0 | } |
1532 | 0 | struct iovec local[1]; |
1533 | 0 | struct iovec remote[1]; |
1534 | 0 | Py_ssize_t result = 0; |
1535 | 0 | Py_ssize_t written = 0; |
1536 | |
|
1537 | 0 | do { |
1538 | 0 | local[0].iov_base = (void*)((char*)src + result); |
1539 | 0 | local[0].iov_len = len - result; |
1540 | 0 | remote[0].iov_base = (void*)((char*)remote_address + result); |
1541 | 0 | remote[0].iov_len = len - result; |
1542 | |
|
1543 | 0 | written = process_vm_writev(handle->pid, local, 1, remote, 1, 0); |
1544 | 0 | if (written < 0) { |
1545 | 0 | int err = errno; |
1546 | 0 | if (err == ENOSYS) { |
1547 | 0 | return _Py_RemoteDebug_WriteRemoteMemoryFallback(handle, remote_address, len, src); |
1548 | 0 | } |
1549 | 0 | errno = err; |
1550 | 0 | PyErr_SetFromErrno(PyExc_OSError); |
1551 | 0 | _set_debug_exception_cause(PyExc_OSError, |
1552 | 0 | "process_vm_writev failed for PID %d at address 0x%lx " |
1553 | 0 | "(size %zu, partial write %zd bytes): %s", |
1554 | 0 | handle->pid, remote_address + result, len - result, result, strerror(err)); |
1555 | 0 | return -1; |
1556 | 0 | } |
1557 | | |
1558 | 0 | if (written == 0) { |
1559 | 0 | PyErr_Format(PyExc_OSError, |
1560 | 0 | "process_vm_writev wrote 0 bytes for PID %d at address 0x%lx " |
1561 | 0 | "(size %zu, partial write %zd bytes)", |
1562 | 0 | handle->pid, remote_address + result, len - result, result); |
1563 | 0 | return -1; |
1564 | 0 | } |
1565 | 0 | result += written; |
1566 | 0 | } while ((size_t)written != local[0].iov_len); |
1567 | 0 | return 0; |
1568 | | #elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX |
1569 | | kern_return_t kr = mach_vm_write( |
1570 | | handle->task, |
1571 | | (mach_vm_address_t)remote_address, |
1572 | | (vm_offset_t)src, |
1573 | | (mach_msg_type_number_t)len); |
1574 | | |
1575 | | if (kr != KERN_SUCCESS) { |
1576 | | switch (kr) { |
1577 | | case KERN_PROTECTION_FAILURE: |
1578 | | PyErr_SetString(PyExc_PermissionError, "Not enough permissions to write memory"); |
1579 | | break; |
1580 | | case KERN_INVALID_ARGUMENT: |
1581 | | PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_write"); |
1582 | | break; |
1583 | | default: |
1584 | | PyErr_Format(PyExc_RuntimeError, "Unknown error writing memory: %d", (int)kr); |
1585 | | } |
1586 | | return -1; |
1587 | | } |
1588 | | return 0; |
1589 | | #else |
1590 | | Py_UNREACHABLE(); |
1591 | | #endif |
1592 | 0 | } |
1593 | | |
1594 | | UNUSED static int |
1595 | | _Py_RemoteDebug_PagedReadRemoteMemory(proc_handle_t *handle, |
1596 | | uintptr_t addr, |
1597 | | size_t size, |
1598 | | void *out) |
1599 | 0 | { |
1600 | 0 | size_t page_size = handle->page_size; |
1601 | 0 | uintptr_t page_base = addr & ~(page_size - 1); |
1602 | 0 | size_t offset_in_page = addr - page_base; |
1603 | 0 |
|
1604 | 0 | if (offset_in_page + size > page_size) { |
1605 | 0 | return _Py_RemoteDebug_ReadRemoteMemory(handle, addr, size, out); |
1606 | 0 | } |
1607 | 0 |
|
1608 | 0 | // Search only the pages used since the last clear. The cache is cleared |
1609 | 0 | // between profiler samples, so entries are packed at the front. |
1610 | 0 | for (int i = 0; i < handle->page_cache_count; i++) { |
1611 | 0 | page_cache_entry_t *entry = &handle->pages[i]; |
1612 | 0 | if (entry->valid && entry->page_addr == page_base) { |
1613 | 0 | memcpy(out, entry->data + offset_in_page, size); |
1614 | 0 | return 0; |
1615 | 0 | } |
1616 | 0 | } |
1617 | 0 |
|
1618 | 0 | if (handle->page_cache_count < MAX_PAGES) { |
1619 | 0 | page_cache_entry_t *entry = &handle->pages[handle->page_cache_count]; |
1620 | 0 | if (entry->data == NULL) { |
1621 | 0 | entry->data = PyMem_RawMalloc(page_size); |
1622 | 0 | if (entry->data == NULL) { |
1623 | 0 | PyErr_NoMemory(); |
1624 | 0 | _set_debug_exception_cause(PyExc_MemoryError, |
1625 | 0 | "Cannot allocate %zu bytes for page cache entry " |
1626 | 0 | "during read from PID %d at address 0x%lx", |
1627 | 0 | page_size, handle->pid, addr); |
1628 | 0 | return -1; |
1629 | 0 | } |
1630 | 0 | } |
1631 | 0 |
|
1632 | 0 | if (_Py_RemoteDebug_ReadRemoteMemory(handle, page_base, page_size, entry->data) < 0) { |
1633 | 0 | // Try to just copy the exact amount as a fallback |
1634 | 0 | PyErr_Clear(); |
1635 | 0 | goto fallback; |
1636 | 0 | } |
1637 | 0 |
|
1638 | 0 | entry->page_addr = page_base; |
1639 | 0 | entry->valid = 1; |
1640 | 0 | handle->page_cache_count++; |
1641 | 0 | memcpy(out, entry->data + offset_in_page, size); |
1642 | 0 | return 0; |
1643 | 0 | } |
1644 | 0 |
|
1645 | 0 | fallback: |
1646 | 0 | // Cache full — fallback to uncached read |
1647 | 0 | return _Py_RemoteDebug_ReadRemoteMemory(handle, addr, size, out); |
1648 | 0 | } |
1649 | | |
1650 | | typedef struct { |
1651 | | uintptr_t remote_addr; |
1652 | | void *local_buf; |
1653 | | size_t size; |
1654 | | } _Py_RemoteReadSegment; |
1655 | | |
1656 | | #define _PY_REMOTE_DEBUG_MAX_BATCHED_SEGMENTS 4 |
1657 | | |
1658 | | // Batched read of multiple remote regions in a single syscall when supported. |
1659 | | // Returns total bytes read (>= 0) on success, -1 if batched reads are |
1660 | | // unavailable or the syscall failed. Callers compare the return value against |
1661 | | // cumulative segment sizes to determine which segments were fully populated. |
1662 | | UNUSED static Py_ssize_t |
1663 | | _Py_RemoteDebug_BatchedReadRemoteMemory( |
1664 | | proc_handle_t *handle, |
1665 | | const _Py_RemoteReadSegment *segments, |
1666 | | int nsegs) |
1667 | 0 | { |
1668 | 0 | #if defined(__linux__) && HAVE_PROCESS_VM_READV |
1669 | 0 | if (handle->memfd == -1 |
1670 | 0 | && nsegs > 0 |
1671 | 0 | && nsegs <= _PY_REMOTE_DEBUG_MAX_BATCHED_SEGMENTS) { |
1672 | 0 | struct iovec local[_PY_REMOTE_DEBUG_MAX_BATCHED_SEGMENTS]; |
1673 | 0 | struct iovec remote[_PY_REMOTE_DEBUG_MAX_BATCHED_SEGMENTS]; |
1674 | 0 | for (int i = 0; i < nsegs; i++) { |
1675 | 0 | local[i].iov_base = segments[i].local_buf; |
1676 | 0 | local[i].iov_len = segments[i].size; |
1677 | 0 | remote[i].iov_base = (void *)segments[i].remote_addr; |
1678 | 0 | remote[i].iov_len = segments[i].size; |
1679 | 0 | } |
1680 | 0 | ssize_t nread = process_vm_readv(handle->pid, local, nsegs, remote, nsegs, 0); |
1681 | 0 | if (nread >= 0) { |
1682 | 0 | return (Py_ssize_t)nread; |
1683 | 0 | } |
1684 | 0 | } |
1685 | 0 | #else |
1686 | 0 | (void)handle; |
1687 | 0 | (void)segments; |
1688 | 0 | (void)nsegs; |
1689 | 0 | #endif |
1690 | 0 | return -1; |
1691 | 0 | } |
1692 | | |
1693 | | UNUSED static int |
1694 | | _Py_RemoteDebug_ReadDebugOffsets( |
1695 | | proc_handle_t *handle, |
1696 | | uintptr_t *runtime_start_address, |
1697 | | _Py_DebugOffsets* debug_offsets |
1698 | 0 | ) { |
1699 | 0 | *runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle); |
1700 | 0 | if (!*runtime_start_address) { |
1701 | 0 | if (!PyErr_Occurred()) { |
1702 | 0 | PyErr_Format(PyExc_RuntimeError, |
1703 | 0 | "Failed to locate PyRuntime address for PID %d", |
1704 | 0 | handle->pid); |
1705 | 0 | } |
1706 | 0 | _set_debug_exception_cause(PyExc_RuntimeError, "PyRuntime address lookup failed during debug offsets initialization"); |
1707 | 0 | return -1; |
1708 | 0 | } |
1709 | 0 | size_t size = sizeof(struct _Py_DebugOffsets); |
1710 | 0 | if (0 != _Py_RemoteDebug_ReadRemoteMemory(handle, *runtime_start_address, size, debug_offsets)) { |
1711 | 0 | _set_debug_exception_cause(PyExc_RuntimeError, "Failed to read debug offsets structure from remote process"); |
1712 | 0 | return -1; |
1713 | 0 | } |
1714 | 0 | return 0; |
1715 | 0 | } |
1716 | | |
1717 | | #ifdef __cplusplus |
1718 | | } |
1719 | | #endif |