Coverage Report

Created: 2026-10-17 06:15

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