Coverage Report

Created: 2025-12-07 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Python/remote_debugging.c
Line
Count
Source
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, uintptr_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
// Use the shared write function from remote_debug.h
28
static int
29
write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
30
0
{
31
0
    return _Py_RemoteDebug_WriteRemoteMemory(handle, remote_address, len, src);
32
0
}
33
34
static int
35
is_prerelease_version(uint64_t version)
36
0
{
37
0
    return (version & 0xF0) != 0xF0;
38
0
}
39
40
static int
41
ensure_debug_offset_compatibility(const _Py_DebugOffsets* debug_offsets)
42
0
{
43
0
    if (memcmp(debug_offsets->cookie, _Py_Debug_Cookie, sizeof(debug_offsets->cookie)) != 0) {
44
        // The remote is probably running a Python version predating debug offsets.
45
0
        PyErr_SetString(
46
0
            PyExc_RuntimeError,
47
0
            "Can't determine the Python version of the remote process");
48
0
        return -1;
49
0
    }
50
51
    // Assume debug offsets could change from one pre-release version to another,
52
    // or one minor version to another, but are stable across patch versions.
53
0
    if (is_prerelease_version(Py_Version) && Py_Version != debug_offsets->version) {
54
0
        PyErr_SetString(
55
0
            PyExc_RuntimeError,
56
0
            "Can't send commands from a pre-release Python interpreter"
57
0
            " to a process running a different Python version");
58
0
        return -1;
59
0
    }
60
61
0
    if (is_prerelease_version(debug_offsets->version) && Py_Version != debug_offsets->version) {
62
0
        PyErr_SetString(
63
0
            PyExc_RuntimeError,
64
0
            "Can't send commands to a pre-release Python interpreter"
65
0
            " from a process running a different Python version");
66
0
        return -1;
67
0
    }
68
69
0
    unsigned int remote_major = (debug_offsets->version >> 24) & 0xFF;
70
0
    unsigned int remote_minor = (debug_offsets->version >> 16) & 0xFF;
71
72
0
    if (PY_MAJOR_VERSION != remote_major || PY_MINOR_VERSION != remote_minor) {
73
0
        PyErr_Format(
74
0
            PyExc_RuntimeError,
75
0
            "Can't send commands from a Python %d.%d process to a Python %d.%d process",
76
0
            PY_MAJOR_VERSION, PY_MINOR_VERSION, remote_major, remote_minor);
77
0
        return -1;
78
0
    }
79
80
    // The debug offsets differ between free threaded and non-free threaded builds.
81
0
    if (_Py_Debug_Free_Threaded && !debug_offsets->free_threaded) {
82
0
        PyErr_SetString(
83
0
            PyExc_RuntimeError,
84
0
            "Cannot send commands from a free-threaded Python process"
85
0
            " to a process running a non-free-threaded version");
86
0
        return -1;
87
0
    }
88
89
0
    if (!_Py_Debug_Free_Threaded && debug_offsets->free_threaded) {
90
0
        PyErr_SetString(
91
0
            PyExc_RuntimeError,
92
0
            "Cannot send commands to a free-threaded Python process"
93
0
            " from a process running a non-free-threaded version");
94
0
        return -1;
95
0
    }
96
97
0
    return 0;
98
0
}
99
100
static int
101
read_offsets(
102
    proc_handle_t *handle,
103
    uintptr_t *runtime_start_address,
104
    _Py_DebugOffsets* debug_offsets
105
0
) {
106
0
    if (_Py_RemoteDebug_ReadDebugOffsets(handle, runtime_start_address, debug_offsets)) {
107
0
        return -1;
108
0
    }
109
0
    if (ensure_debug_offset_compatibility(debug_offsets)) {
110
0
        return -1;
111
0
    }
112
0
    return 0;
113
0
}
114
115
static int
116
send_exec_to_proc_handle(proc_handle_t *handle, int tid, const char *debugger_script_path)
117
0
{
118
0
    uintptr_t runtime_start_address;
119
0
    struct _Py_DebugOffsets debug_offsets;
120
121
0
    if (read_offsets(handle, &runtime_start_address, &debug_offsets)) {
122
0
        return -1;
123
0
    }
124
125
0
    uintptr_t interpreter_state_list_head = (uintptr_t)debug_offsets.runtime_state.interpreters_head;
126
127
0
    uintptr_t interpreter_state_addr;
128
0
    if (0 != read_memory(
129
0
            handle,
130
0
            runtime_start_address + interpreter_state_list_head,
131
0
            sizeof(void*),
132
0
            &interpreter_state_addr))
133
0
    {
134
0
        return -1;
135
0
    }
136
137
0
    if (interpreter_state_addr == 0) {
138
0
        PyErr_SetString(PyExc_RuntimeError, "Can't find a running interpreter in the remote process");
139
0
        return -1;
140
0
    }
141
142
0
    int is_remote_debugging_enabled = 0;
143
0
    if (0 != read_memory(
144
0
            handle,
145
0
            interpreter_state_addr + (uintptr_t)debug_offsets.debugger_support.remote_debugging_enabled,
146
0
            sizeof(int),
147
0
            &is_remote_debugging_enabled))
148
0
    {
149
0
        return -1;
150
0
    }
151
152
0
    if (is_remote_debugging_enabled != 1) {
153
0
        PyErr_SetString(
154
0
            PyExc_RuntimeError,
155
0
            "Remote debugging is not enabled in the remote process");
156
0
        return -1;
157
0
    }
158
159
0
    uintptr_t thread_state_addr;
160
0
    unsigned long this_tid = 0;
161
162
0
    if (tid != 0) {
163
0
        if (0 != read_memory(
164
0
                handle,
165
0
                interpreter_state_addr + (uintptr_t)debug_offsets.interpreter_state.threads_head,
166
0
                sizeof(void*),
167
0
                &thread_state_addr))
168
0
        {
169
0
            return -1;
170
0
        }
171
0
        while (thread_state_addr != 0) {
172
0
            if (0 != read_memory(
173
0
                    handle,
174
0
                    thread_state_addr + (uintptr_t)debug_offsets.thread_state.native_thread_id,
175
0
                    sizeof(this_tid),
176
0
                    &this_tid))
177
0
            {
178
0
                return -1;
179
0
            }
180
181
0
            if (this_tid == (unsigned long)tid) {
182
0
                break;
183
0
            }
184
185
0
            if (0 != read_memory(
186
0
                    handle,
187
0
                    thread_state_addr + (uintptr_t)debug_offsets.thread_state.next,
188
0
                    sizeof(void*),
189
0
                    &thread_state_addr))
190
0
            {
191
0
                return -1;
192
0
            }
193
0
        }
194
195
0
        if (thread_state_addr == 0) {
196
0
            PyErr_SetString(
197
0
                PyExc_RuntimeError,
198
0
                "Can't find the specified thread in the remote process");
199
0
            return -1;
200
0
        }
201
0
    } else {
202
0
        if (0 != read_memory(
203
0
                handle,
204
0
                interpreter_state_addr + (uintptr_t)debug_offsets.interpreter_state.threads_main,
205
0
                sizeof(void*),
206
0
                &thread_state_addr))
207
0
        {
208
0
            return -1;
209
0
        }
210
211
0
        if (thread_state_addr == 0) {
212
0
            PyErr_SetString(
213
0
                PyExc_RuntimeError,
214
0
                "Can't find the main thread in the remote process");
215
0
            return -1;
216
0
        }
217
0
    }
218
219
    // Ensure our path is not too long
220
0
    if (debug_offsets.debugger_support.debugger_script_path_size <= strlen(debugger_script_path)) {
221
0
        PyErr_SetString(PyExc_ValueError, "Debugger script path is too long");
222
0
        return -1;
223
0
    }
224
225
0
    uintptr_t debugger_script_path_addr = (uintptr_t)(
226
0
        thread_state_addr +
227
0
        debug_offsets.debugger_support.remote_debugger_support +
228
0
        debug_offsets.debugger_support.debugger_script_path);
229
0
    if (0 != write_memory(
230
0
            handle,
231
0
            debugger_script_path_addr,
232
0
            strlen(debugger_script_path) + 1,
233
0
            debugger_script_path))
234
0
    {
235
0
        return -1;
236
0
    }
237
238
0
    int pending_call = 1;
239
0
    uintptr_t debugger_pending_call_addr = (uintptr_t)(
240
0
        thread_state_addr +
241
0
        debug_offsets.debugger_support.remote_debugger_support +
242
0
        debug_offsets.debugger_support.debugger_pending_call);
243
0
    if (0 != write_memory(
244
0
            handle,
245
0
            debugger_pending_call_addr,
246
0
            sizeof(int),
247
0
            &pending_call))
248
249
0
    {
250
0
        return -1;
251
0
    }
252
253
0
    uintptr_t eval_breaker;
254
0
    if (0 != read_memory(
255
0
            handle,
256
0
            thread_state_addr + (uintptr_t)debug_offsets.debugger_support.eval_breaker,
257
0
            sizeof(uintptr_t),
258
0
            &eval_breaker))
259
0
    {
260
0
        return -1;
261
0
    }
262
263
0
    eval_breaker |= _PY_EVAL_PLEASE_STOP_BIT;
264
265
0
    if (0 != write_memory(
266
0
            handle,
267
0
            thread_state_addr + (uintptr_t)debug_offsets.debugger_support.eval_breaker,
268
0
            sizeof(uintptr_t),
269
0
            &eval_breaker))
270
271
0
    {
272
0
        return -1;
273
0
    }
274
275
0
    return 0;
276
0
}
277
278
#endif // defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
279
280
int
281
_PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path)
282
0
{
283
#if !defined(Py_SUPPORTS_REMOTE_DEBUG)
284
    PyErr_SetString(PyExc_RuntimeError, "Remote debugging is not supported on this platform");
285
    return -1;
286
#elif !defined(Py_REMOTE_DEBUG)
287
    PyErr_SetString(PyExc_RuntimeError, "Remote debugging support has not been compiled in");
288
    return -1;
289
#else
290
291
0
    PyThreadState *tstate = _PyThreadState_GET();
292
0
    const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp);
293
0
    if (config->remote_debug != 1) {
294
0
        PyErr_SetString(PyExc_RuntimeError, "Remote debugging is not enabled");
295
0
        return -1;
296
0
    }
297
298
0
    proc_handle_t handle;
299
0
    if (init_proc_handle(&handle, pid) < 0) {
300
0
        return -1;
301
0
    }
302
303
0
    int rc = send_exec_to_proc_handle(&handle, tid, debugger_script_path);
304
0
    cleanup_proc_handle(&handle);
305
0
    return rc;
306
0
#endif
307
0
}
308