diff --git a/Python/remote_debugging.c b/Python/remote_debugging.c index 189bec25390640..46f8f44a61844c 100644 --- a/Python/remote_debugging.c +++ b/Python/remote_debugging.c @@ -38,9 +38,9 @@ #ifdef MS_WINDOWS // Windows includes and definitions - #include - #include - #include +#include +#include +#include #endif #include @@ -51,10 +51,10 @@ #include #include #ifndef MS_WINDOWS - #include - #include - #include - #include +#include +#include +#include +#include #endif #ifndef Py_BUILD_CORE_BUILTIN @@ -156,7 +156,7 @@ return_section_address( int nsects = cmd->nsects; struct section_64* sec = (struct section_64*)( (void*)cmd + sizeof(struct segment_command_64) - ); + ); for (int j = 0; j < nsects; j++) { if (strcmp(sec[j].sectname, section) == 0) { return base + sec[j].addr - vmaddr; @@ -201,19 +201,19 @@ search_section_in_file(const char* secname, char* path, uintptr_t base, mach_vm_ struct mach_header_64* hdr = (struct mach_header_64*)map; switch (hdr->magic) { - case MH_MAGIC: - case MH_CIGAM: - case FAT_MAGIC: - case FAT_CIGAM: - PyErr_SetString(PyExc_RuntimeError, "32-bit Mach-O binaries are not supported"); - break; - case MH_MAGIC_64: - case MH_CIGAM_64: - result = return_section_address(secname, proc_ref, base, map); - break; - default: - PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic"); - break; + case MH_MAGIC: + case MH_CIGAM: + case FAT_MAGIC: + case FAT_CIGAM: + PyErr_SetString(PyExc_RuntimeError, "32-bit Mach-O binaries are not supported"); + break; + case MH_MAGIC_64: + case MH_CIGAM_64: + result = return_section_address(secname, proc_ref, base, map); + break; + default: + PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic"); + break; } munmap(map, fs.st_size); @@ -254,13 +254,13 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* s int match_found = 0; char map_filename[MAXPATHLEN + 1]; while (mach_vm_region( - proc_ref, - &address, - &size, - VM_REGION_BASIC_INFO_64, - (vm_region_info_t)®ion_info, - &count, - &object_name) == KERN_SUCCESS) + proc_ref, + &address, + &size, + VM_REGION_BASIC_INFO_64, + (vm_region_info_t)®ion_info, + &count, + &object_name) == KERN_SUCCESS) { if ((region_info.protection & VM_PROT_READ) == 0 || (region_info.protection & VM_PROT_EXECUTE) == 0) { @@ -404,7 +404,7 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* m if (section != NULL && first_load_segment != NULL) { uintptr_t elf_load_addr = first_load_segment->p_vaddr - - (first_load_segment->p_vaddr % first_load_segment->p_align); + - (first_load_segment->p_vaddr % first_load_segment->p_align); result = start_address + (uintptr_t)section->sh_addr - elf_load_addr; } @@ -422,26 +422,119 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* m #ifdef MS_WINDOWS +static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char* secname) { + HANDLE hFile = CreateFileW(mod_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0); + if (!hMap) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hFile); + return NULL; + } + + BYTE* mapView = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); + if (!mapView) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hMap); + CloseHandle(hFile); + return NULL; + } + + IMAGE_DOS_HEADER* pDOSHeader = (IMAGE_DOS_HEADER*)mapView; + if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) { + PyErr_SetString(PyExc_RuntimeError, "Invalid DOS signature."); + UnmapViewOfFile(mapView); + CloseHandle(hMap); + CloseHandle(hFile); + return NULL; + } + + IMAGE_NT_HEADERS* pNTHeaders = (IMAGE_NT_HEADERS*)(mapView + pDOSHeader->e_lfanew); + if (pNTHeaders->Signature != IMAGE_NT_SIGNATURE) { + PyErr_SetString(PyExc_RuntimeError, "Invalid NT signature."); + UnmapViewOfFile(mapView); + CloseHandle(hMap); + CloseHandle(hFile); + return NULL; + } + + IMAGE_SECTION_HEADER* pSection_header = (IMAGE_SECTION_HEADER*)(mapView + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)); + void* runtime_addr = NULL; + + for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) { + const char* name = (const char*)pSection_header[i].Name; + if (strncmp(name, secname, IMAGE_SIZEOF_SHORT_NAME) == 0) { + runtime_addr = remote_base + pSection_header[i].VirtualAddress; + break; + } + } + + UnmapViewOfFile(mapView); + CloseHandle(hMap); + CloseHandle(hFile); + + return runtime_addr; +} + + static uintptr_t -search_map_for_section(proc_handle_t* handle, const char* secname, const char* substr) { - //TODO: Implement this function - PyErr_SetString(PyExc_RuntimeError, "search_map_for_section not implemented on Windows"); - return 0; +search_windows_map_for_section(proc_handle_t* handle, const char* secname, const wchar_t* substr) { + HANDLE hProcSnap; + do { + hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, handle->pid); + } while (hProcSnap == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH); + + if (hProcSnap == INVALID_HANDLE_VALUE) { + PyErr_SetString(PyExc_PermissionError, "Unable to create module snapshot. Check permissions or PID."); + return 0; + } + + MODULEENTRY32W moduleEntry; + moduleEntry.dwSize = sizeof(moduleEntry); + void* runtime_addr = NULL; + + for (BOOL hasModule = Module32FirstW(hProcSnap, &moduleEntry); hasModule; hasModule = Module32NextW(hProcSnap, &moduleEntry)) { + // Look for either python executable or DLL + if (wcsstr(moduleEntry.szModule, substr)) { + runtime_addr = analyze_pe(moduleEntry.szExePath, moduleEntry.modBaseAddr, secname); + if (runtime_addr != NULL) { + break; + } + } + } + + CloseHandle(hProcSnap); + return (uintptr_t)runtime_addr; } #endif // MS_WINDOWS // Get the PyRuntime section address for any platform static uintptr_t -get_py_runtime(proc_handle_t *handle) +get_py_runtime(proc_handle_t* handle) { - // Try libpython first, then fall back to python - uintptr_t address = search_map_for_section(handle, "PyRuntime", "libpython"); + uintptr_t address = 0; + +#ifndef MS_WINDOWS + // On non-Windows platforms, try libpython first, then fall back to python + address = search_map_for_section(handle, "PyRuntime", "libpython"); if (address == 0) { // TODO: Differentiate between not found and error PyErr_Clear(); address = search_map_for_section(handle, "PyRuntime", "python"); } +#else + // On Windows, search for 'python' in executable or DLL + address = search_windows_map_for_section(handle, "PyRuntime", L"python"); + if (address == 0) { + // Error out: 'python' substring covers both executable and DLL + PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + } +#endif + return address; } @@ -450,9 +543,16 @@ static Py_ssize_t read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* dst) { #ifdef MS_WINDOWS - // TODO: Implement this function - PyErr_SetString(PyExc_RuntimeError, "Memory reading is not supported on Windows"); - return -1; + SIZE_T read_bytes = 0; + SIZE_T result = 0; + do { + if (!ReadProcessMemory(handle->hProcess, (LPCVOID)(remote_address + result), (char*)dst + result, len - result, &read_bytes)) { + PyErr_SetFromWindowsErr(0); + return -1; + } + result += read_bytes; + } while (result < len); + return (Py_ssize_t)result; #elif defined(__linux__) && HAVE_PROCESS_VM_READV struct iovec local[1]; struct iovec remote[1]; @@ -477,22 +577,22 @@ read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* ds #elif defined(__APPLE__) && TARGET_OS_OSX Py_ssize_t result = -1; kern_return_t kr = mach_vm_read_overwrite( - pid_to_task(handle->pid), - (mach_vm_address_t)remote_address, - len, - (mach_vm_address_t)dst, - (mach_vm_size_t*)&result); + pid_to_task(handle->pid), + (mach_vm_address_t)remote_address, + len, + (mach_vm_address_t)dst, + (mach_vm_size_t*)&result); if (kr != KERN_SUCCESS) { switch (kr) { - case KERN_PROTECTION_FAILURE: - PyErr_SetString(PyExc_PermissionError, "Not enough permissions to read memory"); - break; - case KERN_INVALID_ARGUMENT: - PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_read_overwrite"); - break; - default: - PyErr_SetString(PyExc_RuntimeError, "Unknown error reading memory"); + case KERN_PROTECTION_FAILURE: + PyErr_SetString(PyExc_PermissionError, "Not enough permissions to read memory"); + break; + case KERN_INVALID_ARGUMENT: + PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_read_overwrite"); + break; + default: + PyErr_SetString(PyExc_RuntimeError, "Unknown error reading memory"); } return -1; } @@ -510,9 +610,16 @@ static Py_ssize_t write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src) { #ifdef MS_WINDOWS - // TODO: Implement this function - PyErr_SetString(PyExc_RuntimeError, "Memory writing is not supported on Windows"); - return -1; + 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 (Py_ssize_t)result; #elif defined(__linux__) && HAVE_PROCESS_VM_READV struct iovec local[1]; struct iovec remote[1]; @@ -536,21 +643,21 @@ write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const return result; #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); + 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_SetString(PyExc_RuntimeError, "Unknown error writing memory"); + 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_SetString(PyExc_RuntimeError, "Unknown error writing memory"); } return -1; } @@ -609,7 +716,7 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) uintptr_t runtime_start_address; struct _Py_DebugOffsets local_debug_offsets; - + if (read_offsets(&handle, &runtime_start_address, &local_debug_offsets)) { cleanup_proc_handle(&handle); return -1; @@ -619,10 +726,10 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) uintptr_t address_of_interpreter_state; Py_ssize_t bytes = read_memory( - &handle, - runtime_start_address + interpreter_state_list_head, - sizeof(void*), - &address_of_interpreter_state); + &handle, + runtime_start_address + interpreter_state_list_head, + sizeof(void*), + &address_of_interpreter_state); if (bytes == -1) { cleanup_proc_handle(&handle); return -1; @@ -636,15 +743,15 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) int is_remote_debugging_enabled = 0; bytes = read_memory( - &handle, - address_of_interpreter_state + local_debug_offsets.debugger_support.remote_debugging_enabled, - sizeof(int), - &is_remote_debugging_enabled); + &handle, + address_of_interpreter_state + local_debug_offsets.debugger_support.remote_debugging_enabled, + sizeof(int), + &is_remote_debugging_enabled); if (bytes == -1) { cleanup_proc_handle(&handle); return -1; } - + if (is_remote_debugging_enabled == 0) { PyErr_SetString(PyExc_RuntimeError, "Remote debugging is not enabled in the remote process"); cleanup_proc_handle(&handle); @@ -653,37 +760,37 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) uintptr_t address_of_thread; pid_t this_tid = 0; - + if (tid != 0) { bytes = read_memory( - &handle, - address_of_interpreter_state + local_debug_offsets.interpreter_state.threads_head, - sizeof(void*), - &address_of_thread); + &handle, + address_of_interpreter_state + local_debug_offsets.interpreter_state.threads_head, + sizeof(void*), + &address_of_thread); if (bytes == -1) { cleanup_proc_handle(&handle); return -1; } while (address_of_thread != 0) { bytes = read_memory( - &handle, - address_of_thread + local_debug_offsets.thread_state.native_thread_id, - sizeof(pid_t), - &this_tid); + &handle, + address_of_thread + local_debug_offsets.thread_state.native_thread_id, + sizeof(pid_t), + &this_tid); if (bytes == -1) { cleanup_proc_handle(&handle); return -1; } - + if (this_tid == tid) { break; } - + bytes = read_memory( - &handle, - address_of_thread + local_debug_offsets.thread_state.next, - sizeof(void*), - &address_of_thread); + &handle, + address_of_thread + local_debug_offsets.thread_state.next, + sizeof(void*), + &address_of_thread); if (bytes == -1) { cleanup_proc_handle(&handle); return -1; @@ -691,10 +798,10 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) } } else { bytes = read_memory( - &handle, - address_of_interpreter_state + local_debug_offsets.interpreter_state.threads_main, - sizeof(void*), - &address_of_thread); + &handle, + address_of_interpreter_state + local_debug_offsets.interpreter_state.threads_main, + sizeof(void*), + &address_of_thread); if (bytes == -1) { cleanup_proc_handle(&handle); return -1; @@ -709,15 +816,15 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) uintptr_t eval_breaker; bytes = read_memory( - &handle, - address_of_thread + local_debug_offsets.debugger_support.eval_breaker, - sizeof(uintptr_t), - &eval_breaker); + &handle, + address_of_thread + local_debug_offsets.debugger_support.eval_breaker, + sizeof(uintptr_t), + &eval_breaker); if (bytes == -1) { cleanup_proc_handle(&handle); return -1; } - + eval_breaker |= _PY_EVAL_PLEASE_STOP_BIT; // Ensure our path is not too long @@ -729,14 +836,14 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) if (debugger_script_path != NULL) { uintptr_t debugger_script_path_addr = ( - address_of_thread + - local_debug_offsets.debugger_support.remote_debugger_support + - local_debug_offsets.debugger_support.debugger_script_path); + address_of_thread + + local_debug_offsets.debugger_support.remote_debugger_support + + local_debug_offsets.debugger_support.debugger_script_path); bytes = write_memory( - &handle, - debugger_script_path_addr, - strlen(debugger_script_path) + 1, - debugger_script_path); + &handle, + debugger_script_path_addr, + strlen(debugger_script_path) + 1, + debugger_script_path); if (bytes == -1) { cleanup_proc_handle(&handle); return -1; @@ -745,14 +852,14 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) int pending_call = 1; uintptr_t debugger_pending_call_addr = ( - address_of_thread + - local_debug_offsets.debugger_support.remote_debugger_support + - local_debug_offsets.debugger_support.debugger_pending_call); + address_of_thread + + local_debug_offsets.debugger_support.remote_debugger_support + + local_debug_offsets.debugger_support.debugger_pending_call); bytes = write_memory( - &handle, - debugger_pending_call_addr, - sizeof(int), - &pending_call); + &handle, + debugger_pending_call_addr, + sizeof(int), + &pending_call); if (bytes == -1) { cleanup_proc_handle(&handle); @@ -760,10 +867,10 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) } bytes = write_memory( - &handle, - address_of_thread + local_debug_offsets.debugger_support.eval_breaker, - sizeof(uintptr_t), - &eval_breaker); + &handle, + address_of_thread + local_debug_offsets.debugger_support.eval_breaker, + sizeof(uintptr_t), + &eval_breaker); if (bytes == -1) { cleanup_proc_handle(&handle); @@ -771,13 +878,13 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path) } bytes = read_memory( - &handle, - address_of_thread + local_debug_offsets.debugger_support.eval_breaker, - sizeof(uintptr_t), - &eval_breaker); + &handle, + address_of_thread + local_debug_offsets.debugger_support.eval_breaker, + sizeof(uintptr_t), + &eval_breaker); printf("Eval breaker: %p\n", (void*)eval_breaker); cleanup_proc_handle(&handle); return 0; -} + }