Coverage Report

Created: 2026-05-30 06:18

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