Coverage Report

Created: 2026-02-26 06:53

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