Files
cpython/Modules/_remote_debugging_module.c
Jelle Zijlstra 6603289847 [3.14] gh-159739: Avoid destroying a remote unwinder cache twice (GH-159982) (#160084)
Leave cleanup of the code-object cache to the unwinder deallocator if
allocating the thread-local bytecode cache fails.

(cherry picked from commit b577a5403a)
2026-10-21 19:13:20 +00:00

3783 lines
124 KiB
C

/******************************************************************************
* Python Remote Debugging Module
*
* This module provides functionality to debug Python processes remotely by
* reading their memory and reconstructing stack traces and asyncio task states.
******************************************************************************/
#define _GNU_SOURCE
/* ============================================================================
* HEADERS AND INCLUDES
* ============================================================================ */
#ifndef Py_BUILD_CORE_BUILTIN
# define Py_BUILD_CORE_MODULE 1
#endif
#include "Python.h"
#include <internal/pycore_debug_offsets.h> // _Py_DebugOffsets
#include <internal/pycore_global_objects.h>
#include <internal/pycore_frame.h> // FRAME_SUSPENDED_YIELD_FROM
#include <internal/pycore_interpframe.h> // FRAME_OWNED_BY_CSTACK
#include <internal/pycore_llist.h> // struct llist_node
#include <internal/pycore_stackref.h> // Py_TAG_BITS
#include <internal/pycore_tstate.h> // _PyThreadStateImpl
#include "../Python/remote_debug.h"
// gh-141784: Python.h header must be included first, before system headers.
// Otherwise, some types such as ino_t can be defined differently, causing ABI
// issues.
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef HAVE_PROCESS_VM_READV
# define HAVE_PROCESS_VM_READV 0
#endif
/* ============================================================================
* TYPE DEFINITIONS AND STRUCTURES
* ============================================================================ */
#define GET_MEMBER(type, obj, offset) \
(*(type *)memcpy(&(type){0}, (const char *)(obj) + (offset), sizeof(type)))
#define CLEAR_PTR_TAG(ptr) (((uintptr_t)(ptr) & ~Py_TAG_BITS))
#define GET_MEMBER_NO_TAG(type, obj, offset) \
(type)(CLEAR_PTR_TAG(GET_MEMBER(type, obj, offset)))
/* Size macros for opaque buffers */
#define SIZEOF_BYTES_OBJ sizeof(PyBytesObject)
#define SIZEOF_CODE_OBJ sizeof(PyCodeObject)
#define SIZEOF_GEN_OBJ sizeof(PyGenObject)
#define SIZEOF_INTERP_FRAME sizeof(_PyInterpreterFrame)
#define SIZEOF_LLIST_NODE sizeof(struct llist_node)
#define SIZEOF_PAGE_CACHE_ENTRY sizeof(page_cache_entry_t)
#define SIZEOF_PYOBJECT sizeof(PyObject)
#define SIZEOF_SET_OBJ sizeof(PySetObject)
#define SIZEOF_TASK_OBJ 4096
#define SIZEOF_THREAD_STATE sizeof(PyThreadState)
#define SIZEOF_TYPE_OBJ sizeof(PyTypeObject)
#define SIZEOF_UNICODE_OBJ sizeof(PyUnicodeObject)
#define SIZEOF_LONG_OBJ sizeof(PyLongObject)
// Calculate the minimum buffer size needed to read interpreter state fields
// We need to read code_object_generation and potentially tlbc_generation
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifdef Py_GIL_DISABLED
#define INTERP_STATE_MIN_SIZE MAX(MAX(MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
offsetof(PyInterpreterState, tlbc_indices.tlbc_generation) + sizeof(uint32_t)), \
offsetof(PyInterpreterState, threads.head) + sizeof(void*)), \
offsetof(PyInterpreterState, _gil.last_holder) + sizeof(PyThreadState*))
#else
#define INTERP_STATE_MIN_SIZE MAX(MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
offsetof(PyInterpreterState, threads.head) + sizeof(void*)), \
offsetof(PyInterpreterState, _gil.last_holder) + sizeof(PyThreadState*))
#endif
#define INTERP_STATE_BUFFER_SIZE MAX(INTERP_STATE_MIN_SIZE, 256)
#define MAX_STACK_CHUNK_SIZE (16 * 1024 * 1024) /* 16 MB max for stack chunks */
#define MAX_SET_TABLE_SIZE (1 << 20) /* 1 million entries max for set iteration */
#define MAX_FRAME_CHAIN_DEPTH 1024 /* Iteration bound for frame chain walks */
#define MAX_TASK_WAITER_WALK_TASKS (1 << 14) /* Total-task bound for waiter walks */
#define MAX_LONG_DIGITS 64 /* Allows values up to ~2^1920 */
// Copied from Modules/_asynciomodule.c because it's not exported
struct _Py_AsyncioModuleDebugOffsets {
struct _asyncio_task_object {
uint64_t size;
uint64_t task_name;
uint64_t task_awaited_by;
uint64_t task_is_task;
uint64_t task_awaited_by_is_set;
uint64_t task_coro;
uint64_t task_node;
} asyncio_task_object;
struct _asyncio_interpreter_state {
uint64_t size;
uint64_t asyncio_tasks_head;
} asyncio_interpreter_state;
struct _asyncio_thread_state {
uint64_t size;
uint64_t asyncio_running_loop;
uint64_t asyncio_running_task;
uint64_t asyncio_tasks_head;
} asyncio_thread_state;
};
/* Treat the remote debug tables as untrusted input and validate every
* size/offset we later dereference against a fixed local buffer or object
* layout before the unwinder starts using them. */
#define FIELD_SIZE(type, member) sizeof(((type *)0)->member)
#define PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS (-2)
static inline int
validate_section_size(const char *section_name, uint64_t size)
{
if (size == 0) {
PyErr_Format(
PyExc_RuntimeError,
"Invalid debug offsets: %s.size must be greater than zero",
section_name);
return -1;
}
return 0;
}
static inline int
validate_read_size(const char *section_name, uint64_t size, size_t buffer_size)
{
if (validate_section_size(section_name, size) < 0) {
return -1;
}
if (size > buffer_size) {
PyErr_Format(
PyExc_RuntimeError,
"Invalid debug offsets: %s.size=%llu exceeds local buffer size %zu",
section_name,
(unsigned long long)size,
buffer_size);
return -1;
}
return 0;
}
static inline int
validate_span(
const char *field_name,
uint64_t offset,
size_t width,
uint64_t limit,
const char *limit_name)
{
uint64_t span = (uint64_t)width;
if (span > limit || offset > limit - span) {
PyErr_Format(
PyExc_RuntimeError,
"Invalid debug offsets: %s=%llu with width %zu exceeds %s %llu",
field_name,
(unsigned long long)offset,
width,
limit_name,
(unsigned long long)limit);
return -1;
}
return 0;
}
static inline int
validate_alignment(
const char *field_name,
uint64_t offset,
size_t alignment)
{
if (alignment > 1 && offset % alignment != 0) {
PyErr_Format(
PyExc_RuntimeError,
"Invalid debug offsets: %s=%llu is not aligned to %zu bytes",
field_name,
(unsigned long long)offset,
alignment);
return -1;
}
return 0;
}
static inline int
validate_field(
const char *field_name,
uint64_t offset,
uint64_t reported_size,
size_t width,
size_t alignment,
size_t buffer_size)
{
if (validate_alignment(field_name, offset, alignment) < 0) {
return -1;
}
if (validate_span(field_name, offset, width, reported_size, "reported size") < 0) {
return -1;
}
return validate_span(field_name, offset, width, buffer_size, "local buffer size");
}
static inline int
validate_fixed_field(
const char *field_name,
uint64_t offset,
size_t width,
size_t alignment,
size_t buffer_size)
{
if (validate_alignment(field_name, offset, alignment) < 0) {
return -1;
}
return validate_span(field_name, offset, width, buffer_size, "local buffer size");
}
#define PY_REMOTE_DEBUG_VALIDATE_SECTION(section) \
do { \
if (validate_section_size(#section, debug_offsets->section.size) < 0) { \
return -1; \
} \
} while (0)
#define PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(section, buffer_size) \
do { \
if (validate_read_size(#section, debug_offsets->section.size, buffer_size) < 0) { \
return -1; \
} \
} while (0)
#define PY_REMOTE_DEBUG_VALIDATE_FIELD(section, field, field_size, field_alignment, buffer_size) \
do { \
if (validate_field( \
#section "." #field, \
debug_offsets->section.field, \
debug_offsets->section.size, \
field_size, \
field_alignment, \
buffer_size) < 0) { \
return -1; \
} \
} while (0)
#define PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD(section, field, field_size, field_alignment, buffer_size) \
do { \
if (validate_fixed_field( \
#section "." #field, \
debug_offsets->section.field, \
field_size, \
field_alignment, \
buffer_size) < 0) { \
return -1; \
} \
} while (0)
static inline int
validate_debug_offsets_layout(struct _Py_DebugOffsets *debug_offsets)
{
PY_REMOTE_DEBUG_VALIDATE_SECTION(runtime_state);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
runtime_state,
interpreters_head,
sizeof(uintptr_t),
_Alignof(uintptr_t),
sizeof(_PyRuntimeState));
PY_REMOTE_DEBUG_VALIDATE_SECTION(interpreter_state);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_state,
threads_head,
sizeof(uintptr_t),
_Alignof(uintptr_t),
INTERP_STATE_BUFFER_SIZE);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_state,
threads_main,
sizeof(uintptr_t),
_Alignof(uintptr_t),
INTERP_STATE_BUFFER_SIZE);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_state,
gil_runtime_state_locked,
sizeof(int),
_Alignof(int),
INTERP_STATE_BUFFER_SIZE);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_state,
gil_runtime_state_holder,
sizeof(PyThreadState *),
_Alignof(PyThreadState *),
INTERP_STATE_BUFFER_SIZE);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_state,
code_object_generation,
sizeof(uint64_t),
_Alignof(uint64_t),
INTERP_STATE_BUFFER_SIZE);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_state,
tlbc_generation,
sizeof(uint32_t),
_Alignof(uint32_t),
INTERP_STATE_BUFFER_SIZE);
PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(thread_state, SIZEOF_THREAD_STATE);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
thread_state,
next,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_THREAD_STATE);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
thread_state,
current_frame,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_THREAD_STATE);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
thread_state,
native_thread_id,
sizeof(unsigned long),
_Alignof(unsigned long),
SIZEOF_THREAD_STATE);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
thread_state,
datastack_chunk,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_THREAD_STATE);
PY_REMOTE_DEBUG_VALIDATE_SECTION(interpreter_frame);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_frame,
previous,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_INTERP_FRAME);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_frame,
executable,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_INTERP_FRAME);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_frame,
instr_ptr,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_INTERP_FRAME);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_frame,
owner,
sizeof(char),
_Alignof(char),
SIZEOF_INTERP_FRAME);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_frame,
stackpointer,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_INTERP_FRAME);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
interpreter_frame,
tlbc_index,
sizeof(int32_t),
_Alignof(int32_t),
SIZEOF_INTERP_FRAME);
PY_REMOTE_DEBUG_VALIDATE_SECTION(code_object);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
code_object,
qualname,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_CODE_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
code_object,
filename,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_CODE_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
code_object,
linetable,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_CODE_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
code_object,
firstlineno,
sizeof(int),
_Alignof(int),
SIZEOF_CODE_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
code_object,
co_code_adaptive,
sizeof(char),
_Alignof(char),
SIZEOF_CODE_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
code_object,
co_tlbc,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_CODE_OBJ);
PY_REMOTE_DEBUG_VALIDATE_SECTION(pyobject);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
pyobject,
ob_type,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_PYOBJECT);
PY_REMOTE_DEBUG_VALIDATE_SECTION(type_object);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
type_object,
tp_flags,
sizeof(unsigned long),
_Alignof(unsigned long),
SIZEOF_TYPE_OBJ);
PY_REMOTE_DEBUG_VALIDATE_SECTION(set_object);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
set_object,
used,
sizeof(Py_ssize_t),
_Alignof(Py_ssize_t),
SIZEOF_SET_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
set_object,
mask,
sizeof(Py_ssize_t),
_Alignof(Py_ssize_t),
SIZEOF_SET_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
set_object,
table,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_SET_OBJ);
PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(long_object, SIZEOF_LONG_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
long_object,
lv_tag,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_LONG_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
long_object,
ob_digit,
sizeof(digit),
_Alignof(digit),
SIZEOF_LONG_OBJ);
PY_REMOTE_DEBUG_VALIDATE_SECTION(bytes_object);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
bytes_object,
ob_size,
sizeof(Py_ssize_t),
_Alignof(Py_ssize_t),
SIZEOF_BYTES_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
bytes_object,
ob_sval,
sizeof(char),
_Alignof(char),
SIZEOF_BYTES_OBJ);
PY_REMOTE_DEBUG_VALIDATE_SECTION(unicode_object);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
unicode_object,
length,
sizeof(Py_ssize_t),
_Alignof(Py_ssize_t),
SIZEOF_UNICODE_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
unicode_object,
asciiobject_size,
sizeof(char),
_Alignof(char),
SIZEOF_UNICODE_OBJ);
PY_REMOTE_DEBUG_VALIDATE_SECTION(gen_object);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
gen_object,
gi_frame_state,
sizeof(int8_t),
_Alignof(int8_t),
SIZEOF_GEN_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
gen_object,
gi_iframe,
FIELD_SIZE(PyGenObject, gi_iframe),
_Alignof(_PyInterpreterFrame),
SIZEOF_GEN_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD(
llist_node,
next,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_LLIST_NODE);
return 0;
}
static inline int
validate_async_debug_offsets_layout(struct _Py_AsyncioModuleDebugOffsets *debug_offsets)
{
PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(asyncio_task_object, SIZEOF_TASK_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_task_object,
task_name,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_TASK_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_task_object,
task_awaited_by,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_TASK_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_task_object,
task_is_task,
sizeof(char),
_Alignof(char),
SIZEOF_TASK_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_task_object,
task_awaited_by_is_set,
sizeof(char),
_Alignof(char),
SIZEOF_TASK_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_task_object,
task_coro,
sizeof(uintptr_t),
_Alignof(uintptr_t),
SIZEOF_TASK_OBJ);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_task_object,
task_node,
SIZEOF_LLIST_NODE,
_Alignof(struct llist_node),
SIZEOF_TASK_OBJ);
PY_REMOTE_DEBUG_VALIDATE_SECTION(asyncio_interpreter_state);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_interpreter_state,
asyncio_tasks_head,
SIZEOF_LLIST_NODE,
_Alignof(struct llist_node),
sizeof(PyInterpreterState));
PY_REMOTE_DEBUG_VALIDATE_SECTION(asyncio_thread_state);
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_thread_state,
asyncio_running_loop,
sizeof(uintptr_t),
_Alignof(uintptr_t),
sizeof(_PyThreadStateImpl));
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_thread_state,
asyncio_running_task,
sizeof(uintptr_t),
_Alignof(uintptr_t),
sizeof(_PyThreadStateImpl));
PY_REMOTE_DEBUG_VALIDATE_FIELD(
asyncio_thread_state,
asyncio_tasks_head,
SIZEOF_LLIST_NODE,
_Alignof(struct llist_node),
sizeof(_PyThreadStateImpl));
return 0;
}
#undef PY_REMOTE_DEBUG_VALIDATE_SECTION
#undef PY_REMOTE_DEBUG_VALIDATE_READ_SECTION
#undef PY_REMOTE_DEBUG_VALIDATE_FIELD
#undef PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD
#undef FIELD_SIZE
/* ============================================================================
* STRUCTSEQ TYPE DEFINITIONS
* ============================================================================ */
// TaskInfo structseq type - replaces 4-tuple (task_id, task_name, coroutine_stack, awaited_by)
static PyStructSequence_Field TaskInfo_fields[] = {
{"task_id", "Task ID (memory address)"},
{"task_name", "Task name"},
{"coroutine_stack", "Coroutine call stack"},
{"awaited_by", "Tasks awaiting this task"},
{NULL}
};
static PyStructSequence_Desc TaskInfo_desc = {
"_remote_debugging.TaskInfo",
"Information about an asyncio task",
TaskInfo_fields,
4
};
// FrameInfo structseq type - replaces 3-tuple (filename, lineno, funcname)
static PyStructSequence_Field FrameInfo_fields[] = {
{"filename", "Source code filename"},
{"lineno", "Line number"},
{"funcname", "Function name"},
{NULL}
};
static PyStructSequence_Desc FrameInfo_desc = {
"_remote_debugging.FrameInfo",
"Information about a frame",
FrameInfo_fields,
3
};
// CoroInfo structseq type - replaces 2-tuple (call_stack, task_name)
static PyStructSequence_Field CoroInfo_fields[] = {
{"call_stack", "Coroutine call stack"},
{"task_name", "Task name"},
{NULL}
};
static PyStructSequence_Desc CoroInfo_desc = {
"_remote_debugging.CoroInfo",
"Information about a coroutine",
CoroInfo_fields,
2
};
// ThreadInfo structseq type - replaces 2-tuple (thread_id, frame_info)
static PyStructSequence_Field ThreadInfo_fields[] = {
{"thread_id", "Thread ID"},
{"frame_info", "Frame information"},
{NULL}
};
static PyStructSequence_Desc ThreadInfo_desc = {
"_remote_debugging.ThreadInfo",
"Information about a thread",
ThreadInfo_fields,
2
};
// AwaitedInfo structseq type - replaces 2-tuple (tid, awaited_by_list)
static PyStructSequence_Field AwaitedInfo_fields[] = {
{"thread_id", "Thread ID"},
{"awaited_by", "List of tasks awaited by this thread"},
{NULL}
};
static PyStructSequence_Desc AwaitedInfo_desc = {
"_remote_debugging.AwaitedInfo",
"Information about what a thread is awaiting",
AwaitedInfo_fields,
2
};
typedef struct {
PyObject *func_name;
PyObject *file_name;
int first_lineno;
PyObject *linetable; // bytes
uintptr_t addr_code_adaptive;
} CachedCodeMetadata;
typedef struct {
/* Types */
PyTypeObject *RemoteDebugging_Type;
PyTypeObject *TaskInfo_Type;
PyTypeObject *FrameInfo_Type;
PyTypeObject *CoroInfo_Type;
PyTypeObject *ThreadInfo_Type;
PyTypeObject *AwaitedInfo_Type;
} RemoteDebuggingState;
typedef struct {
PyObject_HEAD
proc_handle_t handle;
uintptr_t runtime_start_address;
struct _Py_DebugOffsets debug_offsets;
int async_debug_offsets_available;
struct _Py_AsyncioModuleDebugOffsets async_debug_offsets;
uintptr_t interpreter_addr;
uintptr_t tstate_addr;
uint64_t code_object_generation;
_Py_hashtable_t *code_object_cache;
int debug;
int only_active_thread;
RemoteDebuggingState *cached_state; // Cached module state
#ifdef Py_GIL_DISABLED
// TLBC cache invalidation tracking
uint32_t tlbc_generation; // Track TLBC index pool changes
_Py_hashtable_t *tlbc_cache; // Cache of TLBC arrays by code object address
#endif
} RemoteUnwinderObject;
#define RemoteUnwinder_CAST(op) ((RemoteUnwinderObject *)(op))
typedef struct
{
int lineno;
int end_lineno;
int column;
int end_column;
} LocationInfo;
typedef struct {
uintptr_t remote_addr;
size_t size;
void *local_copy;
} StackChunkInfo;
typedef struct {
StackChunkInfo *chunks;
size_t count;
} StackChunkList;
#include "clinic/_remote_debugging_module.c.h"
/*[clinic input]
module _remote_debugging
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=5f507d5b2e76a7f7]*/
/* ============================================================================
* FORWARD DECLARATIONS
* ============================================================================ */
static inline int
is_frame_valid(
RemoteUnwinderObject *unwinder,
uintptr_t frame_addr,
uintptr_t code_object_addr
);
typedef int (*thread_processor_func)(
RemoteUnwinderObject *unwinder,
uintptr_t thread_state_addr,
unsigned long tid,
void *context
);
static int
parse_task(
RemoteUnwinderObject *unwinder,
uintptr_t task_address,
PyObject *render_to
);
static int
parse_coro_chain(
RemoteUnwinderObject *unwinder,
uintptr_t coro_address,
PyObject *render_to
);
/* Forward declarations for task parsing functions */
static int parse_frame_object(
RemoteUnwinderObject *unwinder,
PyObject** result,
uintptr_t address,
uintptr_t* address_of_code_object,
uintptr_t* previous_frame
);
static int
parse_async_frame_chain(
RemoteUnwinderObject *unwinder,
PyObject *calls,
uintptr_t address_of_thread,
uintptr_t running_task_code_obj
);
static int read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr);
static int process_task_waiters(RemoteUnwinderObject *unwinder, PyObject *result);
static int parse_task_awaited_by(RemoteUnwinderObject *unwinder, uintptr_t task_address, PyObject *awaited_by);
static int find_running_task_in_thread(RemoteUnwinderObject *unwinder, uintptr_t thread_state_addr, uintptr_t *running_task_addr);
static int get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *code_obj_addr);
static int append_awaited_by(RemoteUnwinderObject *unwinder, unsigned long tid, uintptr_t head_addr, PyObject *result);
/* ============================================================================
* UTILITY FUNCTIONS AND HELPERS
* ============================================================================ */
#define set_exception_cause(unwinder, exc_type, message) \
do { \
assert(PyErr_Occurred() && "function returned -1 without setting exception"); \
if (unwinder->debug && !_Py_RemoteDebug_IsFatalReadError()) { \
_set_debug_exception_cause(exc_type, message); \
} \
} while (0)
static void
cached_code_metadata_destroy(void *ptr)
{
CachedCodeMetadata *meta = (CachedCodeMetadata *)ptr;
Py_DECREF(meta->func_name);
Py_DECREF(meta->file_name);
Py_DECREF(meta->linetable);
PyMem_RawFree(meta);
}
static inline RemoteDebuggingState *
RemoteDebugging_GetState(PyObject *module)
{
void *state = _PyModule_GetState(module);
assert(state != NULL);
return (RemoteDebuggingState *)state;
}
static inline RemoteDebuggingState *
RemoteDebugging_GetStateFromType(PyTypeObject *type)
{
PyObject *module = PyType_GetModule(type);
assert(module != NULL);
return RemoteDebugging_GetState(module);
}
static inline RemoteDebuggingState *
RemoteDebugging_GetStateFromObject(PyObject *obj)
{
RemoteUnwinderObject *unwinder = (RemoteUnwinderObject *)obj;
if (unwinder->cached_state == NULL) {
unwinder->cached_state = RemoteDebugging_GetStateFromType(Py_TYPE(obj));
}
return unwinder->cached_state;
}
static inline int
RemoteDebugging_InitState(RemoteDebuggingState *st)
{
return 0;
}
static int
is_prerelease_version(uint64_t version)
{
return (version & 0xF0) != 0xF0;
}
static inline int
validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets)
{
if (memcmp(debug_offsets->cookie, _Py_Debug_Cookie, sizeof(debug_offsets->cookie)) != 0) {
// The remote is probably running a Python version predating debug offsets.
PyErr_SetString(
PyExc_RuntimeError,
"Can't determine the Python version of the remote process");
return -1;
}
// Assume debug offsets could change from one pre-release version to another,
// or one minor version to another, but are stable across patch versions.
if (is_prerelease_version(Py_Version) && Py_Version != debug_offsets->version) {
PyErr_SetString(
PyExc_RuntimeError,
"Can't attach from a pre-release Python interpreter"
" to a process running a different Python version");
return -1;
}
if (is_prerelease_version(debug_offsets->version) && Py_Version != debug_offsets->version) {
PyErr_SetString(
PyExc_RuntimeError,
"Can't attach to a pre-release Python interpreter"
" from a process running a different Python version");
return -1;
}
unsigned int remote_major = (debug_offsets->version >> 24) & 0xFF;
unsigned int remote_minor = (debug_offsets->version >> 16) & 0xFF;
if (PY_MAJOR_VERSION != remote_major || PY_MINOR_VERSION != remote_minor) {
PyErr_Format(
PyExc_RuntimeError,
"Can't attach from a Python %d.%d process to a Python %d.%d process",
PY_MAJOR_VERSION, PY_MINOR_VERSION, remote_major, remote_minor);
return -1;
}
// The debug offsets differ between free threaded and non-free threaded builds.
if (_Py_Debug_Free_Threaded && !debug_offsets->free_threaded) {
PyErr_SetString(
PyExc_RuntimeError,
"Cannot attach from a free-threaded Python process"
" to a process running a non-free-threaded version");
return -1;
}
if (!_Py_Debug_Free_Threaded && debug_offsets->free_threaded) {
PyErr_SetString(
PyExc_RuntimeError,
"Cannot attach to a free-threaded Python process"
" from a process running a non-free-threaded version");
return -1;
}
return validate_debug_offsets_layout(debug_offsets);
}
// Generic function to iterate through all threads
static int
iterate_threads(
RemoteUnwinderObject *unwinder,
thread_processor_func processor,
void *context
) {
uintptr_t thread_state_addr;
unsigned long tid = 0;
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
unwinder->interpreter_addr + (uintptr_t)unwinder->debug_offsets.interpreter_state.threads_main,
sizeof(void*),
&thread_state_addr))
{
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read main thread state");
return -1;
}
while (thread_state_addr != 0) {
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
thread_state_addr + (uintptr_t)unwinder->debug_offsets.thread_state.native_thread_id,
sizeof(tid),
&tid))
{
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread ID");
return -1;
}
// Call the processor function for this thread
if (processor(unwinder, thread_state_addr, tid, context) < 0) {
return -1;
}
// Move to next thread
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
thread_state_addr + (uintptr_t)unwinder->debug_offsets.thread_state.next,
sizeof(void*),
&thread_state_addr))
{
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read next thread state");
return -1;
}
}
return 0;
}
// Generic function to iterate through set entries
static int
iterate_set_entries(
RemoteUnwinderObject *unwinder,
uintptr_t set_addr,
PyObject *awaited_by
) {
char set_object[SIZEOF_SET_OBJ];
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, set_addr,
SIZEOF_SET_OBJ, set_object) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set object");
return -1;
}
Py_ssize_t num_els = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.used);
Py_ssize_t mask = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.mask);
uintptr_t table_ptr = GET_MEMBER(uintptr_t, set_object, unwinder->debug_offsets.set_object.table);
// 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;
}
Py_ssize_t set_len = mask + 1;
Py_ssize_t i = 0;
Py_ssize_t els = 0;
while (i < set_len && els < num_els) {
setentry entry;
if (_Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle, table_ptr, sizeof(entry), &entry) < 0)
{
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry");
return -1;
}
uintptr_t key_addr = (uintptr_t)entry.key;
if (key_addr != 0 && entry.hash != -1) {
if (parse_task(unwinder, key_addr, awaited_by) < 0) {
return -1;
}
els++;
}
table_ptr += sizeof(entry);
i++;
}
return 0;
}
/* ============================================================================
* MEMORY READING FUNCTIONS
* ============================================================================ */
#define DEFINE_MEMORY_READER(type_name, c_type, error_msg) \
static inline int \
read_##type_name(RemoteUnwinderObject *unwinder, uintptr_t address, c_type *result) \
{ \
int res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(c_type), result); \
if (res < 0) { \
set_exception_cause(unwinder, PyExc_RuntimeError, error_msg); \
return -1; \
} \
return 0; \
}
DEFINE_MEMORY_READER(ptr, uintptr_t, "Failed to read pointer from remote memory")
DEFINE_MEMORY_READER(char, char, "Failed to read char from remote memory")
static int
read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr)
{
if (read_ptr(unwinder, address, ptr_addr)) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read Python pointer");
return -1;
}
*ptr_addr &= ~Py_TAG_BITS;
return 0;
}
/* ============================================================================
* PYTHON OBJECT READING FUNCTIONS
* ============================================================================ */
static PyObject *
read_py_str(
RemoteUnwinderObject *unwinder,
uintptr_t address,
Py_ssize_t max_len
) {
PyObject *result = NULL;
char *buf = NULL;
// Read the entire PyUnicodeObject at once
char unicode_obj[SIZEOF_UNICODE_OBJ];
int res = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
address,
SIZEOF_UNICODE_OBJ,
unicode_obj
);
if (res < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read PyUnicodeObject");
goto err;
}
Py_ssize_t len = GET_MEMBER(Py_ssize_t, unicode_obj, unwinder->debug_offsets.unicode_object.length);
if (len < 0) {
PyErr_Format(PyExc_RuntimeError,
"Invalid string length (%zd) at 0x%lx", len, address);
set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid string length in remote Unicode object");
return NULL;
}
if (len > max_len) {
// gh-157788: a long name must not fail the whole read
len = max_len;
}
buf = (char *)PyMem_RawMalloc(len+1);
if (buf == NULL) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate buffer for string reading");
return NULL;
}
size_t offset = (size_t)unwinder->debug_offsets.unicode_object.asciiobject_size;
res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address + offset, len, buf);
if (res < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read string data from remote memory");
goto err;
}
buf[len] = '\0';
result = PyUnicode_FromStringAndSize(buf, len);
if (result == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create PyUnicode from remote string data");
goto err;
}
PyMem_RawFree(buf);
assert(result != NULL);
return result;
err:
if (buf != NULL) {
PyMem_RawFree(buf);
}
return NULL;
}
static PyObject *
read_py_bytes(
RemoteUnwinderObject *unwinder,
uintptr_t address,
Py_ssize_t max_len
) {
PyObject *result = NULL;
char *buf = NULL;
// Read the entire PyBytesObject at once
char bytes_obj[SIZEOF_BYTES_OBJ];
int res = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
address,
SIZEOF_BYTES_OBJ,
bytes_obj
);
if (res < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read PyBytesObject");
goto err;
}
Py_ssize_t len = GET_MEMBER(Py_ssize_t, bytes_obj, unwinder->debug_offsets.bytes_object.ob_size);
if (len < 0 || len > max_len) {
PyErr_Format(PyExc_RuntimeError,
"Invalid bytes length (%zd) at 0x%lx", len, address);
set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid bytes length in remote bytes object");
return NULL;
}
buf = (char *)PyMem_RawMalloc(len+1);
if (buf == NULL) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate buffer for bytes reading");
return NULL;
}
size_t offset = (size_t)unwinder->debug_offsets.bytes_object.ob_sval;
res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address + offset, len, buf);
if (res < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read bytes data from remote memory");
goto err;
}
buf[len] = '\0';
result = PyBytes_FromStringAndSize(buf, len);
if (result == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create PyBytes from remote bytes data");
goto err;
}
PyMem_RawFree(buf);
assert(result != NULL);
return result;
err:
if (buf != NULL) {
PyMem_RawFree(buf);
}
return NULL;
}
static long
read_py_long(
RemoteUnwinderObject *unwinder,
uintptr_t address
)
{
unsigned int shift = PYLONG_BITS_IN_DIGIT;
// Read the entire PyLongObject at once
char long_obj[SIZEOF_LONG_OBJ];
int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
address,
(size_t)unwinder->debug_offsets.long_object.size,
long_obj);
if (bytes_read < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read PyLongObject");
return -1;
}
uintptr_t lv_tag = GET_MEMBER(uintptr_t, long_obj, unwinder->debug_offsets.long_object.lv_tag);
int negative = (lv_tag & 3) == 2;
Py_ssize_t size = lv_tag >> 3;
if (size == 0) {
return 0;
}
if (size < 0 || size > MAX_LONG_DIGITS) {
PyErr_Format(PyExc_RuntimeError,
"Invalid PyLong digit count: %zd (expected 0-%d)",
size, MAX_LONG_DIGITS);
set_exception_cause(unwinder, PyExc_RuntimeError,
"Invalid PyLong size (corrupted remote memory)");
return -1;
}
// Calculate how many digits fit inline in our local buffer
Py_ssize_t ob_digit_offset = unwinder->debug_offsets.long_object.ob_digit;
Py_ssize_t inline_digits_space = SIZEOF_LONG_OBJ - ob_digit_offset;
Py_ssize_t max_inline_digits = inline_digits_space / (Py_ssize_t)sizeof(digit);
digit *digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
if (!digits) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate digits for PyLong");
return -1;
}
if (size <= max_inline_digits && size <= _PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS) {
memcpy(digits, long_obj + ob_digit_offset, size * sizeof(digit));
} else {
bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
address + (uintptr_t)unwinder->debug_offsets.long_object.ob_digit,
sizeof(digit) * size,
digits
);
if (bytes_read < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read PyLong digits from remote memory");
goto error;
}
}
unsigned long limit = negative
? (unsigned long)LONG_MAX + 1UL
: (unsigned long)LONG_MAX;
unsigned long value = 0;
for (Py_ssize_t i = size; i-- > 0;) {
if (digits[i] >= PyLong_BASE) {
PyErr_Format(PyExc_RuntimeError,
"Invalid PyLong digit: %u (base %u)", digits[i], PyLong_BASE);
set_exception_cause(unwinder, PyExc_RuntimeError,
"Invalid PyLong digit (corrupted remote memory)");
goto error;
}
if (value > ((limit - (unsigned long)digits[i]) >> shift)) {
PyErr_SetString(PyExc_OverflowError,
"Remote PyLong value does not fit in C long");
set_exception_cause(unwinder, PyExc_OverflowError,
"Remote PyLong value is too large");
goto error;
}
value = (value << shift) | (unsigned long)digits[i];
}
PyMem_RawFree(digits);
if (negative) {
if (value == (unsigned long)LONG_MAX + 1UL) {
return LONG_MIN;
}
return -(long)value;
}
return (long)value;
error:
PyMem_RawFree(digits);
return -1;
}
/* ============================================================================
* ASYNCIO DEBUG FUNCTIONS
* ============================================================================ */
// Get the PyAsyncioDebug section address for any platform
static uintptr_t
_Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle)
{
uintptr_t address;
#ifdef MS_WINDOWS
// On Windows, search for asyncio debug in executable or DLL
address = search_windows_map_for_section(handle, "AsyncioD", L"_asyncio", NULL);
if (address == 0) {
if (!_Py_RemoteDebug_HasPermissionError()) {
PyObject *exc = PyErr_GetRaisedException();
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
_PyErr_ChainExceptions1(exc);
}
}
#elif defined(__linux__)
// On Linux, search for asyncio debug in executable or DLL
address = search_linux_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython", NULL);
if (address == 0) {
if (!_Py_RemoteDebug_HasPermissionError()) {
PyObject *exc = PyErr_GetRaisedException();
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
_PyErr_ChainExceptions1(exc);
}
}
#elif defined(__APPLE__) && TARGET_OS_OSX
// On macOS, try libpython first, then fall back to python
address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython", NULL);
if (address == 0 && !_Py_RemoteDebug_HasPermissionError()) {
PyErr_Clear();
address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython", NULL);
}
if (address == 0) {
if (!_Py_RemoteDebug_HasPermissionError()) {
PyObject *exc = PyErr_GetRaisedException();
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
_PyErr_ChainExceptions1(exc);
}
}
#else
Py_UNREACHABLE();
#endif
return address;
}
static int
read_async_debug(
RemoteUnwinderObject *unwinder
) {
uintptr_t async_debug_addr = _Py_RemoteDebug_GetAsyncioDebugAddress(&unwinder->handle);
if (!async_debug_addr) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to get AsyncioDebug address");
return -1;
}
size_t size = sizeof(struct _Py_AsyncioModuleDebugOffsets);
int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, async_debug_addr, size, &unwinder->async_debug_offsets);
if (result < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read AsyncioDebug offsets");
return result;
}
if (validate_async_debug_offsets_layout(&unwinder->async_debug_offsets) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid AsyncioDebug offsets");
return PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS;
}
return 0;
}
/* ============================================================================
* ASYNCIO TASK PARSING FUNCTIONS
* ============================================================================ */
static PyObject *
parse_task_name(
RemoteUnwinderObject *unwinder,
uintptr_t task_address
) {
// Read the entire TaskObj at once
char task_obj[SIZEOF_TASK_OBJ];
int err = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
task_address,
(size_t)unwinder->async_debug_offsets.asyncio_task_object.size,
task_obj);
if (err < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object");
return NULL;
}
uintptr_t task_name_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_name);
// The task name can be a long or a string so we need to check the type
char task_name_obj[SIZEOF_PYOBJECT];
err = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
task_name_addr,
SIZEOF_PYOBJECT,
task_name_obj);
if (err < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task name object");
return NULL;
}
// Now read the type object to get the flags
char type_obj[SIZEOF_TYPE_OBJ];
err = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
GET_MEMBER(uintptr_t, task_name_obj, unwinder->debug_offsets.pyobject.ob_type),
SIZEOF_TYPE_OBJ,
type_obj);
if (err < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task name type object");
return NULL;
}
if ((GET_MEMBER(unsigned long, type_obj, unwinder->debug_offsets.type_object.tp_flags) & Py_TPFLAGS_LONG_SUBCLASS)) {
long res = read_py_long(unwinder, task_name_addr);
if (res == -1 && PyErr_Occurred()) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Task name PyLong parsing failed");
return NULL;
}
return PyUnicode_FromFormat("Task-%ld", res);
}
if(!(GET_MEMBER(unsigned long, type_obj, unwinder->debug_offsets.type_object.tp_flags) & Py_TPFLAGS_UNICODE_SUBCLASS)) {
PyErr_SetString(PyExc_RuntimeError, "Invalid task name object");
set_exception_cause(unwinder, PyExc_RuntimeError, "Task name object is neither long nor unicode");
return NULL;
}
return read_py_str(
unwinder,
task_name_addr,
255
);
}
// gh-159620
/* Return in *coro_addr the coroutine or async generator to walk for the
awaitable at addr, or 0 if it is neither (for example, a future). */
static int
get_coro_like_address(
RemoteUnwinderObject *unwinder,
uintptr_t addr,
uintptr_t *coro_addr
) {
*coro_addr = 0;
// Skip user classes that share a name with the generator types below
uintptr_t type_addr;
unsigned long tp_flags;
if (read_ptr(unwinder, addr + (uintptr_t)unwinder->debug_offsets.pyobject.ob_type,
&type_addr) < 0
|| _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle,
type_addr + (uintptr_t)unwinder->debug_offsets.type_object.tp_flags,
sizeof(tp_flags), &tp_flags) < 0)
{
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read awaitable type");
return -1;
}
if (tp_flags & Py_TPFLAGS_HEAPTYPE) {
return 0;
}
uintptr_t name_addr;
char name[32] = {0};
if (read_ptr(unwinder, type_addr + (uintptr_t)unwinder->debug_offsets.type_object.tp_name,
&name_addr) < 0
|| _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, name_addr,
sizeof(name) - 1, name) < 0)
{
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read awaitable type name");
return -1;
}
if (strcmp(name, "coroutine") == 0) {
*coro_addr = addr;
}
else if (strcmp(name, "async_generator_asend") == 0
|| strcmp(name, "async_generator_athrow") == 0)
{
// asend and athrow are separate wrapper objects: read the pointer
// to their parent async generator, stored right after PyObject_HEAD
return read_ptr(unwinder, addr + (uintptr_t)unwinder->debug_offsets.pyobject.size,
coro_addr);
}
return 0;
}
static int
get_awaited_coro_address(
RemoteUnwinderObject *unwinder,
uintptr_t gi_iframe_addr,
uintptr_t *next_coro
) {
*next_coro = 0;
// Read the entire interpreter frame at once
char iframe[SIZEOF_INTERP_FRAME];
int err = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
gi_iframe_addr,
SIZEOF_INTERP_FRAME,
iframe);
if (err < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter frame in yield_from handler");
return -1;
}
if (GET_MEMBER(char, iframe, unwinder->debug_offsets.interpreter_frame.owner) != FRAME_OWNED_BY_GENERATOR) {
PyErr_SetString(
PyExc_RuntimeError,
"generator doesn't own its frame \\_o_/");
set_exception_cause(unwinder, PyExc_RuntimeError, "Frame ownership mismatch in yield_from");
return -1;
}
uintptr_t stackpointer_addr = GET_MEMBER_NO_TAG(uintptr_t, iframe, unwinder->debug_offsets.interpreter_frame.stackpointer);
if ((void*)stackpointer_addr != NULL) {
err = read_py_ptr(
unwinder,
stackpointer_addr - sizeof(void*),
next_coro);
if (err) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read gi_await address");
return -1;
}
}
return 0;
}
static int
parse_coro_chain(
RemoteUnwinderObject *unwinder,
uintptr_t coro_address,
PyObject *render_to
) {
assert((void*)coro_address != NULL);
for (size_t depth = 0; (void*)coro_address != NULL; depth++) {
if (depth >= MAX_FRAME_CHAIN_DEPTH) {
PyErr_SetString(PyExc_RuntimeError,
"Too many coroutine frames (possible infinite loop)");
set_exception_cause(unwinder, PyExc_RuntimeError,
"Coroutine chain depth limit exceeded");
return -1;
}
if (get_coro_like_address(unwinder, coro_address, &coro_address) < 0) {
return -1;
}
if ((void*)coro_address == NULL) {
return 0;
}
// Read the entire generator object at once
char gen_object[SIZEOF_GEN_OBJ];
int err = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
coro_address,
SIZEOF_GEN_OBJ,
gen_object);
if (err < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read generator object in coro chain");
return -1;
}
int8_t frame_state = GET_MEMBER(int8_t, gen_object, unwinder->debug_offsets.gen_object.gi_frame_state);
if (frame_state == FRAME_CLEARED) {
return 0;
}
PyObject* name = NULL;
// Parse the previous frame using the gi_iframe from local copy
uintptr_t prev_frame;
uintptr_t gi_iframe_addr = coro_address + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe;
uintptr_t address_of_code_object = 0;
if (parse_frame_object(unwinder, &name, gi_iframe_addr, &address_of_code_object, &prev_frame) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in coro chain");
return -1;
}
if (!name) {
return 0;
}
if (PyList_Append(render_to, name)) {
Py_DECREF(name);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to coro chain");
return -1;
}
Py_DECREF(name);
if (frame_state != FRAME_SUSPENDED_YIELD_FROM) {
return 0;
}
if (get_awaited_coro_address(unwinder, gi_iframe_addr, &coro_address) < 0) {
return -1;
}
}
return 0;
}
static PyObject*
create_task_result(
RemoteUnwinderObject *unwinder,
uintptr_t task_address
) {
PyObject* result = NULL;
PyObject *call_stack = NULL;
PyObject *tn = NULL;
char task_obj[SIZEOF_TASK_OBJ];
uintptr_t coro_addr;
// Create call_stack first since it's the first tuple element
call_stack = PyList_New(0);
if (call_stack == NULL) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create call stack list");
goto error;
}
// Create task name/address for second tuple element
tn = PyLong_FromUnsignedLongLong(task_address);
if (tn == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task name/address");
goto error;
}
// Parse coroutine chain
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address,
(size_t)unwinder->async_debug_offsets.asyncio_task_object.size,
task_obj) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object for coro chain");
goto error;
}
coro_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_coro);
if ((void*)coro_addr != NULL) {
if (parse_coro_chain(unwinder, coro_addr, call_stack) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse coroutine chain");
goto error;
}
if (PyList_Reverse(call_stack)) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to reverse call stack");
goto error;
}
}
// Create final CoroInfo result
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
result = PyStructSequence_New(state->CoroInfo_Type);
if (result == NULL) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create CoroInfo");
goto error;
}
// PyStructSequence_SetItem steals references, so we don't need to DECREF on success
PyStructSequence_SetItem(result, 0, call_stack); // This steals the reference
PyStructSequence_SetItem(result, 1, tn); // This steals the reference
return result;
error:
Py_XDECREF(result);
Py_XDECREF(call_stack);
Py_XDECREF(tn);
return NULL;
}
static int
parse_task(
RemoteUnwinderObject *unwinder,
uintptr_t task_address,
PyObject *render_to
) {
char is_task;
PyObject* result = NULL;
int err;
err = read_char(
unwinder,
task_address + (uintptr_t)unwinder->async_debug_offsets.asyncio_task_object.task_is_task,
&is_task);
if (err) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read is_task flag");
goto error;
}
if (is_task) {
result = create_task_result(unwinder, task_address);
if (!result) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task result");
goto error;
}
} else {
// Create an empty CoroInfo for non-task objects
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
result = PyStructSequence_New(state->CoroInfo_Type);
if (result == NULL) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create empty CoroInfo");
goto error;
}
PyObject *empty_list = PyList_New(0);
if (empty_list == NULL) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create empty list");
goto error;
}
PyObject *task_name = PyLong_FromUnsignedLongLong(task_address);
if (task_name == NULL) {
Py_DECREF(empty_list);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task name");
goto error;
}
PyStructSequence_SetItem(result, 0, empty_list); // This steals the reference
PyStructSequence_SetItem(result, 1, task_name); // This steals the reference
}
if (PyList_Append(render_to, result)) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append task result to render list");
goto error;
}
Py_DECREF(result);
return 0;
error:
Py_XDECREF(result);
return -1;
}
static int
process_single_task_node(
RemoteUnwinderObject *unwinder,
uintptr_t task_addr,
PyObject **task_info,
PyObject *result
) {
PyObject *tn = NULL;
PyObject *current_awaited_by = NULL;
PyObject *task_id = NULL;
PyObject *result_item = NULL;
PyObject *coroutine_stack = NULL;
tn = parse_task_name(unwinder, task_addr);
if (tn == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task name in single task node");
goto error;
}
current_awaited_by = PyList_New(0);
if (current_awaited_by == NULL) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create awaited_by list in single task node");
goto error;
}
// Extract the coroutine stack for this task
coroutine_stack = PyList_New(0);
if (coroutine_stack == NULL) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create coroutine stack list in single task node");
goto error;
}
if (parse_task(unwinder, task_addr, coroutine_stack) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task coroutine stack in single task node");
goto error;
}
task_id = PyLong_FromUnsignedLongLong(task_addr);
if (task_id == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task ID in single task node");
goto error;
}
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
result_item = PyStructSequence_New(state->TaskInfo_Type);
if (result_item == NULL) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create TaskInfo in single task node");
goto error;
}
PyStructSequence_SetItem(result_item, 0, task_id); // steals ref
PyStructSequence_SetItem(result_item, 1, tn); // steals ref
PyStructSequence_SetItem(result_item, 2, coroutine_stack); // steals ref
PyStructSequence_SetItem(result_item, 3, current_awaited_by); // steals ref
// References transferred to tuple
task_id = NULL;
tn = NULL;
coroutine_stack = NULL;
current_awaited_by = NULL;
if (PyList_Append(result, result_item)) {
Py_DECREF(result_item);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append result item in single task node");
return -1;
}
if (task_info != NULL) {
*task_info = result_item;
}
Py_DECREF(result_item);
// Get back current_awaited_by reference for parse_task_awaited_by
current_awaited_by = PyStructSequence_GetItem(result_item, 3);
if (parse_task_awaited_by(unwinder, task_addr, current_awaited_by) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse awaited_by in single task node");
// No cleanup needed here since all references were transferred to result_item
// and result_item was already added to result list and decreffed
return -1;
}
return 0;
error:
Py_XDECREF(tn);
Py_XDECREF(current_awaited_by);
Py_XDECREF(task_id);
Py_XDECREF(result_item);
Py_XDECREF(coroutine_stack);
return -1;
}
// Thread processor for get_all_awaited_by
static int
process_thread_for_awaited_by(
RemoteUnwinderObject *unwinder,
uintptr_t thread_state_addr,
unsigned long tid,
void *context
) {
PyObject *result = (PyObject *)context;
uintptr_t head_addr = thread_state_addr + (uintptr_t)unwinder->async_debug_offsets.asyncio_thread_state.asyncio_tasks_head;
return append_awaited_by(unwinder, tid, head_addr, result);
}
// Generic function to process task awaited_by
static int
parse_task_awaited_by(
RemoteUnwinderObject *unwinder,
uintptr_t task_address,
PyObject *awaited_by
) {
// Read the entire TaskObj at once
char task_obj[SIZEOF_TASK_OBJ];
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address,
(size_t)unwinder->async_debug_offsets.asyncio_task_object.size,
task_obj) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object");
return -1;
}
uintptr_t task_ab_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by);
if ((void*)task_ab_addr == NULL) {
return 0; // No tasks waiting for this one
}
char awaited_by_is_a_set = GET_MEMBER(char, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by_is_set);
if (awaited_by_is_a_set) {
return iterate_set_entries(unwinder, task_ab_addr, awaited_by);
} else {
// Single task waiting
return parse_task(unwinder, task_ab_addr, awaited_by);
}
}
static int
process_running_task_chain(
RemoteUnwinderObject *unwinder,
uintptr_t running_task_addr,
uintptr_t thread_state_addr,
PyObject *result
) {
uintptr_t running_task_code_obj = 0;
if(get_task_code_object(unwinder, running_task_addr, &running_task_code_obj) < 0) {
return -1;
}
// First, add this task to the result
PyObject *task_info = NULL;
if (process_single_task_node(unwinder, running_task_addr, &task_info, result) < 0) {
return -1;
}
// Get the chain from the current frame to this task
PyObject *coro_chain = PyStructSequence_GET_ITEM(task_info, 2);
assert(coro_chain != NULL);
if (PyList_GET_SIZE(coro_chain) != 1) {
PyErr_Format(PyExc_RuntimeError,
"Expected single-item coro chain, got %zd items",
PyList_GET_SIZE(coro_chain));
set_exception_cause(unwinder, PyExc_RuntimeError, "Coro chain is not a single item");
return -1;
}
PyObject *coro_info = PyList_GET_ITEM(coro_chain, 0);
assert(coro_info != NULL);
PyObject *frame_chain = PyStructSequence_GET_ITEM(coro_info, 0);
assert(frame_chain != NULL);
// Clear the coro_chain
if (PyList_Clear(frame_chain) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to clear coroutine chain");
return -1;
}
// Add the chain from the current frame to this task
if (parse_async_frame_chain(unwinder, frame_chain, thread_state_addr, running_task_code_obj) < 0) {
return -1;
}
// Now find all tasks that are waiting for this task and process them
if (process_task_waiters(unwinder, result) < 0) {
return -1;
}
return 0;
}
// Thread processor for get_async_stack_trace
static int
process_thread_for_async_stack_trace(
RemoteUnwinderObject *unwinder,
uintptr_t thread_state_addr,
unsigned long tid,
void *context
) {
PyObject *result = (PyObject *)context;
// Find running task in this thread
uintptr_t running_task_addr;
if (find_running_task_in_thread(unwinder, thread_state_addr, &running_task_addr) < 0) {
return 0;
}
// If we found a running task, process it and its waiters
if ((void*)running_task_addr != NULL) {
PyObject *task_list = PyList_New(0);
if (task_list == NULL) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create task list for thread");
return -1;
}
if (process_running_task_chain(unwinder, running_task_addr, thread_state_addr, task_list) < 0) {
Py_DECREF(task_list);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process running task chain");
return -1;
}
// Create AwaitedInfo structure for this thread
PyObject *tid_py = PyLong_FromUnsignedLong(tid);
if (tid_py == NULL) {
Py_DECREF(task_list);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID");
return -1;
}
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
PyObject *awaited_info = PyStructSequence_New(state->AwaitedInfo_Type);
if (awaited_info == NULL) {
Py_DECREF(tid_py);
Py_DECREF(task_list);
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create AwaitedInfo");
return -1;
}
PyStructSequence_SetItem(awaited_info, 0, tid_py); // steals ref
PyStructSequence_SetItem(awaited_info, 1, task_list); // steals ref
if (PyList_Append(result, awaited_info)) {
Py_DECREF(awaited_info);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append AwaitedInfo to result");
return -1;
}
Py_DECREF(awaited_info);
}
return 0;
}
static int
process_task_waiters(
RemoteUnwinderObject *unwinder,
PyObject *result
) {
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(result); i++) {
PyObject *task_info = PyList_GET_ITEM(result, i);
PyObject *waiters = PyStructSequence_GET_ITEM(task_info, 3);
for (Py_ssize_t j = 0; j < PyList_GET_SIZE(waiters); j++) {
if (PyList_GET_SIZE(result) >= MAX_TASK_WAITER_WALK_TASKS) {
PyErr_SetString(PyExc_RuntimeError,
"Too many task waiters (possible infinite loop)");
set_exception_cause(unwinder, PyExc_RuntimeError,
"Task waiter walk size limit exceeded");
return -1;
}
PyObject *waiter = PyList_GET_ITEM(waiters, j);
// CoroInfo item 1 holds the waiter task address stored by parse_task().
PyObject *task_id = PyStructSequence_GET_ITEM(waiter, 1);
void *task_ptr = PyLong_AsVoidPtr(task_id);
if (task_ptr == NULL && PyErr_Occurred()) {
set_exception_cause(unwinder, PyExc_RuntimeError,
"Failed to parse waiter task ID");
return -1;
}
if (process_single_task_node(
unwinder, (uintptr_t)task_ptr, NULL, result) < 0)
{
return -1;
}
}
}
return 0;
}
static int
find_running_task_in_thread(
RemoteUnwinderObject *unwinder,
uintptr_t thread_state_addr,
uintptr_t *running_task_addr
) {
*running_task_addr = (uintptr_t)NULL;
uintptr_t address_of_running_loop;
int bytes_read = read_py_ptr(
unwinder,
thread_state_addr + (uintptr_t)unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_loop,
&address_of_running_loop);
if (bytes_read == -1) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running loop address");
return -1;
}
// no asyncio loop is now running
if ((void*)address_of_running_loop == NULL) {
return 0;
}
int err = read_ptr(
unwinder,
thread_state_addr + (uintptr_t)unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_task,
running_task_addr);
if (err) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task address");
return -1;
}
return 0;
}
static int
get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *code_obj_addr) {
uintptr_t running_coro_addr = 0;
if(read_py_ptr(
unwinder,
task_addr + (uintptr_t)unwinder->async_debug_offsets.asyncio_task_object.task_coro,
&running_coro_addr) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro read failed");
return -1;
}
if (running_coro_addr == 0) {
PyErr_SetString(PyExc_RuntimeError, "Running task coro is NULL");
set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro address is NULL");
return -1;
}
if (get_coro_like_address(unwinder, running_coro_addr, &running_coro_addr) < 0) {
return -1;
}
// note: genobject's gi_iframe is an embedded struct so the address to
// the offset leads directly to its first field: f_executable
if (read_py_ptr(
unwinder,
running_coro_addr + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe, code_obj_addr) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task code object");
return -1;
}
if (*code_obj_addr == 0) {
PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL");
set_exception_cause(unwinder, PyExc_RuntimeError, "Running task code object address is NULL");
return -1;
}
return 0;
}
/* ============================================================================
* TLBC CACHING FUNCTIONS
* ============================================================================ */
#ifdef Py_GIL_DISABLED
typedef struct {
void *tlbc_array; // Local copy of the TLBC array
Py_ssize_t tlbc_array_size; // Size of the TLBC array
uint32_t generation; // Generation when this was cached
} TLBCCacheEntry;
static void
tlbc_cache_entry_destroy(void *ptr)
{
TLBCCacheEntry *entry = (TLBCCacheEntry *)ptr;
if (entry->tlbc_array) {
PyMem_RawFree(entry->tlbc_array);
}
PyMem_RawFree(entry);
}
static TLBCCacheEntry *
get_tlbc_cache_entry(RemoteUnwinderObject *self, uintptr_t code_addr, uint32_t current_generation)
{
void *key = (void *)code_addr;
TLBCCacheEntry *entry = _Py_hashtable_get(self->tlbc_cache, key);
if (entry && entry->generation != current_generation) {
// Entry is stale, remove it from the cache and destroy it
TLBCCacheEntry *old = _Py_hashtable_steal(self->tlbc_cache, key);
if (old != NULL) {
tlbc_cache_entry_destroy(old);
}
return NULL;
}
return entry;
}
static int
cache_tlbc_array(RemoteUnwinderObject *unwinder, uintptr_t code_addr, uintptr_t tlbc_array_addr, uint32_t generation)
{
uintptr_t tlbc_array_ptr;
void *tlbc_array = NULL;
TLBCCacheEntry *entry = NULL;
// Read the TLBC array pointer
if (read_ptr(unwinder, tlbc_array_addr, &tlbc_array_ptr) != 0 || tlbc_array_ptr == 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read TLBC array pointer");
return 0; // No TLBC array
}
// Read the TLBC array size
Py_ssize_t tlbc_size;
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, tlbc_array_ptr, sizeof(tlbc_size), &tlbc_size) != 0 || tlbc_size <= 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read TLBC array size");
return 0; // Invalid size
}
// Allocate and read the entire TLBC array
size_t array_data_size = tlbc_size * sizeof(void*);
tlbc_array = PyMem_RawMalloc(sizeof(Py_ssize_t) + array_data_size);
if (!tlbc_array) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate TLBC array");
return 0; // Memory error
}
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, tlbc_array_ptr, sizeof(Py_ssize_t) + array_data_size, tlbc_array) != 0) {
PyMem_RawFree(tlbc_array);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read TLBC array data");
return 0; // Read error
}
// Create cache entry
entry = PyMem_RawMalloc(sizeof(TLBCCacheEntry));
if (!entry) {
PyMem_RawFree(tlbc_array);
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate TLBC cache entry");
return 0; // Memory error
}
entry->tlbc_array = tlbc_array;
entry->tlbc_array_size = tlbc_size;
entry->generation = generation;
// Store in cache
void *key = (void *)code_addr;
if (_Py_hashtable_set(unwinder->tlbc_cache, key, entry) < 0) {
tlbc_cache_entry_destroy(entry);
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to store TLBC entry in cache");
return 0; // Cache error
}
return 1; // Success
}
#endif
/* ============================================================================
* LINE TABLE PARSING FUNCTIONS
* ============================================================================ */
static int
scan_varint(const uint8_t **ptr)
{
unsigned int read = **ptr;
*ptr = *ptr + 1;
unsigned int val = read & 63;
unsigned int shift = 0;
while (read & 64) {
read = **ptr;
*ptr = *ptr + 1;
shift += 6;
val |= (read & 63) << shift;
}
return val;
}
static int
scan_signed_varint(const uint8_t **ptr)
{
unsigned int uval = scan_varint(ptr);
if (uval & 1) {
return -(int)(uval >> 1);
}
else {
return uval >> 1;
}
}
static bool
parse_linetable(const uintptr_t addrq, const char* linetable, int firstlineno, LocationInfo* info)
{
const uint8_t* ptr = (const uint8_t*)(linetable);
uintptr_t addr = 0;
info->lineno = firstlineno;
while (*ptr != '\0') {
// See InternalDocs/code_objects.md for where these magic numbers are from
// and for the decoding algorithm.
uint8_t first_byte = *(ptr++);
uint8_t code = (first_byte >> 3) & 15;
size_t length = (first_byte & 7) + 1;
uintptr_t end_addr = addr + length;
switch (code) {
case PY_CODE_LOCATION_INFO_NONE: {
break;
}
case PY_CODE_LOCATION_INFO_LONG: {
int line_delta = scan_signed_varint(&ptr);
info->lineno += line_delta;
info->end_lineno = info->lineno + scan_varint(&ptr);
info->column = scan_varint(&ptr) - 1;
info->end_column = scan_varint(&ptr) - 1;
break;
}
case PY_CODE_LOCATION_INFO_NO_COLUMNS: {
int line_delta = scan_signed_varint(&ptr);
info->lineno += line_delta;
info->column = info->end_column = -1;
break;
}
case PY_CODE_LOCATION_INFO_ONE_LINE0:
case PY_CODE_LOCATION_INFO_ONE_LINE1:
case PY_CODE_LOCATION_INFO_ONE_LINE2: {
int line_delta = code - 10;
info->lineno += line_delta;
info->end_lineno = info->lineno;
info->column = *(ptr++);
info->end_column = *(ptr++);
break;
}
default: {
uint8_t second_byte = *(ptr++);
if ((second_byte & 128) != 0) {
return false;
}
info->column = code << 3 | (second_byte >> 4);
info->end_column = info->column + (second_byte & 15);
break;
}
}
if (addr <= addrq && end_addr > addrq) {
return true;
}
addr = end_addr;
}
return false;
}
/* ============================================================================
* CODE OBJECT AND FRAME PARSING FUNCTIONS
* ============================================================================ */
static int
parse_code_object(RemoteUnwinderObject *unwinder,
PyObject **result,
uintptr_t address,
uintptr_t instruction_pointer,
uintptr_t *previous_frame,
int32_t tlbc_index)
{
void *key = (void *)address;
CachedCodeMetadata *meta = NULL;
PyObject *func = NULL;
PyObject *file = NULL;
PyObject *linetable = NULL;
PyObject *lineno = NULL;
PyObject *tuple = NULL;
int code_metadata_incomplete = 0;
#ifdef Py_GIL_DISABLED
// In free threading builds, code object addresses might have the low bit set
// as a flag, so we need to mask it off to get the real address
uintptr_t real_address = address & (~1);
#else
uintptr_t real_address = address;
#endif
if (unwinder && unwinder->code_object_cache != NULL) {
meta = _Py_hashtable_get(unwinder->code_object_cache, key);
}
if (meta == NULL) {
char code_object[SIZEOF_CODE_OBJ];
if (_Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle, real_address, SIZEOF_CODE_OBJ, code_object) < 0)
{
if (_Py_RemoteDebug_IsFatalReadError()) {
goto error;
}
PyErr_Clear();
func = PyUnicode_FromString("<unreadable frame>");
if (!func) {
goto error;
}
file = Py_NewRef(_Py_LATIN1_CHR('~'));
goto degraded;
}
func = read_py_str(unwinder,
GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.qualname), 1024);
if (!func) {
if (_Py_RemoteDebug_IsFatalReadError()) {
goto error;
}
PyErr_Clear();
func = PyUnicode_FromString("<unknown function>");
if (!func) {
goto error;
}
code_metadata_incomplete = 1;
}
file = read_py_str(unwinder,
GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.filename), 1024);
if (!file) {
if (_Py_RemoteDebug_IsFatalReadError()) {
goto error;
}
PyErr_Clear();
file = PyUnicode_FromString("<unknown file>");
if (!file) {
goto error;
}
code_metadata_incomplete = 1;
}
if (code_metadata_incomplete) {
goto degraded;
}
linetable = read_py_bytes(unwinder,
GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.linetable),
4096);
if (!linetable) {
if (_Py_RemoteDebug_IsFatalReadError()) {
goto error;
}
PyErr_Clear();
goto degraded;
}
meta = PyMem_RawMalloc(sizeof(CachedCodeMetadata));
if (!meta) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate cached code metadata");
goto error;
}
meta->func_name = func;
meta->file_name = file;
meta->linetable = linetable;
meta->first_lineno = GET_MEMBER(int, code_object, unwinder->debug_offsets.code_object.firstlineno);
meta->addr_code_adaptive = real_address + (uintptr_t)unwinder->debug_offsets.code_object.co_code_adaptive;
if (unwinder && unwinder->code_object_cache && _Py_hashtable_set(unwinder->code_object_cache, key, meta) < 0) {
func = NULL;
file = NULL;
linetable = NULL;
cached_code_metadata_destroy(meta);
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to cache code metadata");
goto error;
}
// Ownership transferred to meta
func = NULL;
file = NULL;
linetable = NULL;
}
uintptr_t ip = instruction_pointer;
ptrdiff_t addrq;
#ifdef Py_GIL_DISABLED
// Handle thread-local bytecode (TLBC) in free threading builds
if (tlbc_index == 0 || unwinder->debug_offsets.code_object.co_tlbc == 0 || unwinder == NULL) {
// No TLBC or no unwinder - use main bytecode directly
addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
goto done_tlbc;
}
// Try to get TLBC data from cache (we'll get generation from the caller)
TLBCCacheEntry *tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
if (!tlbc_entry) {
// Cache miss - try to read and cache TLBC array
if (!cache_tlbc_array(unwinder, real_address, real_address + unwinder->debug_offsets.code_object.co_tlbc, unwinder->tlbc_generation)) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to cache TLBC array");
goto error;
}
tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
}
if (tlbc_entry && tlbc_index >= 0) {
uintptr_t *entries = (uintptr_t *)((char *)tlbc_entry->tlbc_array + sizeof(Py_ssize_t));
if (tlbc_index >= tlbc_entry->tlbc_array_size ||
entries[tlbc_index] == 0) {
TLBCCacheEntry *old = _Py_hashtable_steal(unwinder->tlbc_cache, (void *)real_address);
if (old != NULL) {
tlbc_cache_entry_destroy(old);
}
if (!cache_tlbc_array(unwinder, real_address, real_address + unwinder->debug_offsets.code_object.co_tlbc,
unwinder->tlbc_generation)) {
goto error;
}
tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
}
}
if (tlbc_entry) {
if (tlbc_index < 0 || tlbc_index >= tlbc_entry->tlbc_array_size) {
PyErr_Format(PyExc_RuntimeError,
"Invalid tlbc_index %d (array size %zd, corrupted remote memory)",
tlbc_index, tlbc_entry->tlbc_array_size);
set_exception_cause(unwinder, PyExc_RuntimeError,
"Invalid tlbc_index (corrupted remote memory)");
goto error;
}
// Use cached TLBC data
uintptr_t *entries = (uintptr_t *)((char *)tlbc_entry->tlbc_array + sizeof(Py_ssize_t));
uintptr_t tlbc_bytecode_addr = entries[tlbc_index];
if (tlbc_bytecode_addr != 0) {
// Calculate offset from TLBC bytecode
addrq = (uint16_t *)ip - (uint16_t *)tlbc_bytecode_addr;
goto done_tlbc;
}
}
// Fall back to main bytecode
addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
done_tlbc:
#else
// Non-free-threaded build, always use the main bytecode
(void)tlbc_index; // Suppress unused parameter warning
(void)unwinder; // Suppress unused parameter warning
addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
#endif
; // Empty statement to avoid C23 extension warning
LocationInfo info = {0};
bool ok = parse_linetable(addrq, PyBytes_AS_STRING(meta->linetable),
meta->first_lineno, &info);
if (!ok) {
info.lineno = -1;
}
lineno = PyLong_FromLong(info.lineno);
if (!lineno) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create line number object");
goto error;
}
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
tuple = PyStructSequence_New(state->FrameInfo_Type);
if (!tuple) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create FrameInfo for code object");
goto error;
}
Py_INCREF(meta->func_name);
Py_INCREF(meta->file_name);
PyStructSequence_SetItem(tuple, 0, meta->file_name);
PyStructSequence_SetItem(tuple, 1, lineno);
PyStructSequence_SetItem(tuple, 2, meta->func_name);
*result = tuple;
return 0;
degraded: {
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject *)unwinder);
PyObject *degraded_tuple = PyStructSequence_New(state->FrameInfo_Type);
if (!degraded_tuple) {
goto error;
}
PyStructSequence_SetItem(degraded_tuple, 0, file);
PyStructSequence_SetItem(degraded_tuple, 1, Py_NewRef(Py_None));
PyStructSequence_SetItem(degraded_tuple, 2, func);
*result = degraded_tuple;
return 0;
}
error:
Py_XDECREF(func);
Py_XDECREF(file);
Py_XDECREF(linetable);
Py_XDECREF(lineno);
Py_XDECREF(tuple);
return -1;
}
/* ============================================================================
* STACK CHUNK MANAGEMENT FUNCTIONS
* ============================================================================ */
static void
cleanup_stack_chunks(StackChunkList *chunks)
{
for (size_t i = 0; i < chunks->count; ++i) {
PyMem_RawFree(chunks->chunks[i].local_copy);
}
PyMem_RawFree(chunks->chunks);
}
static int
process_single_stack_chunk(
RemoteUnwinderObject *unwinder,
uintptr_t chunk_addr,
StackChunkInfo *chunk_info
) {
// Start with default size assumption
size_t current_size = _PY_DATA_STACK_CHUNK_SIZE;
char *this_chunk = PyMem_RawMalloc(current_size);
if (!this_chunk) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate stack chunk buffer");
return -1;
}
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, chunk_addr, current_size, this_chunk) < 0) {
PyMem_RawFree(this_chunk);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read stack chunk");
return -1;
}
// Check actual size and reread if necessary
size_t actual_size = GET_MEMBER(size_t, this_chunk, offsetof(_PyStackChunk, size));
if (actual_size != current_size) {
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;
}
char *tmp = PyMem_RawRealloc(this_chunk, actual_size);
if (!tmp) {
PyMem_RawFree(this_chunk);
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to reallocate stack chunk buffer");
return -1;
}
this_chunk = tmp;
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, chunk_addr, actual_size, this_chunk) < 0) {
PyMem_RawFree(this_chunk);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to reread stack chunk with correct size");
return -1;
}
current_size = actual_size;
}
chunk_info->remote_addr = chunk_addr;
chunk_info->size = current_size;
chunk_info->local_copy = this_chunk;
return 0;
}
static int
copy_stack_chunks(RemoteUnwinderObject *unwinder,
uintptr_t tstate_addr,
StackChunkList *out_chunks)
{
uintptr_t chunk_addr;
StackChunkInfo *chunks = NULL;
size_t count = 0;
size_t max_chunks = 16;
if (read_ptr(unwinder, tstate_addr + (uintptr_t)unwinder->debug_offsets.thread_state.datastack_chunk, &chunk_addr)) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read initial stack chunk address");
return -1;
}
chunks = PyMem_RawMalloc(max_chunks * sizeof(StackChunkInfo));
if (!chunks) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate stack chunks array");
return -1;
}
while (chunk_addr != 0) {
// Grow array if needed
if (count >= max_chunks) {
max_chunks *= 2;
StackChunkInfo *new_chunks = PyMem_RawRealloc(chunks, max_chunks * sizeof(StackChunkInfo));
if (!new_chunks) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to grow stack chunks array");
goto error;
}
chunks = new_chunks;
}
// Process this chunk
if (process_single_stack_chunk(unwinder, chunk_addr, &chunks[count]) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process stack chunk");
goto error;
}
// Get next chunk address and increment count
chunk_addr = GET_MEMBER(uintptr_t, chunks[count].local_copy, offsetof(_PyStackChunk, previous));
count++;
}
out_chunks->chunks = chunks;
out_chunks->count = count;
return 0;
error:
for (size_t i = 0; i < count; ++i) {
PyMem_RawFree(chunks[i].local_copy);
}
PyMem_RawFree(chunks);
return -1;
}
static void *
find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr)
{
for (size_t i = 0; i < chunks->count; ++i) {
if (chunks->chunks[i].size <= offsetof(_PyStackChunk, data)) {
continue;
}
uintptr_t base = chunks->chunks[i].remote_addr + offsetof(_PyStackChunk, data);
size_t payload = chunks->chunks[i].size - offsetof(_PyStackChunk, data);
if (payload >= SIZEOF_INTERP_FRAME &&
remote_ptr >= base &&
remote_ptr <= base + payload - SIZEOF_INTERP_FRAME) {
return (char *)chunks->chunks[i].local_copy + (remote_ptr - chunks->chunks[i].remote_addr);
}
}
return NULL;
}
static int
parse_frame_from_chunks(
RemoteUnwinderObject *unwinder,
PyObject **result,
uintptr_t address,
uintptr_t *previous_frame,
StackChunkList *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;
}
char *frame = (char *)frame_ptr;
*previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
uintptr_t code_object = GET_MEMBER_NO_TAG(uintptr_t, frame_ptr, unwinder->debug_offsets.interpreter_frame.executable);
int frame_valid = is_frame_valid(unwinder, (uintptr_t)frame, code_object);
if (frame_valid != 1) {
return frame_valid;
}
uintptr_t instruction_pointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.instr_ptr);
// Get tlbc_index for free threading builds
int32_t tlbc_index = 0;
#ifdef Py_GIL_DISABLED
if (unwinder->debug_offsets.interpreter_frame.tlbc_index != 0) {
tlbc_index = GET_MEMBER(int32_t, frame, unwinder->debug_offsets.interpreter_frame.tlbc_index);
}
#endif
return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
}
/* ============================================================================
* INTERPRETER STATE AND THREAD DISCOVERY FUNCTIONS
* ============================================================================ */
static int
populate_initial_state_data(
int all_threads,
RemoteUnwinderObject *unwinder,
uintptr_t runtime_start_address,
uintptr_t *interpreter_state,
uintptr_t *tstate
) {
uintptr_t interpreter_state_list_head =
(uintptr_t)unwinder->debug_offsets.runtime_state.interpreters_head;
uintptr_t address_of_interpreter_state;
int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
runtime_start_address + interpreter_state_list_head,
sizeof(void*),
&address_of_interpreter_state);
if (bytes_read < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter state address");
return -1;
}
if (address_of_interpreter_state == 0) {
PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
set_exception_cause(unwinder, PyExc_RuntimeError, "Interpreter state is NULL");
return -1;
}
*interpreter_state = address_of_interpreter_state;
if (all_threads) {
*tstate = 0;
return 0;
}
uintptr_t address_of_thread = address_of_interpreter_state +
(uintptr_t)unwinder->debug_offsets.interpreter_state.threads_main;
if (_Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
address_of_thread,
sizeof(void*),
tstate) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read main thread state address");
return -1;
}
return 0;
}
static int
find_running_frame(
RemoteUnwinderObject *unwinder,
uintptr_t address_of_thread,
uintptr_t *frame
) {
if ((void*)address_of_thread != NULL) {
int err = read_ptr(
unwinder,
address_of_thread + (uintptr_t)unwinder->debug_offsets.thread_state.current_frame,
frame);
if (err) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read current frame pointer");
return -1;
}
return 0;
}
*frame = (uintptr_t)NULL;
return 0;
}
/* ============================================================================
* FRAME PARSING FUNCTIONS
* ============================================================================ */
static inline int
is_frame_valid(
RemoteUnwinderObject *unwinder,
uintptr_t frame_addr,
uintptr_t code_object_addr
) {
if ((void*)code_object_addr == NULL) {
return 0;
}
void* frame = (void*)frame_addr;
if (GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) == FRAME_OWNED_BY_CSTACK ||
GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) == FRAME_OWNED_BY_INTERPRETER) {
return 0; // C frame
}
if (GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) != FRAME_OWNED_BY_GENERATOR
&& GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) != FRAME_OWNED_BY_THREAD) {
PyErr_Format(PyExc_RuntimeError, "Unhandled frame owner %d.\n",
GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner));
set_exception_cause(unwinder, PyExc_RuntimeError, "Unhandled frame owner type in async frame");
return -1;
}
return 1;
}
static int
parse_frame_object(
RemoteUnwinderObject *unwinder,
PyObject** result,
uintptr_t address,
uintptr_t* address_of_code_object,
uintptr_t* previous_frame
) {
char frame[SIZEOF_INTERP_FRAME];
*address_of_code_object = 0;
Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
address,
SIZEOF_INTERP_FRAME,
frame
);
if (bytes_read < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter frame");
return -1;
}
*previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
uintptr_t code_object = GET_MEMBER_NO_TAG(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable);
int frame_valid = is_frame_valid(unwinder, (uintptr_t)frame, code_object);
if (frame_valid != 1) {
return frame_valid;
}
uintptr_t instruction_pointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.instr_ptr);
// Get tlbc_index for free threading builds
int32_t tlbc_index = 0;
#ifdef Py_GIL_DISABLED
if (unwinder->debug_offsets.interpreter_frame.tlbc_index != 0) {
tlbc_index = GET_MEMBER(int32_t, frame, unwinder->debug_offsets.interpreter_frame.tlbc_index);
}
#endif
*address_of_code_object = code_object;
return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
}
static int
parse_async_frame_chain(
RemoteUnwinderObject *unwinder,
PyObject *calls,
uintptr_t address_of_thread,
uintptr_t running_task_code_obj
) {
uintptr_t address_of_current_frame;
if (find_running_frame(unwinder, address_of_thread, &address_of_current_frame) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Running frame search failed in async chain");
return -1;
}
size_t frame_count = 0;
while ((void*)address_of_current_frame != NULL) {
if (++frame_count > MAX_FRAME_CHAIN_DEPTH) {
PyErr_SetString(PyExc_RuntimeError, "Too many async stack frames (possible infinite loop)");
set_exception_cause(unwinder, PyExc_RuntimeError, "Async frame chain iteration limit exceeded");
return -1;
}
PyObject* frame_info = NULL;
uintptr_t address_of_code_object;
int res = parse_frame_object(
unwinder,
&frame_info,
address_of_current_frame,
&address_of_code_object,
&address_of_current_frame
);
if (res < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Async frame object parsing failed in chain");
return -1;
}
if (!frame_info) {
continue;
}
if (PyList_Append(calls, frame_info) == -1) {
Py_DECREF(frame_info);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame info to async chain");
return -1;
}
Py_DECREF(frame_info);
if (address_of_code_object == running_task_code_obj) {
break;
}
}
return 0;
}
/* ============================================================================
* AWAITED BY PARSING FUNCTIONS
* ============================================================================ */
static int
append_awaited_by_for_thread(
RemoteUnwinderObject *unwinder,
uintptr_t head_addr,
PyObject *result
) {
char task_node[SIZEOF_LLIST_NODE];
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, head_addr,
sizeof(task_node), task_node) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task node head");
return -1;
}
size_t iteration_count = 0;
const size_t MAX_ITERATIONS = 2 << 15; // A reasonable upper bound
while (GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next) != head_addr) {
if (++iteration_count > MAX_ITERATIONS) {
PyErr_SetString(PyExc_RuntimeError, "Task list appears corrupted");
set_exception_cause(unwinder, PyExc_RuntimeError, "Task list iteration limit exceeded");
return -1;
}
if (GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next) == 0) {
PyErr_SetString(PyExc_RuntimeError,
"Invalid linked list structure reading remote memory");
set_exception_cause(unwinder, PyExc_RuntimeError, "NULL pointer in task linked list");
return -1;
}
uintptr_t task_addr = (uintptr_t)GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next)
- (uintptr_t)unwinder->async_debug_offsets.asyncio_task_object.task_node;
if (process_single_task_node(unwinder, task_addr, NULL, result) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process task node in awaited_by");
return -1;
}
// Read next node
if (_Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
(uintptr_t)GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next),
sizeof(task_node),
task_node) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read next task node in awaited_by");
return -1;
}
}
return 0;
}
static int
append_awaited_by(
RemoteUnwinderObject *unwinder,
unsigned long tid,
uintptr_t head_addr,
PyObject *result)
{
PyObject *tid_py = PyLong_FromUnsignedLong(tid);
if (tid_py == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID object");
return -1;
}
PyObject* awaited_by_for_thread = PyList_New(0);
if (awaited_by_for_thread == NULL) {
Py_DECREF(tid_py);
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create awaited_by thread list");
return -1;
}
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
PyObject *result_item = PyStructSequence_New(state->AwaitedInfo_Type);
if (result_item == NULL) {
Py_DECREF(tid_py);
Py_DECREF(awaited_by_for_thread);
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create AwaitedInfo");
return -1;
}
PyStructSequence_SetItem(result_item, 0, tid_py); // steals ref
PyStructSequence_SetItem(result_item, 1, awaited_by_for_thread); // steals ref
if (PyList_Append(result, result_item)) {
Py_DECREF(result_item);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append awaited_by result item");
return -1;
}
Py_DECREF(result_item);
if (append_awaited_by_for_thread(unwinder, head_addr, awaited_by_for_thread))
{
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append awaited_by for thread");
return -1;
}
return 0;
}
/* ============================================================================
* STACK UNWINDING FUNCTIONS
* ============================================================================ */
static int
process_frame_chain(
RemoteUnwinderObject *unwinder,
uintptr_t initial_frame_addr,
StackChunkList *chunks,
PyObject *frame_info
) {
uintptr_t frame_addr = initial_frame_addr;
uintptr_t prev_frame_addr = 0;
size_t frame_count = 0;
while ((void*)frame_addr != NULL) {
PyObject *frame = NULL;
uintptr_t next_frame_addr = 0;
if (++frame_count > MAX_FRAME_CHAIN_DEPTH) {
PyErr_SetString(PyExc_RuntimeError, "Too many stack frames (possible infinite loop)");
set_exception_cause(unwinder, PyExc_RuntimeError, "Frame chain iteration limit exceeded");
return -1;
}
// Try chunks first, fallback to direct memory read
if (parse_frame_from_chunks(unwinder, &frame, frame_addr, &next_frame_addr, chunks) < 0) {
PyErr_Clear();
uintptr_t address_of_code_object = 0;
if (parse_frame_object(unwinder, &frame, frame_addr, &address_of_code_object ,&next_frame_addr) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in chain");
return -1;
}
}
if (!frame) {
break;
}
if (prev_frame_addr && frame_addr != prev_frame_addr) {
PyErr_Format(PyExc_RuntimeError,
"Broken frame chain: expected frame at 0x%lx, got 0x%lx",
prev_frame_addr, frame_addr);
Py_DECREF(frame);
set_exception_cause(unwinder, PyExc_RuntimeError, "Frame chain consistency check failed");
return -1;
}
if (PyList_Append(frame_info, frame) == -1) {
Py_DECREF(frame);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to frame info list");
return -1;
}
Py_DECREF(frame);
prev_frame_addr = next_frame_addr;
frame_addr = next_frame_addr;
}
return 0;
}
static PyObject*
unwind_stack_for_thread(
RemoteUnwinderObject *unwinder,
uintptr_t *current_tstate
) {
PyObject *frame_info = NULL;
PyObject *thread_id = NULL;
PyObject *result = NULL;
StackChunkList chunks = {0};
char ts[SIZEOF_THREAD_STATE];
int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle, *current_tstate, (size_t)unwinder->debug_offsets.thread_state.size, ts);
if (bytes_read < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread state");
goto error;
}
uintptr_t frame_addr = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.current_frame);
frame_info = PyList_New(0);
if (!frame_info) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create frame info list");
goto error;
}
if (copy_stack_chunks(unwinder, *current_tstate, &chunks) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to copy stack chunks");
goto error;
}
if (process_frame_chain(unwinder, frame_addr, &chunks, frame_info) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process frame chain");
goto error;
}
*current_tstate = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.next);
thread_id = PyLong_FromLongLong(
GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.native_thread_id));
if (thread_id == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID");
goto error;
}
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
result = PyStructSequence_New(state->ThreadInfo_Type);
if (result == NULL) {
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create ThreadInfo");
goto error;
}
PyStructSequence_SetItem(result, 0, thread_id); // Steals reference
PyStructSequence_SetItem(result, 1, frame_info); // Steals reference
cleanup_stack_chunks(&chunks);
return result;
error:
Py_XDECREF(frame_info);
Py_XDECREF(thread_id);
Py_XDECREF(result);
cleanup_stack_chunks(&chunks);
return NULL;
}
/* ============================================================================
* REMOTEUNWINDER CLASS IMPLEMENTATION
* ============================================================================ */
/*[clinic input]
class _remote_debugging.RemoteUnwinder "RemoteUnwinderObject *" "&RemoteUnwinder_Type"
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=55f164d8803318be]*/
/*[clinic input]
_remote_debugging.RemoteUnwinder.__init__
pid: int
*
all_threads: bool = False
only_active_thread: bool = False
debug: bool = False
Initialize a new RemoteUnwinder object for debugging a remote Python process.
Args:
pid: Process ID of the target Python process to debug
all_threads: If True, initialize state for all threads in the process.
If False, only initialize for the main thread.
only_active_thread: If True, only sample the thread holding the GIL.
Cannot be used together with all_threads=True.
debug: If True, chain exceptions to explain the sequence of events that
lead to the exception.
The RemoteUnwinder provides functionality to inspect and debug a running Python
process, including examining thread states, stack frames and other runtime data.
Raises:
PermissionError: If access to the target process is denied
OSError: If unable to attach to the target process or access its memory
RuntimeError: If unable to read debug information from the target process
ValueError: If both all_threads and only_active_thread are True
[clinic start generated code]*/
static int
_remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
int pid, int all_threads,
int only_active_thread,
int debug)
/*[clinic end generated code: output=13ba77598ecdcbe1 input=8f8f12504e17da04]*/
{
// Validate that all_threads and only_active_thread are not both True
if (all_threads && only_active_thread) {
PyErr_SetString(PyExc_ValueError,
"all_threads and only_active_thread cannot both be True");
return -1;
}
#ifdef Py_GIL_DISABLED
if (only_active_thread) {
PyErr_SetString(PyExc_ValueError,
"only_active_thread is not supported when Py_GIL_DISABLED is not defined");
return -1;
}
#endif
self->debug = debug;
self->only_active_thread = only_active_thread;
self->cached_state = NULL;
if (_Py_RemoteDebug_InitProcHandle(&self->handle, pid) < 0) {
set_exception_cause(self, PyExc_RuntimeError, "Failed to initialize process handle");
return -1;
}
self->runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(&self->handle);
if (self->runtime_start_address == 0) {
set_exception_cause(self, PyExc_RuntimeError, "Failed to get Python runtime address");
return -1;
}
if (_Py_RemoteDebug_ReadDebugOffsets(&self->handle,
&self->runtime_start_address,
&self->debug_offsets) < 0)
{
set_exception_cause(self, PyExc_RuntimeError, "Failed to read debug offsets");
return -1;
}
// Validate that the debug offsets are valid
if(validate_debug_offsets(&self->debug_offsets) == -1) {
set_exception_cause(self, PyExc_RuntimeError, "Invalid debug offsets found");
return -1;
}
// Try to read async debug offsets, but don't fail if they're not available
self->async_debug_offsets_available = 1;
int async_debug_result = read_async_debug(self);
if (async_debug_result == PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS) {
return -1;
}
if (async_debug_result < 0) {
if (_Py_RemoteDebug_HasPermissionError()) {
return -1;
}
PyErr_Clear();
memset(&self->async_debug_offsets, 0, sizeof(self->async_debug_offsets));
self->async_debug_offsets_available = 0;
}
if (populate_initial_state_data(all_threads, self, self->runtime_start_address,
&self->interpreter_addr ,&self->tstate_addr) < 0)
{
set_exception_cause(self, PyExc_RuntimeError, "Failed to populate initial state data");
return -1;
}
self->code_object_cache = _Py_hashtable_new_full(
_Py_hashtable_hash_ptr,
_Py_hashtable_compare_direct,
NULL, // keys are stable pointers, don't destroy
cached_code_metadata_destroy,
NULL
);
if (self->code_object_cache == NULL) {
PyErr_NoMemory();
set_exception_cause(self, PyExc_MemoryError, "Failed to create code object cache");
return -1;
}
#ifdef Py_GIL_DISABLED
// Initialize TLBC cache
self->tlbc_generation = 0;
self->tlbc_cache = _Py_hashtable_new_full(
_Py_hashtable_hash_ptr,
_Py_hashtable_compare_direct,
NULL, // keys are stable pointers, don't destroy
tlbc_cache_entry_destroy,
NULL
);
if (self->tlbc_cache == NULL) {
PyErr_NoMemory();
set_exception_cause(self, PyExc_MemoryError, "Failed to create TLBC cache");
return -1;
}
#endif
return 0;
}
/*[clinic input]
@critical_section
_remote_debugging.RemoteUnwinder.get_stack_trace
Returns a list of stack traces for threads in the target process.
Each element in the returned list is a tuple of (thread_id, frame_list), where:
- thread_id is the OS thread identifier
- frame_list is a list of tuples (function_name, filename, line_number) representing
the Python stack frames for that thread, ordered from most recent to oldest
The threads returned depend on the initialization parameters:
- If only_active_thread was True: returns only the thread holding the GIL
- If all_threads was True: returns all threads
- Otherwise: returns only the main thread
Example:
[
(1234, [
('process_data', 'worker.py', 127),
('run_worker', 'worker.py', 45),
('main', 'app.py', 23)
]),
(1235, [
('handle_request', 'server.py', 89),
('serve_forever', 'server.py', 52)
])
]
Raises:
RuntimeError: If there is an error copying memory from the target process
OSError: If there is an error accessing the target process
PermissionError: If access to the target process is denied
UnicodeDecodeError: If there is an error decoding strings from the target process
[clinic start generated code]*/
static PyObject *
_remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self)
/*[clinic end generated code: output=666192b90c69d567 input=f756f341206f9116]*/
{
PyObject* result = NULL;
// Read interpreter state into opaque buffer
char interp_state_buffer[INTERP_STATE_BUFFER_SIZE];
if (_Py_RemoteDebug_PagedReadRemoteMemory(
&self->handle,
self->interpreter_addr,
INTERP_STATE_BUFFER_SIZE,
interp_state_buffer) < 0) {
set_exception_cause(self, PyExc_RuntimeError, "Failed to read interpreter state buffer");
goto exit;
}
// Get code object generation from buffer
uint64_t code_object_generation = GET_MEMBER(uint64_t, interp_state_buffer,
self->debug_offsets.interpreter_state.code_object_generation);
if (code_object_generation != self->code_object_generation) {
self->code_object_generation = code_object_generation;
_Py_hashtable_clear(self->code_object_cache);
}
// If only_active_thread is true, we need to determine which thread holds the GIL
PyThreadState* gil_holder = NULL;
if (self->only_active_thread) {
// The GIL state is already in interp_state_buffer, just read from there
// Check if GIL is locked
int gil_locked = GET_MEMBER(int, interp_state_buffer,
self->debug_offsets.interpreter_state.gil_runtime_state_locked);
if (gil_locked) {
// Get the last holder (current holder when GIL is locked)
gil_holder = GET_MEMBER(PyThreadState*, interp_state_buffer,
self->debug_offsets.interpreter_state.gil_runtime_state_holder);
} else {
// GIL is not locked, return empty list
result = PyList_New(0);
if (!result) {
set_exception_cause(self, PyExc_MemoryError, "Failed to create empty result list");
}
goto exit;
}
}
#ifdef Py_GIL_DISABLED
// Check TLBC generation and invalidate cache if needed
uint32_t current_tlbc_generation = GET_MEMBER(uint32_t, interp_state_buffer,
self->debug_offsets.interpreter_state.tlbc_generation);
if (current_tlbc_generation != self->tlbc_generation) {
self->tlbc_generation = current_tlbc_generation;
_Py_hashtable_clear(self->tlbc_cache);
}
#endif
uintptr_t current_tstate;
if (self->only_active_thread && gil_holder != NULL) {
// We have the GIL holder, process only that thread
current_tstate = (uintptr_t)gil_holder;
} else if (self->tstate_addr == 0) {
// Get threads head from buffer
current_tstate = GET_MEMBER(uintptr_t, interp_state_buffer,
self->debug_offsets.interpreter_state.threads_head);
} else {
current_tstate = self->tstate_addr;
}
result = PyList_New(0);
if (!result) {
set_exception_cause(self, PyExc_MemoryError, "Failed to create stack trace result list");
goto exit;
}
while (current_tstate != 0) {
uintptr_t prev_tstate = current_tstate;
PyObject* frame_info = unwind_stack_for_thread(self, &current_tstate);
if (!frame_info) {
Py_CLEAR(result);
set_exception_cause(self, PyExc_RuntimeError, "Failed to unwind stack for thread");
goto exit;
}
if (PyList_Append(result, frame_info) == -1) {
Py_DECREF(frame_info);
Py_CLEAR(result);
set_exception_cause(self, PyExc_RuntimeError, "Failed to append thread frame info");
goto exit;
}
Py_DECREF(frame_info);
if (current_tstate == prev_tstate) {
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);
goto exit;
}
// We are targeting a single tstate, break here
if (self->tstate_addr) {
break;
}
// If we're only processing the GIL holder, we're done after one iteration
if (self->only_active_thread && gil_holder != NULL) {
break;
}
}
exit:
_Py_RemoteDebug_ClearCache(&self->handle);
return result;
}
/*[clinic input]
@critical_section
_remote_debugging.RemoteUnwinder.get_all_awaited_by
Get all tasks and their awaited_by relationships from the remote process.
This provides a tree structure showing which tasks are waiting for other tasks.
For each task, returns:
1. The call stack frames leading to where the task is currently executing
2. The name of the task
3. A list of tasks that this task is waiting for, with their own frames/names/etc
Returns a list of [frames, task_name, subtasks] where:
- frames: List of (func_name, filename, lineno) showing the call stack
- task_name: String identifier for the task
- subtasks: List of tasks being awaited by this task, in same format
Raises:
RuntimeError: If AsyncioDebug section is not available in the remote process
MemoryError: If memory allocation fails
OSError: If reading from the remote process fails
Example output:
[
# Task c2_root waiting for two subtasks
[
# Call stack of c2_root
[("c5", "script.py", 10), ("c4", "script.py", 14)],
"c2_root",
[
# First subtask (sub_main_2) and what it's waiting for
[
[("c1", "script.py", 23)],
"sub_main_2",
[...]
],
# Second subtask and its waiters
[...]
]
]
]
[clinic start generated code]*/
static PyObject *
_remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *self)
/*[clinic end generated code: output=6a49cd345e8aec53 input=a452c652bb00701a]*/
{
if (!self->async_debug_offsets_available) {
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
set_exception_cause(self, PyExc_RuntimeError, "AsyncioDebug section unavailable in get_all_awaited_by");
return NULL;
}
PyObject *result = PyList_New(0);
if (result == NULL) {
set_exception_cause(self, PyExc_MemoryError, "Failed to create awaited_by result list");
goto result_err;
}
// Process all threads
if (iterate_threads(self, process_thread_for_awaited_by, result) < 0) {
goto result_err;
}
uintptr_t head_addr = self->interpreter_addr
+ (uintptr_t)self->async_debug_offsets.asyncio_interpreter_state.asyncio_tasks_head;
// On top of a per-thread task lists used by default by asyncio to avoid
// contention, there is also a fallback per-interpreter list of tasks;
// any tasks still pending when a thread is destroyed will be moved to the
// per-interpreter task list. It's unlikely we'll find anything here, but
// interesting for debugging.
if (append_awaited_by(self, 0, head_addr, result))
{
set_exception_cause(self, PyExc_RuntimeError, "Failed to append interpreter awaited_by in get_all_awaited_by");
goto result_err;
}
_Py_RemoteDebug_ClearCache(&self->handle);
return result;
result_err:
_Py_RemoteDebug_ClearCache(&self->handle);
Py_XDECREF(result);
return NULL;
}
/*[clinic input]
@critical_section
_remote_debugging.RemoteUnwinder.get_async_stack_trace
Get the currently running async tasks and their dependency graphs from the remote process.
This returns information about running tasks and all tasks that are waiting for them,
forming a complete dependency graph for each thread's active task.
For each thread with a running task, returns the running task plus all tasks that
transitively depend on it (tasks waiting for the running task, tasks waiting for
those tasks, etc.).
Returns a list of per-thread results, where each thread result contains:
- Thread ID
- List of task information for the running task and all its waiters
Each task info contains:
- Task ID (memory address)
- Task name
- Call stack frames: List of (func_name, filename, lineno)
- List of tasks waiting for this task (recursive structure)
Raises:
RuntimeError: If AsyncioDebug section is not available in the target process
MemoryError: If memory allocation fails
OSError: If reading from the remote process fails
Example output (similar structure to get_all_awaited_by but only for running tasks):
[
# Thread 140234 results
(140234, [
# Running task and its complete waiter dependency graph
(4345585712, 'main_task',
[("run_server", "server.py", 127), ("main", "app.py", 23)],
[
# Tasks waiting for main_task
(4345585800, 'worker_1', [...], [...]),
(4345585900, 'worker_2', [...], [...])
])
])
]
[clinic start generated code]*/
static PyObject *
_remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject *self)
/*[clinic end generated code: output=6433d52b55e87bbe input=8744b47c9ec2220a]*/
{
if (!self->async_debug_offsets_available) {
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
set_exception_cause(self, PyExc_RuntimeError, "AsyncioDebug section unavailable in get_async_stack_trace");
return NULL;
}
PyObject *result = PyList_New(0);
if (result == NULL) {
set_exception_cause(self, PyExc_MemoryError, "Failed to create result list in get_async_stack_trace");
return NULL;
}
// Process all threads
if (iterate_threads(self, process_thread_for_async_stack_trace, result) < 0) {
goto result_err;
}
_Py_RemoteDebug_ClearCache(&self->handle);
return result;
result_err:
_Py_RemoteDebug_ClearCache(&self->handle);
Py_XDECREF(result);
return NULL;
}
static PyMethodDef RemoteUnwinder_methods[] = {
_REMOTE_DEBUGGING_REMOTEUNWINDER_GET_STACK_TRACE_METHODDEF
_REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ALL_AWAITED_BY_METHODDEF
_REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ASYNC_STACK_TRACE_METHODDEF
{NULL, NULL}
};
static void
RemoteUnwinder_dealloc(PyObject *op)
{
RemoteUnwinderObject *self = RemoteUnwinder_CAST(op);
PyTypeObject *tp = Py_TYPE(self);
if (self->code_object_cache) {
_Py_hashtable_destroy(self->code_object_cache);
}
#ifdef Py_GIL_DISABLED
if (self->tlbc_cache) {
_Py_hashtable_destroy(self->tlbc_cache);
}
#endif
if (self->handle.pid != 0) {
_Py_RemoteDebug_ClearCache(&self->handle);
_Py_RemoteDebug_CleanupProcHandle(&self->handle);
}
PyObject_Del(self);
Py_DECREF(tp);
}
static PyType_Slot RemoteUnwinder_slots[] = {
{Py_tp_doc, (void *)"RemoteUnwinder(pid): Inspect stack of a remote Python process."},
{Py_tp_methods, RemoteUnwinder_methods},
{Py_tp_init, _remote_debugging_RemoteUnwinder___init__},
{Py_tp_dealloc, RemoteUnwinder_dealloc},
{0, NULL}
};
static PyType_Spec RemoteUnwinder_spec = {
.name = "_remote_debugging.RemoteUnwinder",
.basicsize = sizeof(RemoteUnwinderObject),
.flags = Py_TPFLAGS_DEFAULT,
.slots = RemoteUnwinder_slots,
};
/* ============================================================================
* MODULE INITIALIZATION
* ============================================================================ */
static int
_remote_debugging_exec(PyObject *m)
{
RemoteDebuggingState *st = RemoteDebugging_GetState(m);
#define CREATE_TYPE(mod, type, spec) \
do { \
type = (PyTypeObject *)PyType_FromMetaclass(NULL, mod, spec, NULL); \
if (type == NULL) { \
return -1; \
} \
} while (0)
CREATE_TYPE(m, st->RemoteDebugging_Type, &RemoteUnwinder_spec);
if (PyModule_AddType(m, st->RemoteDebugging_Type) < 0) {
return -1;
}
// Initialize structseq types
st->TaskInfo_Type = PyStructSequence_NewType(&TaskInfo_desc);
if (st->TaskInfo_Type == NULL) {
return -1;
}
if (PyModule_AddType(m, st->TaskInfo_Type) < 0) {
return -1;
}
st->FrameInfo_Type = PyStructSequence_NewType(&FrameInfo_desc);
if (st->FrameInfo_Type == NULL) {
return -1;
}
if (PyModule_AddType(m, st->FrameInfo_Type) < 0) {
return -1;
}
st->CoroInfo_Type = PyStructSequence_NewType(&CoroInfo_desc);
if (st->CoroInfo_Type == NULL) {
return -1;
}
if (PyModule_AddType(m, st->CoroInfo_Type) < 0) {
return -1;
}
st->ThreadInfo_Type = PyStructSequence_NewType(&ThreadInfo_desc);
if (st->ThreadInfo_Type == NULL) {
return -1;
}
if (PyModule_AddType(m, st->ThreadInfo_Type) < 0) {
return -1;
}
st->AwaitedInfo_Type = PyStructSequence_NewType(&AwaitedInfo_desc);
if (st->AwaitedInfo_Type == NULL) {
return -1;
}
if (PyModule_AddType(m, st->AwaitedInfo_Type) < 0) {
return -1;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
int rc = PyModule_AddIntConstant(m, "PROCESS_VM_READV_SUPPORTED", HAVE_PROCESS_VM_READV);
if (rc < 0) {
return -1;
}
if (RemoteDebugging_InitState(st) < 0) {
return -1;
}
return 0;
}
static int
remote_debugging_traverse(PyObject *mod, visitproc visit, void *arg)
{
RemoteDebuggingState *state = RemoteDebugging_GetState(mod);
Py_VISIT(state->RemoteDebugging_Type);
Py_VISIT(state->TaskInfo_Type);
Py_VISIT(state->FrameInfo_Type);
Py_VISIT(state->CoroInfo_Type);
Py_VISIT(state->ThreadInfo_Type);
Py_VISIT(state->AwaitedInfo_Type);
return 0;
}
static int
remote_debugging_clear(PyObject *mod)
{
RemoteDebuggingState *state = RemoteDebugging_GetState(mod);
Py_CLEAR(state->RemoteDebugging_Type);
Py_CLEAR(state->TaskInfo_Type);
Py_CLEAR(state->FrameInfo_Type);
Py_CLEAR(state->CoroInfo_Type);
Py_CLEAR(state->ThreadInfo_Type);
Py_CLEAR(state->AwaitedInfo_Type);
return 0;
}
static void
remote_debugging_free(void *mod)
{
(void)remote_debugging_clear((PyObject *)mod);
}
static PyModuleDef_Slot remote_debugging_slots[] = {
{Py_mod_exec, _remote_debugging_exec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0, NULL},
};
static PyMethodDef remote_debugging_methods[] = {
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef remote_debugging_module = {
PyModuleDef_HEAD_INIT,
.m_name = "_remote_debugging",
.m_size = sizeof(RemoteDebuggingState),
.m_methods = remote_debugging_methods,
.m_slots = remote_debugging_slots,
.m_traverse = remote_debugging_traverse,
.m_clear = remote_debugging_clear,
.m_free = remote_debugging_free,
};
PyMODINIT_FUNC
PyInit__remote_debugging(void)
{
return PyModuleDef_Init(&remote_debugging_module);
}