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]