Coverage Report

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