| 
									
										
										
										
											2025-04-03 16:20:01 +01:00
										 |  |  | #define _GNU_SOURCE
 | 
					
						
							|  |  |  | #include "pyconfig.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "Python.h"
 | 
					
						
							|  |  |  | #include "internal/pycore_runtime.h"
 | 
					
						
							|  |  |  | #include "internal/pycore_ceval.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-06 21:39:25 +01:00
										 |  |  | #if defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  | #include "remote_debug.h"
 | 
					
						
							| 
									
										
										
										
											2025-04-06 21:39:25 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-03 16:20:01 +01:00
										 |  |  | static int | 
					
						
							|  |  |  | init_proc_handle(proc_handle_t *handle, pid_t pid) { | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     return _Py_RemoteDebug_InitProcHandle(handle, pid); | 
					
						
							| 
									
										
										
										
											2025-04-03 16:20:01 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | cleanup_proc_handle(proc_handle_t *handle) { | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     _Py_RemoteDebug_CleanupProcHandle(handle); | 
					
						
							| 
									
										
										
										
											2025-04-03 16:20:01 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int | 
					
						
							|  |  |  | read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* dst) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     return _Py_RemoteDebug_ReadRemoteMemory(handle, remote_address, len, dst); | 
					
						
							| 
									
										
										
										
											2025-04-03 16:20:01 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int | 
					
						
							|  |  |  | write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | #ifdef MS_WINDOWS
 | 
					
						
							|  |  |  |     SIZE_T written = 0; | 
					
						
							|  |  |  |     SIZE_T result = 0; | 
					
						
							|  |  |  |     do { | 
					
						
							|  |  |  |         if (!WriteProcessMemory(handle->hProcess, (LPVOID)(remote_address + result), (const char*)src + result, len - result, &written)) { | 
					
						
							|  |  |  |             PyErr_SetFromWindowsErr(0); | 
					
						
							|  |  |  |             return -1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         result += written; | 
					
						
							|  |  |  |     } while (result < len); | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | #elif defined(__linux__) && HAVE_PROCESS_VM_READV
 | 
					
						
							|  |  |  |     struct iovec local[1]; | 
					
						
							|  |  |  |     struct iovec remote[1]; | 
					
						
							|  |  |  |     Py_ssize_t result = 0; | 
					
						
							|  |  |  |     Py_ssize_t written = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     do { | 
					
						
							|  |  |  |         local[0].iov_base = (void*)((char*)src + result); | 
					
						
							|  |  |  |         local[0].iov_len = len - result; | 
					
						
							|  |  |  |         remote[0].iov_base = (void*)((char*)remote_address + result); | 
					
						
							|  |  |  |         remote[0].iov_len = len - result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         written = process_vm_writev(handle->pid, local, 1, remote, 1, 0); | 
					
						
							|  |  |  |         if (written < 0) { | 
					
						
							|  |  |  |             PyErr_SetFromErrno(PyExc_OSError); | 
					
						
							|  |  |  |             return -1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         result += written; | 
					
						
							|  |  |  |     } while ((size_t)written != local[0].iov_len); | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | #elif defined(__APPLE__) && TARGET_OS_OSX
 | 
					
						
							|  |  |  |     kern_return_t kr = mach_vm_write( | 
					
						
							|  |  |  |         pid_to_task(handle->pid), | 
					
						
							|  |  |  |         (mach_vm_address_t)remote_address, | 
					
						
							|  |  |  |         (vm_offset_t)src, | 
					
						
							|  |  |  |         (mach_msg_type_number_t)len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (kr != KERN_SUCCESS) { | 
					
						
							|  |  |  |         switch (kr) { | 
					
						
							|  |  |  |         case KERN_PROTECTION_FAILURE: | 
					
						
							|  |  |  |             PyErr_SetString(PyExc_PermissionError, "Not enough permissions to write memory"); | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case KERN_INVALID_ARGUMENT: | 
					
						
							|  |  |  |             PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_write"); | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         default: | 
					
						
							|  |  |  |             PyErr_Format(PyExc_RuntimeError, "Unknown error writing memory: %d", (int)kr); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |     Py_UNREACHABLE(); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int | 
					
						
							|  |  |  | is_prerelease_version(uint64_t version) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return (version & 0xF0) != 0xF0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int | 
					
						
							|  |  |  | ensure_debug_offset_compatibility(const _Py_DebugOffsets* debug_offsets) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (memcmp(debug_offsets->cookie, _Py_Debug_Cookie, sizeof(debug_offsets->cookie)) != 0) { | 
					
						
							|  |  |  |         // The remote is probably running a Python version predating debug offsets.
 | 
					
						
							|  |  |  |         PyErr_SetString( | 
					
						
							|  |  |  |             PyExc_RuntimeError, | 
					
						
							|  |  |  |             "Can't determine the Python version of the remote process"); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Assume debug offsets could change from one pre-release version to another,
 | 
					
						
							|  |  |  |     // or one minor version to another, but are stable across patch versions.
 | 
					
						
							|  |  |  |     if (is_prerelease_version(Py_Version) && Py_Version != debug_offsets->version) { | 
					
						
							|  |  |  |         PyErr_SetString( | 
					
						
							|  |  |  |             PyExc_RuntimeError, | 
					
						
							|  |  |  |             "Can't send commands from a pre-release Python interpreter" | 
					
						
							|  |  |  |             " to a process running a different Python version"); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (is_prerelease_version(debug_offsets->version) && Py_Version != debug_offsets->version) { | 
					
						
							|  |  |  |         PyErr_SetString( | 
					
						
							|  |  |  |             PyExc_RuntimeError, | 
					
						
							|  |  |  |             "Can't send commands to a pre-release Python interpreter" | 
					
						
							|  |  |  |             " from a process running a different Python version"); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     unsigned int remote_major = (debug_offsets->version >> 24) & 0xFF; | 
					
						
							|  |  |  |     unsigned int remote_minor = (debug_offsets->version >> 16) & 0xFF; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (PY_MAJOR_VERSION != remote_major || PY_MINOR_VERSION != remote_minor) { | 
					
						
							|  |  |  |         PyErr_Format( | 
					
						
							|  |  |  |             PyExc_RuntimeError, | 
					
						
							|  |  |  |             "Can't send commands from a Python %d.%d process to a Python %d.%d process", | 
					
						
							|  |  |  |             PY_MAJOR_VERSION, PY_MINOR_VERSION, remote_major, remote_minor); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // The debug offsets differ between free threaded and non-free threaded builds.
 | 
					
						
							|  |  |  |     if (_Py_Debug_Free_Threaded && !debug_offsets->free_threaded) { | 
					
						
							|  |  |  |         PyErr_SetString( | 
					
						
							|  |  |  |             PyExc_RuntimeError, | 
					
						
							|  |  |  |             "Cannot send commands from a free-threaded Python process" | 
					
						
							|  |  |  |             " to a process running a non-free-threaded version"); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!_Py_Debug_Free_Threaded && debug_offsets->free_threaded) { | 
					
						
							|  |  |  |         PyErr_SetString( | 
					
						
							|  |  |  |             PyExc_RuntimeError, | 
					
						
							|  |  |  |             "Cannot send commands to a free-threaded Python process" | 
					
						
							|  |  |  |             " from a process running a non-free-threaded version"); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int | 
					
						
							|  |  |  | read_offsets( | 
					
						
							|  |  |  |     proc_handle_t *handle, | 
					
						
							|  |  |  |     uintptr_t *runtime_start_address, | 
					
						
							|  |  |  |     _Py_DebugOffsets* debug_offsets | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     if (_Py_RemoteDebug_ReadDebugOffsets(handle, runtime_start_address, debug_offsets)) { | 
					
						
							| 
									
										
										
										
											2025-04-03 16:20:01 +01:00
										 |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (ensure_debug_offset_compatibility(debug_offsets)) { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int | 
					
						
							|  |  |  | send_exec_to_proc_handle(proc_handle_t *handle, int tid, const char *debugger_script_path) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     uintptr_t runtime_start_address; | 
					
						
							|  |  |  |     struct _Py_DebugOffsets debug_offsets; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (read_offsets(handle, &runtime_start_address, &debug_offsets)) { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     uintptr_t interpreter_state_list_head = (uintptr_t)debug_offsets.runtime_state.interpreters_head; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     uintptr_t interpreter_state_addr; | 
					
						
							|  |  |  |     if (0 != read_memory( | 
					
						
							|  |  |  |             handle, | 
					
						
							|  |  |  |             runtime_start_address + interpreter_state_list_head, | 
					
						
							|  |  |  |             sizeof(void*), | 
					
						
							|  |  |  |             &interpreter_state_addr)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (interpreter_state_addr == 0) { | 
					
						
							|  |  |  |         PyErr_SetString(PyExc_RuntimeError, "Can't find a running interpreter in the remote process"); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     int is_remote_debugging_enabled = 0; | 
					
						
							|  |  |  |     if (0 != read_memory( | 
					
						
							|  |  |  |             handle, | 
					
						
							|  |  |  |             interpreter_state_addr + debug_offsets.debugger_support.remote_debugging_enabled, | 
					
						
							|  |  |  |             sizeof(int), | 
					
						
							|  |  |  |             &is_remote_debugging_enabled)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (is_remote_debugging_enabled != 1) { | 
					
						
							|  |  |  |         PyErr_SetString( | 
					
						
							|  |  |  |             PyExc_RuntimeError, | 
					
						
							|  |  |  |             "Remote debugging is not enabled in the remote process"); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     uintptr_t thread_state_addr; | 
					
						
							|  |  |  |     unsigned long this_tid = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (tid != 0) { | 
					
						
							|  |  |  |         if (0 != read_memory( | 
					
						
							|  |  |  |                 handle, | 
					
						
							|  |  |  |                 interpreter_state_addr + debug_offsets.interpreter_state.threads_head, | 
					
						
							|  |  |  |                 sizeof(void*), | 
					
						
							|  |  |  |                 &thread_state_addr)) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             return -1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         while (thread_state_addr != 0) { | 
					
						
							|  |  |  |             if (0 != read_memory( | 
					
						
							|  |  |  |                     handle, | 
					
						
							|  |  |  |                     thread_state_addr + debug_offsets.thread_state.native_thread_id, | 
					
						
							|  |  |  |                     sizeof(this_tid), | 
					
						
							|  |  |  |                     &this_tid)) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 return -1; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (this_tid == (unsigned long)tid) { | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (0 != read_memory( | 
					
						
							|  |  |  |                     handle, | 
					
						
							|  |  |  |                     thread_state_addr + debug_offsets.thread_state.next, | 
					
						
							|  |  |  |                     sizeof(void*), | 
					
						
							|  |  |  |                     &thread_state_addr)) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 return -1; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (thread_state_addr == 0) { | 
					
						
							|  |  |  |             PyErr_SetString( | 
					
						
							|  |  |  |                 PyExc_RuntimeError, | 
					
						
							|  |  |  |                 "Can't find the specified thread in the remote process"); | 
					
						
							|  |  |  |             return -1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         if (0 != read_memory( | 
					
						
							|  |  |  |                 handle, | 
					
						
							|  |  |  |                 interpreter_state_addr + debug_offsets.interpreter_state.threads_main, | 
					
						
							|  |  |  |                 sizeof(void*), | 
					
						
							|  |  |  |                 &thread_state_addr)) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             return -1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (thread_state_addr == 0) { | 
					
						
							|  |  |  |             PyErr_SetString( | 
					
						
							|  |  |  |                 PyExc_RuntimeError, | 
					
						
							|  |  |  |                 "Can't find the main thread in the remote process"); | 
					
						
							|  |  |  |             return -1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Ensure our path is not too long
 | 
					
						
							|  |  |  |     if (debug_offsets.debugger_support.debugger_script_path_size <= strlen(debugger_script_path)) { | 
					
						
							|  |  |  |         PyErr_SetString(PyExc_ValueError, "Debugger script path is too long"); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     uintptr_t debugger_script_path_addr = (uintptr_t)( | 
					
						
							|  |  |  |         thread_state_addr + | 
					
						
							|  |  |  |         debug_offsets.debugger_support.remote_debugger_support + | 
					
						
							|  |  |  |         debug_offsets.debugger_support.debugger_script_path); | 
					
						
							|  |  |  |     if (0 != write_memory( | 
					
						
							|  |  |  |             handle, | 
					
						
							|  |  |  |             debugger_script_path_addr, | 
					
						
							|  |  |  |             strlen(debugger_script_path) + 1, | 
					
						
							|  |  |  |             debugger_script_path)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     int pending_call = 1; | 
					
						
							|  |  |  |     uintptr_t debugger_pending_call_addr = (uintptr_t)( | 
					
						
							|  |  |  |         thread_state_addr + | 
					
						
							|  |  |  |         debug_offsets.debugger_support.remote_debugger_support + | 
					
						
							|  |  |  |         debug_offsets.debugger_support.debugger_pending_call); | 
					
						
							|  |  |  |     if (0 != write_memory( | 
					
						
							|  |  |  |             handle, | 
					
						
							|  |  |  |             debugger_pending_call_addr, | 
					
						
							|  |  |  |             sizeof(int), | 
					
						
							|  |  |  |             &pending_call)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     uintptr_t eval_breaker; | 
					
						
							|  |  |  |     if (0 != read_memory( | 
					
						
							|  |  |  |             handle, | 
					
						
							|  |  |  |             thread_state_addr + debug_offsets.debugger_support.eval_breaker, | 
					
						
							|  |  |  |             sizeof(uintptr_t), | 
					
						
							|  |  |  |             &eval_breaker)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     eval_breaker |= _PY_EVAL_PLEASE_STOP_BIT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (0 != write_memory( | 
					
						
							|  |  |  |             handle, | 
					
						
							|  |  |  |             thread_state_addr + (uintptr_t)debug_offsets.debugger_support.eval_breaker, | 
					
						
							|  |  |  |             sizeof(uintptr_t), | 
					
						
							|  |  |  |             &eval_breaker)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif // defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int | 
					
						
							|  |  |  | _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | #if !defined(Py_SUPPORTS_REMOTE_DEBUG)
 | 
					
						
							|  |  |  |     PyErr_SetString(PyExc_RuntimeError, "Remote debugging is not supported on this platform"); | 
					
						
							|  |  |  |     return -1; | 
					
						
							|  |  |  | #elif !defined(Py_REMOTE_DEBUG)
 | 
					
						
							|  |  |  |     PyErr_SetString(PyExc_RuntimeError, "Remote debugging support has not been compiled in"); | 
					
						
							|  |  |  |     return -1; | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     PyThreadState *tstate = _PyThreadState_GET(); | 
					
						
							|  |  |  |     const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp); | 
					
						
							|  |  |  |     if (config->remote_debug != 1) { | 
					
						
							|  |  |  |         PyErr_SetString(PyExc_RuntimeError, "Remote debugging is not enabled"); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     proc_handle_t handle; | 
					
						
							|  |  |  |     if (init_proc_handle(&handle, pid) < 0) { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     int rc = send_exec_to_proc_handle(&handle, tid, debugger_script_path); | 
					
						
							|  |  |  |     cleanup_proc_handle(&handle); | 
					
						
							|  |  |  |     return rc; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  | 
 |