From 0664231cc0b393740a37b0dcbc92fb2b631628d5 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:51:20 +0500 Subject: [PATCH 1/8] Add Py_UNREACHABLE --- Modules/_testexternalinspection.c | 2 +- Python/remote_debug.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index b43e8b2155730f..0853259256685c 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -65,7 +65,7 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle) address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython"); } #else - address = 0; + Py_UNREACHABLE(); #endif return address; diff --git a/Python/remote_debug.h b/Python/remote_debug.h index cb1baf799052d5..3af0f7c9a93f25 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -699,7 +699,7 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) address = search_map_for_section(handle, "PyRuntime", "python"); } #else - address = 0; + Py_UNREACHABLE(); #endif return address; From cd9a499104a039b54e37de76cd5fce0715b4b27e Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:54:10 +0500 Subject: [PATCH 2/8] Remove unused refcnt --- Modules/_testexternalinspection.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index 0853259256685c..7b16de27a9070e 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -482,9 +482,6 @@ parse_task( return -1; } - uintptr_t refcnt; - read_ptr(handle, task_address + sizeof(Py_ssize_t), &refcnt); - PyObject* result = PyList_New(0); if (result == NULL) { return -1; From 3ed2837897f02e23cb0fb8f1ed34d2d61bc501ba Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:55:24 +0500 Subject: [PATCH 3/8] Reset result if can not close file descriptor --- Python/remote_debug.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 3af0f7c9a93f25..321d4060a63587 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -342,6 +342,7 @@ search_section_in_file(const char* secname, char* path, uintptr_t base, mach_vm_ munmap(map, fs.st_size); if (close(fd) != 0) { PyErr_SetFromErrno(PyExc_OSError); + result = 0; } return result; } @@ -495,6 +496,7 @@ search_elf_file_for_section( } if (fd >= 0 && close(fd) != 0) { PyErr_SetFromErrno(PyExc_OSError); + result = 0; } return result; } @@ -570,7 +572,10 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c } PyMem_Free(line); - fclose(maps_file); + if (fclose(maps_file) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + retval = 0; + } return retval; } From cc46cc28d9d20ae5180e3d8e69583a1afe5e6dc0 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:55:36 +0500 Subject: [PATCH 4/8] Set exception only if not set --- Python/remote_debug.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 321d4060a63587..12926a94757548 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -372,7 +372,9 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* s mach_port_t proc_ref = pid_to_task(handle->pid); if (proc_ref == 0) { - PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID"); + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID"); + } return 0; } From 6a72bf9c24eca95ec44f41b816fe756734af7cf2 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:56:10 +0500 Subject: [PATCH 5/8] Chain exception to get more info --- Modules/_testexternalinspection.c | 66 +++++++++++++++++++------------ Python/remote_debug.h | 9 +++++ 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index 7b16de27a9070e..e8350d6473bcb3 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -23,6 +23,18 @@ # define HAVE_PROCESS_VM_READV 0 #endif +#ifdef CHAIN_EXCEPTIONS +#error "CHAIN_EXCEPTIONS should not be defined" +#endif + +#define CHAIN_EXCEPTIONS(Type, Msg) \ + do { \ + PyObject *exc = PyErr_GetRaisedException(); \ + PyErr_SetString((Type), (Msg)); \ + _PyErr_ChainExceptions1(exc); \ + } while(0) + + struct _Py_AsyncioModuleDebugOffsets { struct _asyncio_task_object { uint64_t size; @@ -304,7 +316,7 @@ parse_task_name( if ((flags & Py_TPFLAGS_LONG_SUBCLASS)) { long res = read_py_long(handle, offsets, task_name_addr); if (res == -1) { - PyErr_SetString(PyExc_RuntimeError, "Failed to get task name"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to get task name"); return NULL; } return PyUnicode_FromFormat("Task-%d", res); @@ -1156,30 +1168,32 @@ get_all_awaited_by(PyObject* self, PyObject* args) return 0; } + PyObject *result = NULL; + uintptr_t runtime_start_addr = _Py_RemoteDebug_GetPyRuntimeAddress(handle); if (runtime_start_addr == 0) { if (!PyErr_Occurred()) { PyErr_SetString( PyExc_RuntimeError, "Failed to get .PyRuntime address"); } - return NULL; + goto result_err; } struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_addr, &local_debug_offsets)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read debug offsets"); - return NULL; + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + goto result_err; } struct _Py_AsyncioModuleDebugOffsets local_async_debug; if (read_async_debug(handle, &local_async_debug)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); - return NULL; + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); + goto result_err; } - PyObject *result = PyList_New(0); + result = PyList_New(0); if (result == NULL) { - return NULL; + goto result_err; } uint64_t interpreter_state_list_head = @@ -1256,7 +1270,7 @@ get_all_awaited_by(PyObject* self, PyObject* args) return result; result_err: - Py_DECREF(result); + Py_XDECREF(result); _Py_RemoteDebug_CleanupProcHandle(handle); return NULL; } @@ -1296,7 +1310,7 @@ get_stack_trace(PyObject* self, PyObject* args) struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read debug offsets"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); goto result_err; } @@ -1354,40 +1368,40 @@ get_async_stack_trace(PyObject* self, PyObject* args) return 0; } + PyObject *result = NULL; + uintptr_t runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle); if (runtime_start_address == 0) { if (!PyErr_Occurred()) { PyErr_SetString( PyExc_RuntimeError, "Failed to get .PyRuntime address"); } - return NULL; + goto result_err; } struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read debug offsets"); - return NULL; + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + goto result_err; } struct _Py_AsyncioModuleDebugOffsets local_async_debug; if (read_async_debug(handle, &local_async_debug)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); - return NULL; + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); + goto result_err; } - PyObject* result = PyList_New(1); + result = PyList_New(1); if (result == NULL) { - return NULL; + goto result_err; } PyObject* calls = PyList_New(0); if (calls == NULL) { - Py_DECREF(result); - return NULL; + goto result_err; } if (PyList_SetItem(result, 0, calls)) { /* steals ref to 'calls' */ - Py_DECREF(result); Py_DECREF(calls); - return NULL; + goto result_err; } uintptr_t running_task_addr = (uintptr_t)NULL; @@ -1395,7 +1409,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) handle, runtime_start_address, &local_debug_offsets, &local_async_debug, &running_task_addr) ) { - PyErr_SetString(PyExc_RuntimeError, "Failed to find running task"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to find running task"); goto result_err; } @@ -1410,7 +1424,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) running_task_addr + local_async_debug.asyncio_task_object.task_coro, &running_coro_addr )) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read running task coro"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read running task coro"); goto result_err; } @@ -1440,7 +1454,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) handle, runtime_start_address, &local_debug_offsets, &address_of_current_frame) ) { - PyErr_SetString(PyExc_RuntimeError, "Failed to find running frame"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to find running frame"); goto result_err; } @@ -1456,7 +1470,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) ); if (res < 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to parse async frame object"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to parse async frame object"); goto result_err; } @@ -1498,7 +1512,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) result_err: _Py_RemoteDebug_CleanupProcHandle(handle); - Py_DECREF(result); + Py_XDECREF(result); return NULL; } diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 12926a94757548..7627d65cea59c6 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -688,14 +688,18 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) address = search_windows_map_for_section(handle, "PyRuntime", L"python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL + PyObject *exc = PyErr_GetRaisedException(); PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); } #elif defined(__linux__) // On Linux, search for 'python' in executable or DLL address = search_linux_map_for_section(handle, "PyRuntime", "python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL + PyObject *exc = PyErr_GetRaisedException(); PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); } #elif defined(__APPLE__) && TARGET_OS_OSX // On macOS, try libpython first, then fall back to python @@ -704,6 +708,11 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) // TODO: Differentiate between not found and error PyErr_Clear(); address = search_map_for_section(handle, "PyRuntime", "python"); + if (address == 0) { + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); + } } #else Py_UNREACHABLE(); From 6466cf9f61fff26ed2af6b01b741a53e5d40ce6e Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 01:11:34 +0500 Subject: [PATCH 6/8] Remove error string from macos impl to keep PermissionError --- Python/remote_debug.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 7627d65cea59c6..edc77c302916ca 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -708,11 +708,6 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) // TODO: Differentiate between not found and error PyErr_Clear(); address = search_map_for_section(handle, "PyRuntime", "python"); - if (address == 0) { - PyObject *exc = PyErr_GetRaisedException(); - PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); - _PyErr_ChainExceptions1(exc); - } } #else Py_UNREACHABLE(); From e7183302c4b95f4221bd08c9d91b31a0d24eea12 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 01:16:34 +0500 Subject: [PATCH 7/8] Replace macro with static function --- Modules/_testexternalinspection.c | 32 ++++++++++--------------------- Python/remote_debug.h | 17 ++++++++++------ 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index e8350d6473bcb3..1ecbad56594336 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -23,18 +23,6 @@ # define HAVE_PROCESS_VM_READV 0 #endif -#ifdef CHAIN_EXCEPTIONS -#error "CHAIN_EXCEPTIONS should not be defined" -#endif - -#define CHAIN_EXCEPTIONS(Type, Msg) \ - do { \ - PyObject *exc = PyErr_GetRaisedException(); \ - PyErr_SetString((Type), (Msg)); \ - _PyErr_ChainExceptions1(exc); \ - } while(0) - - struct _Py_AsyncioModuleDebugOffsets { struct _asyncio_task_object { uint64_t size; @@ -316,7 +304,7 @@ parse_task_name( if ((flags & Py_TPFLAGS_LONG_SUBCLASS)) { long res = read_py_long(handle, offsets, task_name_addr); if (res == -1) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to get task name"); + chain_exceptions(PyExc_RuntimeError, "Failed to get task name"); return NULL; } return PyUnicode_FromFormat("Task-%d", res); @@ -1181,13 +1169,13 @@ get_all_awaited_by(PyObject* self, PyObject* args) struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_addr, &local_debug_offsets)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets"); goto result_err; } struct _Py_AsyncioModuleDebugOffsets local_async_debug; if (read_async_debug(handle, &local_async_debug)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); goto result_err; } @@ -1310,7 +1298,7 @@ get_stack_trace(PyObject* self, PyObject* args) struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets"); goto result_err; } @@ -1381,13 +1369,13 @@ get_async_stack_trace(PyObject* self, PyObject* args) struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets"); goto result_err; } struct _Py_AsyncioModuleDebugOffsets local_async_debug; if (read_async_debug(handle, &local_async_debug)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); goto result_err; } @@ -1409,7 +1397,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) handle, runtime_start_address, &local_debug_offsets, &local_async_debug, &running_task_addr) ) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to find running task"); + chain_exceptions(PyExc_RuntimeError, "Failed to find running task"); goto result_err; } @@ -1424,7 +1412,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) running_task_addr + local_async_debug.asyncio_task_object.task_coro, &running_coro_addr )) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read running task coro"); + chain_exceptions(PyExc_RuntimeError, "Failed to read running task coro"); goto result_err; } @@ -1454,7 +1442,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) handle, runtime_start_address, &local_debug_offsets, &address_of_current_frame) ) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to find running frame"); + chain_exceptions(PyExc_RuntimeError, "Failed to find running frame"); goto result_err; } @@ -1470,7 +1458,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) ); if (res < 0) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to parse async frame object"); + chain_exceptions(PyExc_RuntimeError, "Failed to parse async frame object"); goto result_err; } diff --git a/Python/remote_debug.h b/Python/remote_debug.h index edc77c302916ca..2072f6e03102a8 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -109,6 +109,15 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) { handle->pid = 0; } +// Helper to chain exceptions and avoid repetitions +static void +chain_exceptions(PyObject *exception, const char *string) +{ + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(exception, string); + _PyErr_ChainExceptions1(exc); +} + #if defined(__APPLE__) && TARGET_OS_OSX static uintptr_t @@ -688,18 +697,14 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) address = search_windows_map_for_section(handle, "PyRuntime", L"python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL - PyObject *exc = PyErr_GetRaisedException(); - PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); - _PyErr_ChainExceptions1(exc); + chain_exceptions(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); } #elif defined(__linux__) // On Linux, search for 'python' in executable or DLL address = search_linux_map_for_section(handle, "PyRuntime", "python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL - PyObject *exc = PyErr_GetRaisedException(); - PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); - _PyErr_ChainExceptions1(exc); + chain_exceptions(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); } #elif defined(__APPLE__) && TARGET_OS_OSX // On macOS, try libpython first, then fall back to python From 40e099b080700eb049349e5fb64634055177877a Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 01:33:58 +0500 Subject: [PATCH 8/8] Remove chain_exceptions from remote_debug to make clang on macos happy - I believe it is not worth to add conditional compilation of such small function like chain_exceptions --- Modules/_testexternalinspection.c | 9 +++++++++ Python/remote_debug.h | 17 ++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index 1ecbad56594336..b65c5821443ebf 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -45,6 +45,15 @@ struct _Py_AsyncioModuleDebugOffsets { } asyncio_thread_state; }; +// Helper to chain exceptions and avoid repetitions +static void +chain_exceptions(PyObject *exception, const char *string) +{ + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(exception, string); + _PyErr_ChainExceptions1(exc); +} + // Get the PyAsyncioDebug section address for any platform static uintptr_t _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 2072f6e03102a8..edc77c302916ca 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -109,15 +109,6 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) { handle->pid = 0; } -// Helper to chain exceptions and avoid repetitions -static void -chain_exceptions(PyObject *exception, const char *string) -{ - PyObject *exc = PyErr_GetRaisedException(); - PyErr_SetString(exception, string); - _PyErr_ChainExceptions1(exc); -} - #if defined(__APPLE__) && TARGET_OS_OSX static uintptr_t @@ -697,14 +688,18 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) address = search_windows_map_for_section(handle, "PyRuntime", L"python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL - chain_exceptions(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); } #elif defined(__linux__) // On Linux, search for 'python' in executable or DLL address = search_linux_map_for_section(handle, "PyRuntime", "python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL - chain_exceptions(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); } #elif defined(__APPLE__) && TARGET_OS_OSX // On macOS, try libpython first, then fall back to python pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy