Coverage Report

Created: 2026-01-17 06:16

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