--- /dev/null
+Fix crash in ``_remote_debugging`` that caused ``test_external_inspection`` to intermittently fail. Patch by Taegyun Kim.
#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"
#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
// 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;
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;
// 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;
) {
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;
}
// 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);
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",
}
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;
}