Coverage Report

Created: 2026-09-27 06:15

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