https://github.com/python/cpython/commit/c2d3d6b0dd87aaea2f3a72419e1d1488d3ab9503
commit: c2d3d6b0dd87aaea2f3a72419e1d1488d3ab9503
branch: main
author: Taegyun Kim <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-02-26T21:14:34Z
summary:

gh-144316: Fix missing exception in _remote_debugging with debug=False (#144442)

files:
A Misc/NEWS.d/next/Library/2026-02-03-19-57-41.gh-issue-144316.wop870.rst
M Modules/_remote_debugging/_remote_debugging.h
M Modules/_remote_debugging/asyncio.c
M Modules/_remote_debugging/code_objects.c
M Modules/_remote_debugging/frames.c
M Modules/_remote_debugging/module.c
M Python/remote_debug.h

diff --git 
a/Misc/NEWS.d/next/Library/2026-02-03-19-57-41.gh-issue-144316.wop870.rst 
b/Misc/NEWS.d/next/Library/2026-02-03-19-57-41.gh-issue-144316.wop870.rst
new file mode 100644
index 00000000000000..b9d0749f56ba6a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-02-03-19-57-41.gh-issue-144316.wop870.rst
@@ -0,0 +1 @@
+Fix crash in ``_remote_debugging`` that caused ``test_external_inspection`` to 
intermittently fail. Patch by Taegyun Kim.
diff --git a/Modules/_remote_debugging/_remote_debugging.h 
b/Modules/_remote_debugging/_remote_debugging.h
index 78add74423b608..7bcb2f483234ec 100644
--- a/Modules/_remote_debugging/_remote_debugging.h
+++ b/Modules/_remote_debugging/_remote_debugging.h
@@ -29,6 +29,7 @@ extern "C" {
 #include "internal/pycore_interpframe.h"    // FRAME_OWNED_BY_INTERPRETER
 #include "internal/pycore_llist.h"          // struct llist_node
 #include "internal/pycore_long.h"           // _PyLong_GetZero
+#include "internal/pycore_pyerrors.h"       // _PyErr_FormatFromCause
 #include "internal/pycore_stackref.h"       // Py_TAG_BITS
 #include "../../Python/remote_debug.h"
 
@@ -173,10 +174,13 @@ typedef enum _WIN32_THREADSTATE {
 #define THREAD_STATUS_HAS_EXCEPTION       (1 << 4)
 
 /* Exception cause macro */
-#define set_exception_cause(unwinder, exc_type, message) \
-    if (unwinder->debug) { \
-        _set_debug_exception_cause(exc_type, message); \
-    }
+#define set_exception_cause(unwinder, exc_type, message)                       
       \
+    do {                                                                       
       \
+        assert(PyErr_Occurred() && "function returned -1 without setting 
exception"); \
+        if (unwinder->debug) {                                                 
       \
+            _set_debug_exception_cause(exc_type, message);                     
       \
+        }                                                                      
       \
+    } while (0)
 
 /* ============================================================================
  * TYPE DEFINITIONS
diff --git a/Modules/_remote_debugging/asyncio.c 
b/Modules/_remote_debugging/asyncio.c
index fc059659511fd8..69478634de6926 100644
--- a/Modules/_remote_debugging/asyncio.c
+++ b/Modules/_remote_debugging/asyncio.c
@@ -121,6 +121,7 @@ iterate_set_entries(
 
     // Validate mask and num_els to prevent huge loop iterations from garbage 
data
     if (mask < 0 || mask >= MAX_SET_TABLE_SIZE || num_els < 0 || num_els > 
mask + 1) {
+        PyErr_SetString(PyExc_RuntimeError, "Invalid set object (corrupted 
remote memory)");
         set_exception_cause(unwinder, PyExc_RuntimeError,
             "Invalid set object (corrupted remote memory)");
         return -1;
diff --git a/Modules/_remote_debugging/code_objects.c 
b/Modules/_remote_debugging/code_objects.c
index 9b7b4dc22b873b..91f7a02005391a 100644
--- a/Modules/_remote_debugging/code_objects.c
+++ b/Modules/_remote_debugging/code_objects.c
@@ -446,6 +446,9 @@ parse_code_object(RemoteUnwinderObject *unwinder,
     if (tlbc_entry) {
         // Validate index bounds (also catches negative values since 
tlbc_index is signed)
         if (ctx->tlbc_index < 0 || ctx->tlbc_index >= 
tlbc_entry->tlbc_array_size) {
+            PyErr_Format(PyExc_RuntimeError,
+                "Invalid tlbc_index %d (array size %zd, corrupted remote 
memory)",
+                ctx->tlbc_index, tlbc_entry->tlbc_array_size);
             set_exception_cause(unwinder, PyExc_RuntimeError,
                 "Invalid tlbc_index (corrupted remote memory)");
             goto error;
diff --git a/Modules/_remote_debugging/frames.c 
b/Modules/_remote_debugging/frames.c
index 02c48205b85a37..2ace0c0f7676ae 100644
--- a/Modules/_remote_debugging/frames.c
+++ b/Modules/_remote_debugging/frames.c
@@ -49,6 +49,8 @@ process_single_stack_chunk(
         // Size must be at least enough for the header and reasonably bounded
         if (actual_size <= offsetof(_PyStackChunk, data) || actual_size > 
MAX_STACK_CHUNK_SIZE) {
             PyMem_RawFree(this_chunk);
+            PyErr_Format(PyExc_RuntimeError,
+                "Invalid stack chunk size %zu (corrupted remote memory)", 
actual_size);
             set_exception_cause(unwinder, PyExc_RuntimeError,
                 "Invalid stack chunk size (corrupted remote memory)");
             return -1;
@@ -244,6 +246,7 @@ parse_frame_from_chunks(
 ) {
     void *frame_ptr = find_frame_in_chunks(chunks, address);
     if (!frame_ptr) {
+        PyErr_Format(PyExc_RuntimeError, "Frame at address 0x%lx not found in 
stack chunks", address);
         set_exception_cause(unwinder, PyExc_RuntimeError, "Frame not found in 
stack chunks");
         return -1;
     }
diff --git a/Modules/_remote_debugging/module.c 
b/Modules/_remote_debugging/module.c
index 26ebed13098f0e..040bd3db377315 100644
--- a/Modules/_remote_debugging/module.c
+++ b/Modules/_remote_debugging/module.c
@@ -595,6 +595,9 @@ 
_remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
                     // Detect cycle: if current_tstate didn't advance, we have 
corrupted data
                     if (current_tstate == prev_tstate) {
                         Py_DECREF(interpreter_threads);
+                        PyErr_Format(PyExc_RuntimeError,
+                            "Thread list cycle detected at address 0x%lx 
(corrupted remote memory)",
+                            current_tstate);
                         set_exception_cause(self, PyExc_RuntimeError,
                             "Thread list cycle detected (corrupted remote 
memory)");
                         Py_CLEAR(result);
diff --git a/Python/remote_debug.h b/Python/remote_debug.h
index 4ae1166e885485..7628fb04ba5bae 100644
--- a/Python/remote_debug.h
+++ b/Python/remote_debug.h
@@ -1302,6 +1302,7 @@ _Py_RemoteDebug_PagedReadRemoteMemory(proc_handle_t 
*handle,
             if (entry->data == NULL) {
                 entry->data = PyMem_RawMalloc(page_size);
                 if (entry->data == NULL) {
+                    PyErr_NoMemory();
                     _set_debug_exception_cause(PyExc_MemoryError,
                         "Cannot allocate %zu bytes for page cache entry "
                         "during read from PID %d at address 0x%lx",
@@ -1311,7 +1312,7 @@ _Py_RemoteDebug_PagedReadRemoteMemory(proc_handle_t 
*handle,
             }
 
             if (_Py_RemoteDebug_ReadRemoteMemory(handle, page_base, page_size, 
entry->data) < 0) {
-                // Try to just copy the exact ammount as a fallback
+                // Try to just copy the exact amount as a fallback
                 PyErr_Clear();
                 goto fallback;
             }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to