/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 | | |