Coverage Report

Created: 2025-07-04 06:49

/src/cpython/Python/remote_debugging.c
Line
Count
Source (jump to first uncovered line)
1
#define _GNU_SOURCE
2
#include "pyconfig.h"
3
4
#include "Python.h"
5
#include "internal/pycore_runtime.h"
6
#include "internal/pycore_ceval.h"
7
8
#if defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
9
#include "remote_debug.h"
10
11
static int
12
0
init_proc_handle(proc_handle_t *handle, pid_t pid) {
13
0
    return _Py_RemoteDebug_InitProcHandle(handle, pid);
14
0
}
15
16
static void
17
0
cleanup_proc_handle(proc_handle_t *handle) {
18
0
    _Py_RemoteDebug_CleanupProcHandle(handle);
19
0
}
20
21
static int
22
read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* dst)
23
0
{
24
0
    return _Py_RemoteDebug_ReadRemoteMemory(handle, remote_address, len, dst);
25
0
}
26
27
// Why is pwritev not guarded? Except on Android API level 23 (no longer
28
// supported), HAVE_PROCESS_VM_READV is sufficient.
29
#if defined(__linux__) && HAVE_PROCESS_VM_READV
30
static int
31
write_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
32
0
{
33
0
    if (handle->memfd == -1) {
34
0
        if (open_proc_mem_fd(handle) < 0) {
35
0
            return -1;
36
0
        }
37
0
    }
38
39
0
    struct iovec local[1];
40
0
    Py_ssize_t result = 0;
41
0
    Py_ssize_t written = 0;
42
43
0
    do {
44
0
        local[0].iov_base = (char*)src + result;
45
0
        local[0].iov_len = len - result;
46
0
        off_t offset = remote_address + result;
47
48
0
        written = pwritev(handle->memfd, local, 1, offset);
49
0
        if (written < 0) {
50
0
            PyErr_SetFromErrno(PyExc_OSError);
51
0
            return -1;
52
0
        }
53
54
0
        result += written;
55
0
    } while ((size_t)written != local[0].iov_len);
56
0
    return 0;
57
0
}
58
#endif // __linux__
59
60
static int
61
write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
62
0
{
63
#ifdef MS_WINDOWS
64
    SIZE_T written = 0;
65
    SIZE_T result = 0;
66
    do {
67
        if (!WriteProcessMemory(handle->hProcess, (LPVOID)(remote_address + result), (const char*)src + result, len - result, &written)) {
68
            PyErr_SetFromWindowsErr(0);
69
            return -1;
70
        }
71
        result += written;
72
    } while (result < len);
73
    return 0;
74
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
75
0
    if (handle->memfd != -1) {
76
0
        return write_memory_fallback(handle, remote_address, len, src);
77
0
    }
78
0
    struct iovec local[1];
79
0
    struct iovec remote[1];
80
0
    Py_ssize_t result = 0;
81
0
    Py_ssize_t written = 0;
82
83
0
    do {
84
0
        local[0].iov_base = (void*)((char*)src + result);
85
0
        local[0].iov_len = len - result;
86
0
        remote[0].iov_base = (void*)((char*)remote_address + result);
87
0
        remote[0].iov_len = len - result;
88
89
0
        written = process_vm_writev(handle->pid, local, 1, remote, 1, 0);
90
0
        if (written < 0) {
91
0
            if (errno == ENOSYS) {
92
0
                return write_memory_fallback(handle, remote_address, len, src);
93
0
            }
94
0
            PyErr_SetFromErrno(PyExc_OSError);
95
0
            return -1;
96
0
        }
97
98
0
        result += written;
99
0
    } while ((size_t)written != local[0].iov_len);
100
0
    return 0;
101
#elif defined(__APPLE__) && TARGET_OS_OSX
102
    kern_return_t kr = mach_vm_write(
103
        pid_to_task(handle->pid),
104
        (mach_vm_address_t)remote_address,
105
        (vm_offset_t)src,
106
        (mach_msg_type_number_t)len);
107
108
    if (kr != KERN_SUCCESS) {
109
        switch (kr) {
110
        case KERN_PROTECTION_FAILURE:
111
            PyErr_SetString(PyExc_PermissionError, "Not enough permissions to write memory");
112
            break;
113
        case KERN_INVALID_ARGUMENT:
114
            PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_write");
115
            break;
116
        default:
117
            PyErr_Format(PyExc_RuntimeError, "Unknown error writing memory: %d", (int)kr);
118
        }
119
        return -1;
120
    }
121
    return 0;
122
#else
123
    Py_UNREACHABLE();
124
#endif
125
0
}
126
127
static int
128
is_prerelease_version(uint64_t version)
129
0
{
130
0
    return (version & 0xF0) != 0xF0;
131
0
}
132
133
static int
134
ensure_debug_offset_compatibility(const _Py_DebugOffsets* debug_offsets)
135
0
{
136
0
    if (memcmp(debug_offsets->cookie, _Py_Debug_Cookie, sizeof(debug_offsets->cookie)) != 0) {
137
        // The remote is probably running a Python version predating debug offsets.
138
0
        PyErr_SetString(
139
0
            PyExc_RuntimeError,
140
0
            "Can't determine the Python version of the remote process");
141
0
        return -1;
142
0
    }
143
144
    // Assume debug offsets could change from one pre-release version to another,
145
    // or one minor version to another, but are stable across patch versions.
146
0
    if (is_prerelease_version(Py_Version) && Py_Version != debug_offsets->version) {
147
0
        PyErr_SetString(
148
0
            PyExc_RuntimeError,
149
0
            "Can't send commands from a pre-release Python interpreter"
150
0
            " to a process running a different Python version");
151
0
        return -1;
152
0
    }
153
154
0
    if (is_prerelease_version(debug_offsets->version) && Py_Version != debug_offsets->version) {
155
0
        PyErr_SetString(
156
0
            PyExc_RuntimeError,
157
0
            "Can't send commands to a pre-release Python interpreter"
158
0
            " from a process running a different Python version");
159
0
        return -1;
160
0
    }
161
162
0
    unsigned int remote_major = (debug_offsets->version >> 24) & 0xFF;
163
0
    unsigned int remote_minor = (debug_offsets->version >> 16) & 0xFF;
164
165
0
    if (PY_MAJOR_VERSION != remote_major || PY_MINOR_VERSION != remote_minor) {
166
0
        PyErr_Format(
167
0
            PyExc_RuntimeError,
168
0
            "Can't send commands from a Python %d.%d process to a Python %d.%d process",
169
0
            PY_MAJOR_VERSION, PY_MINOR_VERSION, remote_major, remote_minor);
170
0
        return -1;
171
0
    }
172
173
    // The debug offsets differ between free threaded and non-free threaded builds.
174
0
    if (_Py_Debug_Free_Threaded && !debug_offsets->free_threaded) {
175
0
        PyErr_SetString(
176
0
            PyExc_RuntimeError,
177
0
            "Cannot send commands from a free-threaded Python process"
178
0
            " to a process running a non-free-threaded version");
179
0
        return -1;
180
0
    }
181
182
0
    if (!_Py_Debug_Free_Threaded && debug_offsets->free_threaded) {
183
0
        PyErr_SetString(
184
0
            PyExc_RuntimeError,
185
0
            "Cannot send commands to a free-threaded Python process"
186
0
            " from a process running a non-free-threaded version");
187
0
        return -1;
188
0
    }
189
190
0
    return 0;
191
0
}
192
193
static int
194
read_offsets(
195
    proc_handle_t *handle,
196
    uintptr_t *runtime_start_address,
197
    _Py_DebugOffsets* debug_offsets
198
0
) {
199
0
    if (_Py_RemoteDebug_ReadDebugOffsets(handle, runtime_start_address, debug_offsets)) {
200
0
        return -1;
201
0
    }
202
0
    if (ensure_debug_offset_compatibility(debug_offsets)) {
203
0
        return -1;
204
0
    }
205
0
    return 0;
206
0
}
207
208
static int
209
send_exec_to_proc_handle(proc_handle_t *handle, int tid, const char *debugger_script_path)
210
0
{
211
0
    uintptr_t runtime_start_address;
212
0
    struct _Py_DebugOffsets debug_offsets;
213
214
0
    if (read_offsets(handle, &runtime_start_address, &debug_offsets)) {
215
0
        return -1;
216
0
    }
217
218
0
    uintptr_t interpreter_state_list_head = (uintptr_t)debug_offsets.runtime_state.interpreters_head;
219
220
0
    uintptr_t interpreter_state_addr;
221
0
    if (0 != read_memory(
222
0
            handle,
223
0
            runtime_start_address + interpreter_state_list_head,
224
0
            sizeof(void*),
225
0
            &interpreter_state_addr))
226
0
    {
227
0
        return -1;
228
0
    }
229
230
0
    if (interpreter_state_addr == 0) {
231
0
        PyErr_SetString(PyExc_RuntimeError, "Can't find a running interpreter in the remote process");
232
0
        return -1;
233
0
    }
234
235
0
    int is_remote_debugging_enabled = 0;
236
0
    if (0 != read_memory(
237
0
            handle,
238
0
            interpreter_state_addr + debug_offsets.debugger_support.remote_debugging_enabled,
239
0
            sizeof(int),
240
0
            &is_remote_debugging_enabled))
241
0
    {
242
0
        return -1;
243
0
    }
244
245
0
    if (is_remote_debugging_enabled != 1) {
246
0
        PyErr_SetString(
247
0
            PyExc_RuntimeError,
248
0
            "Remote debugging is not enabled in the remote process");
249
0
        return -1;
250
0
    }
251
252
0
    uintptr_t thread_state_addr;
253
0
    unsigned long this_tid = 0;
254
255
0
    if (tid != 0) {
256
0
        if (0 != read_memory(
257
0
                handle,
258
0
                interpreter_state_addr + debug_offsets.interpreter_state.threads_head,
259
0
                sizeof(void*),
260
0
                &thread_state_addr))
261
0
        {
262
0
            return -1;
263
0
        }
264
0
        while (thread_state_addr != 0) {
265
0
            if (0 != read_memory(
266
0
                    handle,
267
0
                    thread_state_addr + debug_offsets.thread_state.native_thread_id,
268
0
                    sizeof(this_tid),
269
0
                    &this_tid))
270
0
            {
271
0
                return -1;
272
0
            }
273
274
0
            if (this_tid == (unsigned long)tid) {
275
0
                break;
276
0
            }
277
278
0
            if (0 != read_memory(
279
0
                    handle,
280
0
                    thread_state_addr + debug_offsets.thread_state.next,
281
0
                    sizeof(void*),
282
0
                    &thread_state_addr))
283
0
            {
284
0
                return -1;
285
0
            }
286
0
        }
287
288
0
        if (thread_state_addr == 0) {
289
0
            PyErr_SetString(
290
0
                PyExc_RuntimeError,
291
0
                "Can't find the specified thread in the remote process");
292
0
            return -1;
293
0
        }
294
0
    } else {
295
0
        if (0 != read_memory(
296
0
                handle,
297
0
                interpreter_state_addr + debug_offsets.interpreter_state.threads_main,
298
0
                sizeof(void*),
299
0
                &thread_state_addr))
300
0
        {
301
0
            return -1;
302
0
        }
303
304
0
        if (thread_state_addr == 0) {
305
0
            PyErr_SetString(
306
0
                PyExc_RuntimeError,
307
0
                "Can't find the main thread in the remote process");
308
0
            return -1;
309
0
        }
310
0
    }
311
312
    // Ensure our path is not too long
313
0
    if (debug_offsets.debugger_support.debugger_script_path_size <= strlen(debugger_script_path)) {
314
0
        PyErr_SetString(PyExc_ValueError, "Debugger script path is too long");
315
0
        return -1;
316
0
    }
317
318
0
    uintptr_t debugger_script_path_addr = (uintptr_t)(
319
0
        thread_state_addr +
320
0
        debug_offsets.debugger_support.remote_debugger_support +
321
0
        debug_offsets.debugger_support.debugger_script_path);
322
0
    if (0 != write_memory(
323
0
            handle,
324
0
            debugger_script_path_addr,
325
0
            strlen(debugger_script_path) + 1,
326
0
            debugger_script_path))
327
0
    {
328
0
        return -1;
329
0
    }
330
331
0
    int pending_call = 1;
332
0
    uintptr_t debugger_pending_call_addr = (uintptr_t)(
333
0
        thread_state_addr +
334
0
        debug_offsets.debugger_support.remote_debugger_support +
335
0
        debug_offsets.debugger_support.debugger_pending_call);
336
0
    if (0 != write_memory(
337
0
            handle,
338
0
            debugger_pending_call_addr,
339
0
            sizeof(int),
340
0
            &pending_call))
341
342
0
    {
343
0
        return -1;
344
0
    }
345
346
0
    uintptr_t eval_breaker;
347
0
    if (0 != read_memory(
348
0
            handle,
349
0
            thread_state_addr + debug_offsets.debugger_support.eval_breaker,
350
0
            sizeof(uintptr_t),
351
0
            &eval_breaker))
352
0
    {
353
0
        return -1;
354
0
    }
355
356
0
    eval_breaker |= _PY_EVAL_PLEASE_STOP_BIT;
357
358
0
    if (0 != write_memory(
359
0
            handle,
360
0
            thread_state_addr + (uintptr_t)debug_offsets.debugger_support.eval_breaker,
361
0
            sizeof(uintptr_t),
362
0
            &eval_breaker))
363
364
0
    {
365
0
        return -1;
366
0
    }
367
368
0
    return 0;
369
0
}
370
371
#endif // defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
372
373
int
374
_PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path)
375
0
{
376
#if !defined(Py_SUPPORTS_REMOTE_DEBUG)
377
    PyErr_SetString(PyExc_RuntimeError, "Remote debugging is not supported on this platform");
378
    return -1;
379
#elif !defined(Py_REMOTE_DEBUG)
380
    PyErr_SetString(PyExc_RuntimeError, "Remote debugging support has not been compiled in");
381
    return -1;
382
#else
383
384
0
    PyThreadState *tstate = _PyThreadState_GET();
385
0
    const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp);
386
0
    if (config->remote_debug != 1) {
387
0
        PyErr_SetString(PyExc_RuntimeError, "Remote debugging is not enabled");
388
0
        return -1;
389
0
    }
390
391
0
    proc_handle_t handle;
392
0
    if (init_proc_handle(&handle, pid) < 0) {
393
0
        return -1;
394
0
    }
395
396
0
    int rc = send_exec_to_proc_handle(&handle, tid, debugger_script_path);
397
0
    cleanup_proc_handle(&handle);
398
0
    return rc;
399
0
#endif
400
0
}
401