Coverage Report

Created: 2025-07-04 06:49

/src/cpython/Python/remote_debug.h
Line
Count
Source (jump to first uncovered line)
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
#    if INTPTR_MAX == INT64_MAX
37
0
#        define Elf_Ehdr Elf64_Ehdr
38
0
#        define Elf_Shdr Elf64_Shdr
39
0
#        define Elf_Phdr Elf64_Phdr
40
#    else
41
#        define Elf_Ehdr Elf32_Ehdr
42
#        define Elf_Shdr Elf32_Shdr
43
#        define Elf_Phdr Elf32_Phdr
44
#    endif
45
#    include <sys/mman.h>
46
#endif
47
48
#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
49
#  include <libproc.h>
50
#  include <mach-o/fat.h>
51
#  include <mach-o/loader.h>
52
#  include <mach-o/nlist.h>
53
#  include <mach/mach.h>
54
#  include <mach/mach_vm.h>
55
#  include <mach/machine.h>
56
#  include <sys/mman.h>
57
#  include <sys/proc.h>
58
#  include <sys/sysctl.h>
59
#endif
60
61
#ifdef MS_WINDOWS
62
    // Windows includes and definitions
63
#include <windows.h>
64
#include <psapi.h>
65
#include <tlhelp32.h>
66
#endif
67
68
#include <errno.h>
69
#include <fcntl.h>
70
#include <stddef.h>
71
#include <stdint.h>
72
#include <stdio.h>
73
#include <stdlib.h>
74
#include <string.h>
75
#ifndef MS_WINDOWS
76
#include <sys/param.h>
77
#include <sys/stat.h>
78
#include <sys/types.h>
79
#include <unistd.h>
80
#endif
81
82
#ifndef HAVE_PROCESS_VM_READV
83
#    define HAVE_PROCESS_VM_READV 0
84
#endif
85
86
#define _set_debug_exception_cause(exception, format, ...) \
87
0
    do { \
88
0
        if (!PyErr_ExceptionMatches(PyExc_PermissionError)) { \
89
0
            PyThreadState *tstate = _PyThreadState_GET(); \
90
0
            if (!_PyErr_Occurred(tstate)) { \
91
0
                _PyErr_Format(tstate, exception, format, ##__VA_ARGS__); \
92
0
            } else { \
93
0
                _PyErr_FormatFromCause(exception, format, ##__VA_ARGS__); \
94
0
            } \
95
0
        } \
96
0
    } while (0)
97
98
static inline size_t
99
0
get_page_size(void) {
100
0
    size_t page_size = 0;
101
0
    if (page_size == 0) {
102
#ifdef MS_WINDOWS
103
        SYSTEM_INFO si;
104
        GetSystemInfo(&si);
105
        page_size = si.dwPageSize;
106
#else
107
0
        page_size = (size_t)getpagesize();
108
0
#endif
109
0
    }
110
0
    return page_size;
111
0
}
112
113
114
// Define a platform-independent process handle structure
115
typedef struct {
116
    pid_t pid;
117
#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
118
    mach_port_t task;
119
#elif defined(MS_WINDOWS)
120
    HANDLE hProcess;
121
#elif defined(__linux__)
122
    int memfd;
123
#endif
124
    Py_ssize_t page_size;
125
} proc_handle_t;
126
127
128
#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
129
static mach_port_t pid_to_task(pid_t pid);
130
#endif
131
132
// Initialize the process handle
133
static int
134
0
_Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, pid_t pid) {
135
0
    handle->pid = pid;
136
#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
137
    handle->task = pid_to_task(handle->pid);
138
    if (handle->task == 0) {
139
        _set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize macOS process handle");
140
        return -1;
141
    }
142
#elif defined(MS_WINDOWS)
143
    handle->hProcess = OpenProcess(
144
        PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION,
145
        FALSE, pid);
146
    if (handle->hProcess == NULL) {
147
        PyErr_SetFromWindowsErr(0);
148
        _set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize Windows process handle");
149
        return -1;
150
    }
151
#elif defined(__linux__)
152
    handle->memfd = -1;
153
0
#endif
154
0
    handle->page_size = get_page_size();
155
0
    return 0;
156
0
}
157
158
// Clean up the process handle
159
static void
160
0
_Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) {
161
#ifdef MS_WINDOWS
162
    if (handle->hProcess != NULL) {
163
        CloseHandle(handle->hProcess);
164
        handle->hProcess = NULL;
165
    }
166
#elif defined(__linux__)
167
0
    if (handle->memfd != -1) {
168
0
        close(handle->memfd);
169
0
        handle->memfd = -1;
170
0
    }
171
0
#endif
172
0
    handle->pid = 0;
173
0
}
174
175
#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
176
177
static uintptr_t
178
return_section_address64(
179
    const char* section,
180
    mach_port_t proc_ref,
181
    uintptr_t base,
182
    void* map
183
) {
184
    struct mach_header_64* hdr = (struct mach_header_64*)map;
185
    int ncmds = hdr->ncmds;
186
187
    int cmd_cnt = 0;
188
    struct segment_command_64* cmd = map + sizeof(struct mach_header_64);
189
190
    mach_vm_size_t size = 0;
191
    mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
192
    mach_vm_address_t address = (mach_vm_address_t)base;
193
    vm_region_basic_info_data_64_t r_info;
194
    mach_port_t object_name;
195
    uintptr_t vmaddr = 0;
196
197
    for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
198
        if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__TEXT") == 0) {
199
            vmaddr = cmd->vmaddr;
200
        }
201
        if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__DATA") == 0) {
202
            while (cmd->filesize != size) {
203
                address += size;
204
                kern_return_t ret = mach_vm_region(
205
                    proc_ref,
206
                    &address,
207
                    &size,
208
                    VM_REGION_BASIC_INFO_64,
209
                    (vm_region_info_t)&r_info,  // cppcheck-suppress [uninitvar]
210
                    &count,
211
                    &object_name
212
                );
213
                if (ret != KERN_SUCCESS) {
214
                    PyErr_Format(PyExc_RuntimeError,
215
                        "mach_vm_region failed while parsing 64-bit Mach-O binary "
216
                        "at base address 0x%lx (kern_return_t: %d)",
217
                        base, ret);
218
                    return 0;
219
                }
220
            }
221
222
            int nsects = cmd->nsects;
223
            struct section_64* sec = (struct section_64*)(
224
                (void*)cmd + sizeof(struct segment_command_64)
225
                );
226
            for (int j = 0; j < nsects; j++) {
227
                if (strcmp(sec[j].sectname, section) == 0) {
228
                    return base + sec[j].addr - vmaddr;
229
                }
230
            }
231
            cmd_cnt++;
232
        }
233
234
        cmd = (struct segment_command_64*)((void*)cmd + cmd->cmdsize);
235
    }
236
237
    return 0;
238
}
239
240
static uintptr_t
241
return_section_address32(
242
    const char* section,
243
    mach_port_t proc_ref,
244
    uintptr_t base,
245
    void* map
246
) {
247
    struct mach_header* hdr = (struct mach_header*)map;
248
    int ncmds = hdr->ncmds;
249
250
    int cmd_cnt = 0;
251
    struct segment_command* cmd = map + sizeof(struct mach_header);
252
253
    mach_vm_size_t size = 0;
254
    mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_t);
255
    mach_vm_address_t address = (mach_vm_address_t)base;
256
    vm_region_basic_info_data_t r_info;
257
    mach_port_t object_name;
258
    uintptr_t vmaddr = 0;
259
260
    for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
261
        if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__TEXT") == 0) {
262
            vmaddr = cmd->vmaddr;
263
        }
264
        if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__DATA") == 0) {
265
            while (cmd->filesize != size) {
266
                address += size;
267
                kern_return_t ret = mach_vm_region(
268
                    proc_ref,
269
                    &address,
270
                    &size,
271
                    VM_REGION_BASIC_INFO,
272
                    (vm_region_info_t)&r_info,  // cppcheck-suppress [uninitvar]
273
                    &count,
274
                    &object_name
275
                );
276
                if (ret != KERN_SUCCESS) {
277
                    PyErr_Format(PyExc_RuntimeError,
278
                        "mach_vm_region failed while parsing 32-bit Mach-O binary "
279
                        "at base address 0x%lx (kern_return_t: %d)",
280
                        base, ret);
281
                    return 0;
282
                }
283
            }
284
285
            int nsects = cmd->nsects;
286
            struct section* sec = (struct section*)(
287
                (void*)cmd + sizeof(struct segment_command)
288
                );
289
            for (int j = 0; j < nsects; j++) {
290
                if (strcmp(sec[j].sectname, section) == 0) {
291
                    return base + sec[j].addr - vmaddr;
292
                }
293
            }
294
            cmd_cnt++;
295
        }
296
297
        cmd = (struct segment_command*)((void*)cmd + cmd->cmdsize);
298
    }
299
300
    return 0;
301
}
302
303
static uintptr_t
304
return_section_address_fat(
305
    const char* section,
306
    mach_port_t proc_ref,
307
    uintptr_t base,
308
    void* map
309
) {
310
    struct fat_header* fat_hdr = (struct fat_header*)map;
311
312
    // Determine host CPU type for architecture selection
313
    cpu_type_t cpu;
314
    int is_abi64;
315
    size_t cpu_size = sizeof(cpu), abi64_size = sizeof(is_abi64);
316
317
    if (sysctlbyname("hw.cputype", &cpu, &cpu_size, NULL, 0) != 0) {
318
        PyErr_Format(PyExc_OSError,
319
            "Failed to determine CPU type via sysctlbyname "
320
            "for fat binary analysis at 0x%lx: %s",
321
            base, strerror(errno));
322
        return 0;
323
    }
324
    if (sysctlbyname("hw.cpu64bit_capable", &is_abi64, &abi64_size, NULL, 0) != 0) {
325
        PyErr_Format(PyExc_OSError,
326
            "Failed to determine CPU ABI capability via sysctlbyname "
327
            "for fat binary analysis at 0x%lx: %s",
328
            base, strerror(errno));
329
        return 0;
330
    }
331
332
    cpu |= is_abi64 * CPU_ARCH_ABI64;
333
334
    // Check endianness
335
    int swap = fat_hdr->magic == FAT_CIGAM;
336
    struct fat_arch* arch = (struct fat_arch*)(map + sizeof(struct fat_header));
337
338
    // Get number of architectures in fat binary
339
    uint32_t nfat_arch = swap ? __builtin_bswap32(fat_hdr->nfat_arch) : fat_hdr->nfat_arch;
340
341
    // Search for matching architecture
342
    for (uint32_t i = 0; i < nfat_arch; i++) {
343
        cpu_type_t arch_cpu = swap ? __builtin_bswap32(arch[i].cputype) : arch[i].cputype;
344
345
        if (arch_cpu == cpu) {
346
            // Found matching architecture, now process it
347
            uint32_t offset = swap ? __builtin_bswap32(arch[i].offset) : arch[i].offset;
348
            struct mach_header_64* hdr = (struct mach_header_64*)(map + offset);
349
350
            // Determine which type of Mach-O it is and process accordingly
351
            switch (hdr->magic) {
352
                case MH_MAGIC:
353
                case MH_CIGAM:
354
                    return return_section_address32(section, proc_ref, base, (void*)hdr);
355
356
                case MH_MAGIC_64:
357
                case MH_CIGAM_64:
358
                    return return_section_address64(section, proc_ref, base, (void*)hdr);
359
360
                default:
361
                    PyErr_Format(PyExc_RuntimeError,
362
                        "Unknown Mach-O magic number 0x%x in fat binary architecture %u at base 0x%lx",
363
                        hdr->magic, i, base);
364
                    return 0;
365
            }
366
        }
367
    }
368
369
    PyErr_Format(PyExc_RuntimeError,
370
        "No matching architecture found for CPU type 0x%x "
371
        "in fat binary at base 0x%lx (%u architectures examined)",
372
        cpu, base, nfat_arch);
373
    return 0;
374
}
375
376
static uintptr_t
377
search_section_in_file(const char* secname, char* path, uintptr_t base, mach_vm_size_t size, mach_port_t proc_ref)
378
{
379
    int fd = open(path, O_RDONLY);
380
    if (fd == -1) {
381
        PyErr_Format(PyExc_OSError,
382
            "Cannot open binary file '%s' for section '%s' search: %s",
383
            path, secname, strerror(errno));
384
        return 0;
385
    }
386
387
    struct stat fs;
388
    if (fstat(fd, &fs) == -1) {
389
        PyErr_Format(PyExc_OSError,
390
            "Cannot get file size for binary '%s' during section '%s' search: %s",
391
            path, secname, strerror(errno));
392
        close(fd);
393
        return 0;
394
    }
395
396
    void* map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0);
397
    if (map == MAP_FAILED) {
398
        PyErr_Format(PyExc_OSError,
399
            "Cannot memory map binary file '%s' (size: %lld bytes) for section '%s' search: %s",
400
            path, (long long)fs.st_size, secname, strerror(errno));
401
        close(fd);
402
        return 0;
403
    }
404
405
    uintptr_t result = 0;
406
    uint32_t magic = *(uint32_t*)map;
407
408
    switch (magic) {
409
    case MH_MAGIC:
410
    case MH_CIGAM:
411
        result = return_section_address32(secname, proc_ref, base, map);
412
        break;
413
    case MH_MAGIC_64:
414
    case MH_CIGAM_64:
415
        result = return_section_address64(secname, proc_ref, base, map);
416
        break;
417
    case FAT_MAGIC:
418
    case FAT_CIGAM:
419
        result = return_section_address_fat(secname, proc_ref, base, map);
420
        break;
421
    default:
422
        PyErr_Format(PyExc_RuntimeError,
423
            "Unrecognized Mach-O magic number 0x%x in binary file '%s' for section '%s' search",
424
            magic, path, secname);
425
        break;
426
    }
427
428
    if (munmap(map, fs.st_size) != 0) {
429
        PyErr_Format(PyExc_OSError,
430
            "Failed to unmap binary file '%s' (size: %lld bytes): %s",
431
            path, (long long)fs.st_size, strerror(errno));
432
        result = 0;
433
    }
434
    if (close(fd) != 0) {
435
        PyErr_Format(PyExc_OSError,
436
            "Failed to close binary file '%s': %s",
437
            path, strerror(errno));
438
        result = 0;
439
    }
440
    return result;
441
}
442
443
444
static mach_port_t
445
pid_to_task(pid_t pid)
446
{
447
    mach_port_t task;
448
    kern_return_t result;
449
450
    result = task_for_pid(mach_task_self(), pid, &task);
451
    if (result != KERN_SUCCESS) {
452
        PyErr_Format(PyExc_PermissionError,
453
            "Cannot get task port for PID %d (kern_return_t: %d). "
454
            "This typically requires running as root or having the 'com.apple.system-task-ports' entitlement.",
455
            pid, result);
456
        return 0;
457
    }
458
    return task;
459
}
460
461
static uintptr_t
462
search_map_for_section(proc_handle_t *handle, const char* secname, const char* substr) {
463
    mach_vm_address_t address = 0;
464
    mach_vm_size_t size = 0;
465
    mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
466
    vm_region_basic_info_data_64_t region_info;
467
    mach_port_t object_name;
468
469
    mach_port_t proc_ref = pid_to_task(handle->pid);
470
    if (proc_ref == 0) {
471
        if (!PyErr_Occurred()) {
472
            PyErr_Format(PyExc_PermissionError,
473
                "Cannot get task port for PID %d during section search",
474
                handle->pid);
475
        }
476
        return 0;
477
    }
478
479
    char map_filename[MAXPATHLEN + 1];
480
481
    while (mach_vm_region(
482
        proc_ref,
483
        &address,
484
        &size,
485
        VM_REGION_BASIC_INFO_64,
486
        (vm_region_info_t)&region_info,
487
        &count,
488
        &object_name) == KERN_SUCCESS)
489
    {
490
491
        if ((region_info.protection & VM_PROT_READ) == 0
492
            || (region_info.protection & VM_PROT_EXECUTE) == 0) {
493
            address += size;
494
            continue;
495
        }
496
497
        int path_len = proc_regionfilename(
498
            handle->pid, address, map_filename, MAXPATHLEN);
499
        if (path_len == 0) {
500
            address += size;
501
            continue;
502
        }
503
504
        char* filename = strrchr(map_filename, '/');
505
        if (filename != NULL) {
506
            filename++;  // Move past the '/'
507
        } else {
508
            filename = map_filename;  // No path, use the whole string
509
        }
510
511
        if (strncmp(filename, substr, strlen(substr)) == 0) {
512
            uintptr_t result = search_section_in_file(
513
                secname, map_filename, address, size, proc_ref);
514
            if (result != 0) {
515
                return result;
516
            }
517
        }
518
519
        address += size;
520
    }
521
522
    return 0;
523
}
524
525
#endif // (__APPLE__ && defined(TARGET_OS_OSX) && TARGET_OS_OSX)
526
527
#if defined(__linux__) && HAVE_PROCESS_VM_READV
528
static uintptr_t
529
search_elf_file_for_section(
530
        proc_handle_t *handle,
531
        const char* secname,
532
        uintptr_t start_address,
533
        const char *elf_file)
534
0
{
535
0
    if (start_address == 0) {
536
0
        return 0;
537
0
    }
538
539
0
    uintptr_t result = 0;
540
0
    void* file_memory = NULL;
541
542
0
    int fd = open(elf_file, O_RDONLY);
543
0
    if (fd < 0) {
544
0
        PyErr_Format(PyExc_OSError,
545
0
            "Cannot open ELF file '%s' for section '%s' search: %s",
546
0
            elf_file, secname, strerror(errno));
547
0
        goto exit;
548
0
    }
549
550
0
    struct stat file_stats;
551
0
    if (fstat(fd, &file_stats) != 0) {
552
0
        PyErr_Format(PyExc_OSError,
553
0
            "Cannot get file size for ELF file '%s' during section '%s' search: %s",
554
0
            elf_file, secname, strerror(errno));
555
0
        goto exit;
556
0
    }
557
558
0
    file_memory = mmap(NULL, file_stats.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
559
0
    if (file_memory == MAP_FAILED) {
560
0
        PyErr_Format(PyExc_OSError,
561
0
            "Cannot memory map ELF file '%s' (size: %lld bytes) for section '%s' search: %s",
562
0
            elf_file, (long long)file_stats.st_size, secname, strerror(errno));
563
0
        goto exit;
564
0
    }
565
566
0
    Elf_Ehdr* elf_header = (Elf_Ehdr*)file_memory;
567
568
    // Validate ELF header
569
0
    if (elf_header->e_shstrndx >= elf_header->e_shnum) {
570
0
        PyErr_Format(PyExc_RuntimeError,
571
0
            "Invalid ELF file '%s': string table index %u >= section count %u",
572
0
            elf_file, elf_header->e_shstrndx, elf_header->e_shnum);
573
0
        goto exit;
574
0
    }
575
576
0
    Elf_Shdr* section_header_table = (Elf_Shdr*)(file_memory + elf_header->e_shoff);
577
578
0
    Elf_Shdr* shstrtab_section = &section_header_table[elf_header->e_shstrndx];
579
0
    char* shstrtab = (char*)(file_memory + shstrtab_section->sh_offset);
580
581
0
    Elf_Shdr* section = NULL;
582
0
    for (int i = 0; i < elf_header->e_shnum; i++) {
583
0
        char* this_sec_name = shstrtab + section_header_table[i].sh_name;
584
        // Move 1 character to account for the leading "."
585
0
        this_sec_name += 1;
586
0
        if (strcmp(secname, this_sec_name) == 0) {
587
0
            section = &section_header_table[i];
588
0
            break;
589
0
        }
590
0
    }
591
592
0
    if (section == NULL) {
593
0
        goto exit;
594
0
    }
595
596
0
    Elf_Phdr* program_header_table = (Elf_Phdr*)(file_memory + elf_header->e_phoff);
597
    // Find the first PT_LOAD segment
598
0
    Elf_Phdr* first_load_segment = NULL;
599
0
    for (int i = 0; i < elf_header->e_phnum; i++) {
600
0
        if (program_header_table[i].p_type == PT_LOAD) {
601
0
            first_load_segment = &program_header_table[i];
602
0
            break;
603
0
        }
604
0
    }
605
606
0
    if (first_load_segment == NULL) {
607
0
        PyErr_Format(PyExc_RuntimeError,
608
0
            "No PT_LOAD segment found in ELF file '%s' (%u program headers examined)",
609
0
            elf_file, elf_header->e_phnum);
610
0
        goto exit;
611
0
    }
612
613
0
    uintptr_t elf_load_addr = first_load_segment->p_vaddr
614
0
        - (first_load_segment->p_vaddr % first_load_segment->p_align);
615
0
    result = start_address + (uintptr_t)section->sh_addr - elf_load_addr;
616
617
0
exit:
618
0
    if (file_memory != NULL) {
619
0
        munmap(file_memory, file_stats.st_size);
620
0
    }
621
0
    if (fd >= 0 && close(fd) != 0) {
622
0
        PyErr_Format(PyExc_OSError,
623
0
            "Failed to close ELF file '%s': %s",
624
0
            elf_file, strerror(errno));
625
0
        result = 0;
626
0
    }
627
0
    return result;
628
0
}
629
630
static uintptr_t
631
search_linux_map_for_section(proc_handle_t *handle, const char* secname, const char* substr)
632
0
{
633
0
    char maps_file_path[64];
634
0
    sprintf(maps_file_path, "/proc/%d/maps", handle->pid);
635
636
0
    FILE* maps_file = fopen(maps_file_path, "r");
637
0
    if (maps_file == NULL) {
638
0
        PyErr_Format(PyExc_OSError,
639
0
            "Cannot open process memory map file '%s' for PID %d section search: %s",
640
0
            maps_file_path, handle->pid, strerror(errno));
641
0
        return 0;
642
0
    }
643
644
0
    size_t linelen = 0;
645
0
    size_t linesz = PATH_MAX;
646
0
    char *line = PyMem_Malloc(linesz);
647
0
    if (!line) {
648
0
        fclose(maps_file);
649
0
        _set_debug_exception_cause(PyExc_MemoryError,
650
0
            "Cannot allocate memory for reading process map file '%s'",
651
0
            maps_file_path);
652
0
        return 0;
653
0
    }
654
655
0
    uintptr_t retval = 0;
656
657
0
    while (fgets(line + linelen, linesz - linelen, maps_file) != NULL) {
658
0
        linelen = strlen(line);
659
0
        if (line[linelen - 1] != '\n') {
660
            // Read a partial line: realloc and keep reading where we left off.
661
            // Note that even the last line will be terminated by a newline.
662
0
            linesz *= 2;
663
0
            char *biggerline = PyMem_Realloc(line, linesz);
664
0
            if (!biggerline) {
665
0
                PyMem_Free(line);
666
0
                fclose(maps_file);
667
0
                _set_debug_exception_cause(PyExc_MemoryError,
668
0
                    "Cannot reallocate memory while reading process map file '%s' (attempted size: %zu)",
669
0
                    maps_file_path, linesz);
670
0
                return 0;
671
0
            }
672
0
            line = biggerline;
673
0
            continue;
674
0
        }
675
676
        // Read a full line: strip the newline
677
0
        line[linelen - 1] = '\0';
678
        // and prepare to read the next line into the start of the buffer.
679
0
        linelen = 0;
680
681
0
        unsigned long start = 0;
682
0
        unsigned long path_pos = 0;
683
0
        sscanf(line, "%lx-%*x %*s %*s %*s %*s %ln", &start, &path_pos);
684
685
0
        if (!path_pos) {
686
            // Line didn't match our format string.  This shouldn't be
687
            // possible, but let's be defensive and skip the line.
688
0
            continue;
689
0
        }
690
691
0
        const char *path = line + path_pos;
692
0
        const char *filename = strrchr(path, '/');
693
0
        if (filename) {
694
0
            filename++;  // Move past the '/'
695
0
        } else {
696
0
            filename = path;  // No directories, or an empty string
697
0
        }
698
699
0
        if (strstr(filename, substr)) {
700
0
            retval = search_elf_file_for_section(handle, secname, start, path);
701
0
            if (retval) {
702
0
                break;
703
0
            }
704
0
        }
705
0
    }
706
707
0
    PyMem_Free(line);
708
0
    if (fclose(maps_file) != 0) {
709
0
        PyErr_Format(PyExc_OSError,
710
0
            "Failed to close process map file '%s': %s",
711
0
            maps_file_path, strerror(errno));
712
0
        retval = 0;
713
0
    }
714
715
0
    return retval;
716
0
}
717
718
719
#endif // __linux__
720
721
#ifdef MS_WINDOWS
722
723
static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char* secname) {
724
    HANDLE hFile = CreateFileW(mod_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
725
    if (hFile == INVALID_HANDLE_VALUE) {
726
        PyErr_SetFromWindowsErr(0);
727
        DWORD error = GetLastError();
728
        PyErr_Format(PyExc_OSError,
729
            "Cannot open PE file for section '%s' analysis (error %lu)",
730
            secname, error);
731
        return NULL;
732
    }
733
734
    HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0);
735
    if (!hMap) {
736
        PyErr_SetFromWindowsErr(0);
737
        DWORD error = GetLastError();
738
        PyErr_Format(PyExc_OSError,
739
            "Cannot create file mapping for PE file section '%s' analysis (error %lu)",
740
            secname, error);
741
        CloseHandle(hFile);
742
        return NULL;
743
    }
744
745
    BYTE* mapView = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
746
    if (!mapView) {
747
        PyErr_SetFromWindowsErr(0);
748
        DWORD error = GetLastError();
749
        PyErr_Format(PyExc_OSError,
750
            "Cannot map view of PE file for section '%s' analysis (error %lu)",
751
            secname, error);
752
        CloseHandle(hMap);
753
        CloseHandle(hFile);
754
        return NULL;
755
    }
756
757
    IMAGE_DOS_HEADER* pDOSHeader = (IMAGE_DOS_HEADER*)mapView;
758
    if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) {
759
        PyErr_Format(PyExc_RuntimeError,
760
            "Invalid DOS signature (0x%x) in PE file for section '%s' analysis (expected 0x%x)",
761
            pDOSHeader->e_magic, secname, IMAGE_DOS_SIGNATURE);
762
        UnmapViewOfFile(mapView);
763
        CloseHandle(hMap);
764
        CloseHandle(hFile);
765
        return NULL;
766
    }
767
768
    IMAGE_NT_HEADERS* pNTHeaders = (IMAGE_NT_HEADERS*)(mapView + pDOSHeader->e_lfanew);
769
    if (pNTHeaders->Signature != IMAGE_NT_SIGNATURE) {
770
        PyErr_Format(PyExc_RuntimeError,
771
            "Invalid NT signature (0x%lx) in PE file for section '%s' analysis (expected 0x%lx)",
772
            pNTHeaders->Signature, secname, IMAGE_NT_SIGNATURE);
773
        UnmapViewOfFile(mapView);
774
        CloseHandle(hMap);
775
        CloseHandle(hFile);
776
        return NULL;
777
    }
778
779
    IMAGE_SECTION_HEADER* pSection_header = (IMAGE_SECTION_HEADER*)(mapView + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
780
    void* runtime_addr = NULL;
781
782
    for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) {
783
        const char* name = (const char*)pSection_header[i].Name;
784
        if (strncmp(name, secname, IMAGE_SIZEOF_SHORT_NAME) == 0) {
785
            runtime_addr = remote_base + pSection_header[i].VirtualAddress;
786
            break;
787
        }
788
    }
789
790
    UnmapViewOfFile(mapView);
791
    CloseHandle(hMap);
792
    CloseHandle(hFile);
793
794
    return runtime_addr;
795
}
796
797
798
static uintptr_t
799
search_windows_map_for_section(proc_handle_t* handle, const char* secname, const wchar_t* substr) {
800
    HANDLE hProcSnap;
801
    do {
802
        hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, handle->pid);
803
    } while (hProcSnap == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH);
804
805
    if (hProcSnap == INVALID_HANDLE_VALUE) {
806
        PyErr_SetFromWindowsErr(0);
807
        DWORD error = GetLastError();
808
        PyErr_Format(PyExc_PermissionError,
809
            "Unable to create module snapshot for PID %d section '%s' "
810
            "search (error %lu). Check permissions or PID validity",
811
            handle->pid, secname, error);
812
        return 0;
813
    }
814
815
    MODULEENTRY32W moduleEntry;
816
    moduleEntry.dwSize = sizeof(moduleEntry);
817
    void* runtime_addr = NULL;
818
819
    for (BOOL hasModule = Module32FirstW(hProcSnap, &moduleEntry); hasModule; hasModule = Module32NextW(hProcSnap, &moduleEntry)) {
820
        // Look for either python executable or DLL
821
        if (wcsstr(moduleEntry.szModule, substr)) {
822
            runtime_addr = analyze_pe(moduleEntry.szExePath, moduleEntry.modBaseAddr, secname);
823
            if (runtime_addr != NULL) {
824
                break;
825
            }
826
        }
827
    }
828
829
    CloseHandle(hProcSnap);
830
831
    return (uintptr_t)runtime_addr;
832
}
833
834
#endif // MS_WINDOWS
835
836
// Get the PyRuntime section address for any platform
837
static uintptr_t
838
_Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
839
0
{
840
0
    uintptr_t address;
841
842
#ifdef MS_WINDOWS
843
    // On Windows, search for 'python' in executable or DLL
844
    address = search_windows_map_for_section(handle, "PyRuntime", L"python");
845
    if (address == 0) {
846
        // Error out: 'python' substring covers both executable and DLL
847
        PyObject *exc = PyErr_GetRaisedException();
848
        PyErr_Format(PyExc_RuntimeError,
849
            "Failed to find the PyRuntime section in process %d on Windows platform",
850
            handle->pid);
851
        _PyErr_ChainExceptions1(exc);
852
    }
853
#elif defined(__linux__)
854
    // On Linux, search for 'python' in executable or DLL
855
0
    address = search_linux_map_for_section(handle, "PyRuntime", "python");
856
0
    if (address == 0) {
857
        // Error out: 'python' substring covers both executable and DLL
858
0
        PyObject *exc = PyErr_GetRaisedException();
859
0
        PyErr_Format(PyExc_RuntimeError,
860
0
            "Failed to find the PyRuntime section in process %d on Linux platform",
861
0
            handle->pid);
862
0
        _PyErr_ChainExceptions1(exc);
863
0
    }
864
#elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
865
    // On macOS, try libpython first, then fall back to python
866
    const char* candidates[] = {"libpython", "python", "Python", NULL};
867
    for (const char** candidate = candidates; *candidate; candidate++) {
868
        PyErr_Clear();
869
        address = search_map_for_section(handle, "PyRuntime", *candidate);
870
        if (address != 0) {
871
            break;
872
        }
873
    }
874
    if (address == 0) {
875
        PyObject *exc = PyErr_GetRaisedException();
876
        PyErr_Format(PyExc_RuntimeError,
877
            "Failed to find the PyRuntime section in process %d "
878
            "on macOS platform (tried both libpython and python)",
879
            handle->pid);
880
        _PyErr_ChainExceptions1(exc);
881
    }
882
#else
883
    Py_UNREACHABLE();
884
#endif
885
886
0
    return address;
887
0
}
888
889
#if defined(__linux__) && HAVE_PROCESS_VM_READV
890
891
static int
892
open_proc_mem_fd(proc_handle_t *handle)
893
0
{
894
0
    char mem_file_path[64];
895
0
    sprintf(mem_file_path, "/proc/%d/mem", handle->pid);
896
897
0
    handle->memfd = open(mem_file_path, O_RDWR);
898
0
    if (handle->memfd == -1) {
899
0
        PyErr_SetFromErrno(PyExc_OSError);
900
0
        _set_debug_exception_cause(PyExc_OSError,
901
0
            "failed to open file %s: %s", mem_file_path, strerror(errno));
902
0
        return -1;
903
0
    }
904
0
    return 0;
905
0
}
906
907
// Why is pwritev not guarded? Except on Android API level 23 (no longer
908
// supported), HAVE_PROCESS_VM_READV is sufficient.
909
static int
910
read_remote_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst)
911
0
{
912
0
    if (handle->memfd == -1) {
913
0
        if (open_proc_mem_fd(handle) < 0) {
914
0
            return -1;
915
0
        }
916
0
    }
917
918
0
    struct iovec local[1];
919
0
    Py_ssize_t result = 0;
920
0
    Py_ssize_t read_bytes = 0;
921
922
0
    do {
923
0
        local[0].iov_base = (char*)dst + result;
924
0
        local[0].iov_len = len - result;
925
0
        off_t offset = remote_address + result;
926
927
0
        read_bytes = preadv(handle->memfd, local, 1, offset);
928
0
        if (read_bytes < 0) {
929
0
            PyErr_SetFromErrno(PyExc_OSError);
930
0
            _set_debug_exception_cause(PyExc_OSError,
931
0
                "preadv failed for PID %d at address 0x%lx "
932
0
                "(size %zu, partial read %zd bytes): %s",
933
0
                handle->pid, remote_address + result, len - result, result, strerror(errno));
934
0
            return -1;
935
0
        }
936
937
0
        result += read_bytes;
938
0
    } while ((size_t)read_bytes != local[0].iov_len);
939
0
    return 0;
940
0
}
941
942
#endif // __linux__
943
944
// Platform-independent memory read function
945
static int
946
_Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst)
947
0
{
948
#ifdef MS_WINDOWS
949
    SIZE_T read_bytes = 0;
950
    SIZE_T result = 0;
951
    do {
952
        if (!ReadProcessMemory(handle->hProcess, (LPCVOID)(remote_address + result), (char*)dst + result, len - result, &read_bytes)) {
953
            PyErr_SetFromWindowsErr(0);
954
            DWORD error = GetLastError();
955
            _set_debug_exception_cause(PyExc_OSError,
956
                "ReadProcessMemory failed for PID %d at address 0x%lx "
957
                "(size %zu, partial read %zu bytes): Windows error %lu",
958
                handle->pid, remote_address + result, len - result, result, error);
959
            return -1;
960
        }
961
        result += read_bytes;
962
    } while (result < len);
963
    return 0;
964
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
965
0
    if (handle->memfd != -1) {
966
0
        return read_remote_memory_fallback(handle, remote_address, len, dst);
967
0
    }
968
0
    struct iovec local[1];
969
0
    struct iovec remote[1];
970
0
    Py_ssize_t result = 0;
971
0
    Py_ssize_t read_bytes = 0;
972
973
0
    do {
974
0
        local[0].iov_base = (char*)dst + result;
975
0
        local[0].iov_len = len - result;
976
0
        remote[0].iov_base = (void*)(remote_address + result);
977
0
        remote[0].iov_len = len - result;
978
979
0
        read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0);
980
0
        if (read_bytes < 0) {
981
0
            if (errno == ENOSYS) {
982
0
                return read_remote_memory_fallback(handle, remote_address, len, dst);
983
0
            }
984
0
            PyErr_SetFromErrno(PyExc_OSError);
985
0
            _set_debug_exception_cause(PyExc_OSError,
986
0
                "process_vm_readv failed for PID %d at address 0x%lx "
987
0
                "(size %zu, partial read %zd bytes): %s",
988
0
                handle->pid, remote_address + result, len - result, result, strerror(errno));
989
0
            return -1;
990
0
        }
991
992
0
        result += read_bytes;
993
0
    } while ((size_t)read_bytes != local[0].iov_len);
994
0
    return 0;
995
#elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
996
    Py_ssize_t result = -1;
997
    kern_return_t kr = mach_vm_read_overwrite(
998
        handle->task,
999
        (mach_vm_address_t)remote_address,
1000
        len,
1001
        (mach_vm_address_t)dst,
1002
        (mach_vm_size_t*)&result);
1003
1004
    if (kr != KERN_SUCCESS) {
1005
        switch (kr) {
1006
        case KERN_PROTECTION_FAILURE:
1007
            PyErr_Format(PyExc_PermissionError,
1008
                "Memory protection failure reading from PID %d at address "
1009
                "0x%lx (size %zu): insufficient permissions",
1010
                handle->pid, remote_address, len);
1011
            break;
1012
        case KERN_INVALID_ARGUMENT:
1013
            PyErr_Format(PyExc_ValueError,
1014
                "Invalid argument to mach_vm_read_overwrite for PID %d at "
1015
                "address 0x%lx (size %zu)",
1016
                handle->pid, remote_address, len);
1017
            break;
1018
        default:
1019
            PyErr_Format(PyExc_RuntimeError,
1020
                "mach_vm_read_overwrite failed for PID %d at address 0x%lx "
1021
                "(size %zu): kern_return_t %d",
1022
                handle->pid, remote_address, len, kr);
1023
        }
1024
        return -1;
1025
    }
1026
    return 0;
1027
#else
1028
    Py_UNREACHABLE();
1029
#endif
1030
0
}
1031
1032
UNUSED static int
1033
_Py_RemoteDebug_PagedReadRemoteMemory(proc_handle_t *handle,
1034
                                      uintptr_t addr,
1035
                                      size_t size,
1036
                                      void *out)
1037
0
{
1038
0
    return _Py_RemoteDebug_ReadRemoteMemory(handle, addr, size, out);
1039
0
}
1040
1041
static int
1042
_Py_RemoteDebug_ReadDebugOffsets(
1043
    proc_handle_t *handle,
1044
    uintptr_t *runtime_start_address,
1045
    _Py_DebugOffsets* debug_offsets
1046
0
) {
1047
0
    *runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle);
1048
0
    if (!*runtime_start_address) {
1049
0
        if (!PyErr_Occurred()) {
1050
0
            PyErr_Format(PyExc_RuntimeError,
1051
0
                "Failed to locate PyRuntime address for PID %d",
1052
0
                handle->pid);
1053
0
        }
1054
0
        _set_debug_exception_cause(PyExc_RuntimeError, "PyRuntime address lookup failed during debug offsets initialization");
1055
0
        return -1;
1056
0
    }
1057
0
    size_t size = sizeof(struct _Py_DebugOffsets);
1058
0
    if (0 != _Py_RemoteDebug_ReadRemoteMemory(handle, *runtime_start_address, size, debug_offsets)) {
1059
0
        _set_debug_exception_cause(PyExc_RuntimeError, "Failed to read debug offsets structure from remote process");
1060
0
        return -1;
1061
0
    }
1062
0
    return 0;
1063
0
}
1064
1065
#ifdef __cplusplus
1066
}
1067
#endif