Completely refactor Modules/_remote_debugging_module.c with improved code organization, replacing scattered reference counting and error handling with centralized goto error paths. This cleanup improves maintainability and reduces code duplication throughout the module while preserving the same external API. Implement memory page caching optimization in Python/remote_debug.h to avoid repeated reads of the same memory regions during debugging operations. The cache stores previously read memory pages and reuses them for subsequent reads, significantly reducing system calls and improving performance. Add code object caching mechanism with a new code_object_generation field in the interpreter state that tracks when code object caches need invalidation. This allows efficient reuse of parsed code object metadata and eliminates redundant processing of the same code objects across debugging sessions. Optimize memory operations by replacing multiple individual structure copies with single bulk reads for the same data structures. This reduces the number of memory operations and system calls required to gather debugging information from the target process. Update Makefile.pre.in to include Python/remote_debug.h in the headers list, ensuring that changes to the remote debugging header force proper recompilation of dependent modules and maintain build consistency across the codebase. Also, make the module compatible with the free threading build as an extra :) Co-authored-by: Łukasz Langa <lukasz@langa.pl>
2692 lines
78 KiB
C
2692 lines
78 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
|
|
* ============================================================================ */
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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_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 "../Python/remote_debug.h"
|
|
|
|
#ifndef HAVE_PROCESS_VM_READV
|
|
# define HAVE_PROCESS_VM_READV 0
|
|
#endif
|
|
|
|
/* ============================================================================
|
|
* TYPE DEFINITIONS AND STRUCTURES
|
|
* ============================================================================ */
|
|
|
|
#define GET_MEMBER(type, obj, offset) (*(type*)((char*)(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(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
|
|
offsetof(PyInterpreterState, tlbc_indices.tlbc_generation) + sizeof(uint32_t)), \
|
|
offsetof(PyInterpreterState, threads.head) + sizeof(void*))
|
|
#else
|
|
#define INTERP_STATE_MIN_SIZE MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
|
|
offsetof(PyInterpreterState, threads.head) + sizeof(void*))
|
|
#endif
|
|
#define INTERP_STATE_BUFFER_SIZE MAX(INTERP_STATE_MIN_SIZE, 256)
|
|
|
|
|
|
|
|
// 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;
|
|
};
|
|
|
|
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;
|
|
#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;
|
|
|
|
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;
|
|
} RemoteDebuggingState;
|
|
|
|
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 int
|
|
parse_tasks_in_set(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t set_addr,
|
|
PyObject *awaited_by,
|
|
int recurse_task
|
|
);
|
|
|
|
static int
|
|
parse_task(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t task_address,
|
|
PyObject *render_to,
|
|
int recurse_task
|
|
);
|
|
|
|
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* previous_frame
|
|
);
|
|
|
|
/* ============================================================================
|
|
* UTILITY FUNCTIONS AND HELPERS
|
|
* ============================================================================ */
|
|
|
|
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 int
|
|
RemoteDebugging_InitState(RemoteDebuggingState *st)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Helper to chain exceptions and avoid repetitions
|
|
static void
|
|
chain_exceptions(PyObject *exception, const char *string)
|
|
{
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
PyErr_SetString(exception, string);
|
|
_PyErr_ChainExceptions1(exc);
|
|
}
|
|
|
|
/* ============================================================================
|
|
* MEMORY READING FUNCTIONS
|
|
* ============================================================================ */
|
|
|
|
static inline int
|
|
read_ptr(proc_handle_t *handle, uintptr_t address, uintptr_t *ptr_addr)
|
|
{
|
|
int result = _Py_RemoteDebug_PagedReadRemoteMemory(handle, address, sizeof(void*), ptr_addr);
|
|
if (result < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int
|
|
read_Py_ssize_t(proc_handle_t *handle, uintptr_t address, Py_ssize_t *size)
|
|
{
|
|
int result = _Py_RemoteDebug_PagedReadRemoteMemory(handle, address, sizeof(Py_ssize_t), size);
|
|
if (result < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
read_py_ptr(proc_handle_t *handle, uintptr_t address, uintptr_t *ptr_addr)
|
|
{
|
|
if (read_ptr(handle, address, ptr_addr)) {
|
|
return -1;
|
|
}
|
|
*ptr_addr &= ~Py_TAG_BITS;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
read_char(proc_handle_t *handle, uintptr_t address, char *result)
|
|
{
|
|
int res = _Py_RemoteDebug_PagedReadRemoteMemory(handle, address, sizeof(char), result);
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
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) {
|
|
goto err;
|
|
}
|
|
|
|
Py_ssize_t len = GET_MEMBER(Py_ssize_t, unicode_obj, unwinder->debug_offsets.unicode_object.length);
|
|
if (len < 0 || len > max_len) {
|
|
PyErr_Format(PyExc_RuntimeError,
|
|
"Invalid string length (%zd) at 0x%lx", len, address);
|
|
return NULL;
|
|
}
|
|
|
|
buf = (char *)PyMem_RawMalloc(len+1);
|
|
if (buf == NULL) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
|
|
size_t offset = unwinder->debug_offsets.unicode_object.asciiobject_size;
|
|
res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address + offset, len, buf);
|
|
if (res < 0) {
|
|
goto err;
|
|
}
|
|
buf[len] = '\0';
|
|
|
|
result = PyUnicode_FromStringAndSize(buf, len);
|
|
if (result == NULL) {
|
|
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) {
|
|
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 string length (%zd) at 0x%lx", len, address);
|
|
return NULL;
|
|
}
|
|
|
|
buf = (char *)PyMem_RawMalloc(len+1);
|
|
if (buf == NULL) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
|
|
size_t offset = unwinder->debug_offsets.bytes_object.ob_sval;
|
|
res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address + offset, len, buf);
|
|
if (res < 0) {
|
|
goto err;
|
|
}
|
|
buf[len] = '\0';
|
|
|
|
result = PyBytes_FromStringAndSize(buf, len);
|
|
if (result == NULL) {
|
|
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,
|
|
unwinder->debug_offsets.long_object.size,
|
|
long_obj);
|
|
if (bytes_read < 0) {
|
|
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 the long object has inline digits, use them directly
|
|
digit *digits;
|
|
if (size <= _PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS) {
|
|
// For small integers, digits are inline in the long_value.ob_digit array
|
|
digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
|
|
if (!digits) {
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
memcpy(digits, long_obj + unwinder->debug_offsets.long_object.ob_digit, size * sizeof(digit));
|
|
} else {
|
|
// For larger integers, we need to read the digits separately
|
|
digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
|
|
if (!digits) {
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
|
|
bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
address + unwinder->debug_offsets.long_object.ob_digit,
|
|
sizeof(digit) * size,
|
|
digits
|
|
);
|
|
if (bytes_read < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
long long value = 0;
|
|
|
|
// In theory this can overflow, but because of llvm/llvm-project#16778
|
|
// we can't use __builtin_mul_overflow because it fails to link with
|
|
// __muloti4 on aarch64. In practice this is fine because all we're
|
|
// testing here are task numbers that would fit in a single byte.
|
|
for (Py_ssize_t i = 0; i < size; ++i) {
|
|
long long factor = digits[i] * (1UL << (Py_ssize_t)(shift * i));
|
|
value += factor;
|
|
}
|
|
PyMem_RawFree(digits);
|
|
if (negative) {
|
|
value *= -1;
|
|
}
|
|
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");
|
|
if (address == 0) {
|
|
// Error out: 'python' substring covers both executable and DLL
|
|
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");
|
|
if (address == 0) {
|
|
// Error out: 'python' substring covers both executable and DLL
|
|
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");
|
|
if (address == 0) {
|
|
PyErr_Clear();
|
|
address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython");
|
|
}
|
|
if (address == 0) {
|
|
// Error out: 'python' substring covers both executable and DLL
|
|
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) {
|
|
return -1;
|
|
}
|
|
|
|
size_t size = sizeof(struct _Py_AsyncioModuleDebugOffsets);
|
|
int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, async_debug_addr, size, &unwinder->async_debug_offsets);
|
|
return result;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* 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,
|
|
unwinder->async_debug_offsets.asyncio_task_object.size,
|
|
task_obj);
|
|
if (err < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
uintptr_t task_name_addr = GET_MEMBER(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_name);
|
|
task_name_addr &= ~Py_TAG_BITS;
|
|
|
|
// 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) {
|
|
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) {
|
|
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) {
|
|
chain_exceptions(PyExc_RuntimeError, "Failed to get task name");
|
|
return NULL;
|
|
}
|
|
return PyUnicode_FromFormat("Task-%d", 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");
|
|
return NULL;
|
|
}
|
|
|
|
return read_py_str(
|
|
unwinder,
|
|
task_name_addr,
|
|
255
|
|
);
|
|
}
|
|
|
|
static int parse_task_awaited_by(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t task_address,
|
|
PyObject *awaited_by,
|
|
int recurse_task
|
|
) {
|
|
// Read the entire TaskObj at once
|
|
char task_obj[SIZEOF_TASK_OBJ];
|
|
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address,
|
|
unwinder->async_debug_offsets.asyncio_task_object.size,
|
|
task_obj) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
uintptr_t task_ab_addr = GET_MEMBER(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by);
|
|
task_ab_addr &= ~Py_TAG_BITS;
|
|
|
|
if ((void*)task_ab_addr == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
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) {
|
|
if (parse_tasks_in_set(unwinder, task_ab_addr, awaited_by, recurse_task)) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (parse_task(unwinder, task_ab_addr, awaited_by, recurse_task)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
handle_yield_from_frame(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t gi_iframe_addr,
|
|
uintptr_t gen_type_addr,
|
|
PyObject *render_to
|
|
) {
|
|
// 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) {
|
|
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_/");
|
|
return -1;
|
|
}
|
|
|
|
uintptr_t stackpointer_addr = GET_MEMBER(uintptr_t, iframe, unwinder->debug_offsets.interpreter_frame.stackpointer);
|
|
stackpointer_addr &= ~Py_TAG_BITS;
|
|
|
|
if ((void*)stackpointer_addr != NULL) {
|
|
uintptr_t gi_await_addr;
|
|
err = read_py_ptr(
|
|
&unwinder->handle,
|
|
stackpointer_addr - sizeof(void*),
|
|
&gi_await_addr);
|
|
if (err) {
|
|
return -1;
|
|
}
|
|
|
|
if ((void*)gi_await_addr != NULL) {
|
|
uintptr_t gi_await_addr_type_addr;
|
|
err = read_ptr(
|
|
&unwinder->handle,
|
|
gi_await_addr + unwinder->debug_offsets.pyobject.ob_type,
|
|
&gi_await_addr_type_addr);
|
|
if (err) {
|
|
return -1;
|
|
}
|
|
|
|
if (gen_type_addr == gi_await_addr_type_addr) {
|
|
/* This needs an explanation. We always start with parsing
|
|
native coroutine / generator frames. Ultimately they
|
|
are awaiting on something. That something can be
|
|
a native coroutine frame or... an iterator.
|
|
If it's the latter -- we can't continue building
|
|
our chain. So the condition to bail out of this is
|
|
to do that when the type of the current coroutine
|
|
doesn't match the type of whatever it points to
|
|
in its cr_await.
|
|
*/
|
|
err = parse_coro_chain(unwinder, gi_await_addr, render_to);
|
|
if (err) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
parse_coro_chain(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t coro_address,
|
|
PyObject *render_to
|
|
) {
|
|
assert((void*)coro_address != NULL);
|
|
|
|
// 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) {
|
|
return -1;
|
|
}
|
|
|
|
uintptr_t gen_type_addr = GET_MEMBER(uintptr_t, gen_object, unwinder->debug_offsets.pyobject.ob_type);
|
|
|
|
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 + unwinder->debug_offsets.gen_object.gi_iframe;
|
|
if (parse_frame_object(unwinder, &name, gi_iframe_addr, &prev_frame) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (PyList_Append(render_to, name)) {
|
|
Py_DECREF(name);
|
|
return -1;
|
|
}
|
|
Py_DECREF(name);
|
|
|
|
if (GET_MEMBER(int8_t, gen_object, unwinder->debug_offsets.gen_object.gi_frame_state) == FRAME_SUSPENDED_YIELD_FROM) {
|
|
return handle_yield_from_frame(unwinder, gi_iframe_addr, gen_type_addr, render_to);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static PyObject*
|
|
create_task_result(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t task_address,
|
|
int recurse_task
|
|
) {
|
|
PyObject* result = NULL;
|
|
PyObject *call_stack = NULL;
|
|
PyObject *tn = NULL;
|
|
char task_obj[SIZEOF_TASK_OBJ];
|
|
uintptr_t coro_addr;
|
|
|
|
result = PyList_New(0);
|
|
if (result == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
call_stack = PyList_New(0);
|
|
if (call_stack == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
if (PyList_Append(result, call_stack)) {
|
|
goto error;
|
|
}
|
|
Py_CLEAR(call_stack);
|
|
|
|
if (recurse_task) {
|
|
tn = parse_task_name(unwinder, task_address);
|
|
} else {
|
|
tn = PyLong_FromUnsignedLongLong(task_address);
|
|
}
|
|
if (tn == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
if (PyList_Append(result, tn)) {
|
|
goto error;
|
|
}
|
|
Py_CLEAR(tn);
|
|
|
|
// Parse coroutine chain
|
|
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address,
|
|
unwinder->async_debug_offsets.asyncio_task_object.size,
|
|
task_obj) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
coro_addr = GET_MEMBER(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_coro);
|
|
coro_addr &= ~Py_TAG_BITS;
|
|
|
|
if ((void*)coro_addr != NULL) {
|
|
call_stack = PyList_New(0);
|
|
if (call_stack == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
if (parse_coro_chain(unwinder, coro_addr, call_stack) < 0) {
|
|
Py_DECREF(call_stack);
|
|
goto error;
|
|
}
|
|
|
|
if (PyList_Reverse(call_stack)) {
|
|
Py_DECREF(call_stack);
|
|
goto error;
|
|
}
|
|
|
|
if (PyList_SetItem(result, 0, call_stack) < 0) {
|
|
Py_DECREF(call_stack);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
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,
|
|
int recurse_task
|
|
) {
|
|
char is_task;
|
|
PyObject* result = NULL;
|
|
PyObject* awaited_by = NULL;
|
|
int err;
|
|
|
|
err = read_char(
|
|
&unwinder->handle,
|
|
task_address + unwinder->async_debug_offsets.asyncio_task_object.task_is_task,
|
|
&is_task);
|
|
if (err) {
|
|
goto error;
|
|
}
|
|
|
|
if (is_task) {
|
|
result = create_task_result(unwinder, task_address, recurse_task);
|
|
if (!result) {
|
|
goto error;
|
|
}
|
|
} else {
|
|
result = PyList_New(0);
|
|
if (result == NULL) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (PyList_Append(render_to, result)) {
|
|
goto error;
|
|
}
|
|
|
|
if (recurse_task) {
|
|
awaited_by = PyList_New(0);
|
|
if (awaited_by == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
if (PyList_Append(result, awaited_by)) {
|
|
goto error;
|
|
}
|
|
Py_DECREF(awaited_by);
|
|
|
|
/* awaited_by is borrowed from 'result' to simplify cleanup */
|
|
if (parse_task_awaited_by(unwinder, task_address, awaited_by, 1) < 0) {
|
|
// Clear the pointer so the cleanup doesn't try to decref it since
|
|
// it's borrowed from 'result' and will be decrefed when result is
|
|
// deleted.
|
|
awaited_by = NULL;
|
|
goto error;
|
|
}
|
|
}
|
|
Py_DECREF(result);
|
|
|
|
return 0;
|
|
|
|
error:
|
|
Py_XDECREF(result);
|
|
Py_XDECREF(awaited_by);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
process_set_entry(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t table_ptr,
|
|
PyObject *awaited_by,
|
|
int recurse_task
|
|
) {
|
|
uintptr_t key_addr;
|
|
if (read_py_ptr(&unwinder->handle, table_ptr, &key_addr)) {
|
|
return -1;
|
|
}
|
|
|
|
if ((void*)key_addr != NULL) {
|
|
Py_ssize_t ref_cnt;
|
|
if (read_Py_ssize_t(&unwinder->handle, table_ptr, &ref_cnt)) {
|
|
return -1;
|
|
}
|
|
|
|
if (ref_cnt) {
|
|
// if 'ref_cnt=0' it's a set dummy marker
|
|
if (parse_task(unwinder, key_addr, awaited_by, recurse_task)) {
|
|
return -1;
|
|
}
|
|
return 1; // Successfully processed a valid entry
|
|
}
|
|
}
|
|
return 0; // Entry was NULL or dummy marker
|
|
}
|
|
|
|
static int
|
|
parse_tasks_in_set(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t set_addr,
|
|
PyObject *awaited_by,
|
|
int recurse_task
|
|
) {
|
|
char set_object[SIZEOF_SET_OBJ];
|
|
int err = _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
set_addr,
|
|
SIZEOF_SET_OBJ,
|
|
set_object);
|
|
if (err < 0) {
|
|
return -1;
|
|
}
|
|
|
|
Py_ssize_t num_els = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.used);
|
|
Py_ssize_t set_len = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.mask) + 1; // The set contains the `mask+1` element slots.
|
|
uintptr_t table_ptr = GET_MEMBER(uintptr_t, set_object, unwinder->debug_offsets.set_object.table);
|
|
|
|
Py_ssize_t i = 0;
|
|
Py_ssize_t els = 0;
|
|
while (i < set_len && els < num_els) {
|
|
int result = process_set_entry(unwinder, table_ptr, awaited_by, recurse_task);
|
|
|
|
if (result < 0) {
|
|
return -1;
|
|
}
|
|
if (result > 0) {
|
|
els++;
|
|
}
|
|
|
|
table_ptr += sizeof(void*) * 2;
|
|
i++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
setup_async_result_structure(PyObject **result, PyObject **calls)
|
|
{
|
|
*result = PyList_New(1);
|
|
if (*result == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
*calls = PyList_New(0);
|
|
if (*calls == NULL) {
|
|
Py_DECREF(*result);
|
|
*result = NULL;
|
|
return -1;
|
|
}
|
|
|
|
if (PyList_SetItem(*result, 0, *calls)) { /* steals ref to 'calls' */
|
|
Py_DECREF(*calls);
|
|
Py_DECREF(*result);
|
|
*result = NULL;
|
|
*calls = NULL;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
add_task_info_to_result(
|
|
RemoteUnwinderObject *self,
|
|
PyObject *result,
|
|
uintptr_t running_task_addr
|
|
) {
|
|
PyObject *tn = parse_task_name(self, running_task_addr);
|
|
if (tn == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (PyList_Append(result, tn)) {
|
|
Py_DECREF(tn);
|
|
return -1;
|
|
}
|
|
Py_DECREF(tn);
|
|
|
|
PyObject* awaited_by = PyList_New(0);
|
|
if (awaited_by == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (PyList_Append(result, awaited_by)) {
|
|
Py_DECREF(awaited_by);
|
|
return -1;
|
|
}
|
|
Py_DECREF(awaited_by);
|
|
|
|
if (parse_task_awaited_by(
|
|
self, running_task_addr, awaited_by, 1) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
process_single_task_node(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t task_addr,
|
|
PyObject *result
|
|
) {
|
|
PyObject *tn = NULL;
|
|
PyObject *current_awaited_by = NULL;
|
|
PyObject *task_id = NULL;
|
|
PyObject *result_item = NULL;
|
|
|
|
tn = parse_task_name(unwinder, task_addr);
|
|
if (tn == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
current_awaited_by = PyList_New(0);
|
|
if (current_awaited_by == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
task_id = PyLong_FromUnsignedLongLong(task_addr);
|
|
if (task_id == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
result_item = PyTuple_New(3);
|
|
if (result_item == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
PyTuple_SET_ITEM(result_item, 0, task_id); // steals ref
|
|
PyTuple_SET_ITEM(result_item, 1, tn); // steals ref
|
|
PyTuple_SET_ITEM(result_item, 2, current_awaited_by); // steals ref
|
|
|
|
// References transferred to tuple
|
|
task_id = NULL;
|
|
tn = NULL;
|
|
current_awaited_by = NULL;
|
|
|
|
if (PyList_Append(result, result_item)) {
|
|
Py_DECREF(result_item);
|
|
return -1;
|
|
}
|
|
Py_DECREF(result_item);
|
|
|
|
// Get back current_awaited_by reference for parse_task_awaited_by
|
|
current_awaited_by = PyTuple_GET_ITEM(result_item, 2);
|
|
if (parse_task_awaited_by(unwinder, task_addr, current_awaited_by, 0) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
Py_XDECREF(tn);
|
|
Py_XDECREF(current_awaited_by);
|
|
Py_XDECREF(task_id);
|
|
Py_XDECREF(result_item);
|
|
return -1;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* 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 by setting to NULL
|
|
_Py_hashtable_set(self->tlbc_cache, key, NULL);
|
|
entry = NULL;
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
static int
|
|
cache_tlbc_array(RemoteUnwinderObject *self, 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(&self->handle, tlbc_array_addr, &tlbc_array_ptr) != 0 || tlbc_array_ptr == 0) {
|
|
return 0; // No TLBC array
|
|
}
|
|
|
|
// Read the TLBC array size
|
|
Py_ssize_t tlbc_size;
|
|
if (_Py_RemoteDebug_PagedReadRemoteMemory(&self->handle, tlbc_array_ptr, sizeof(tlbc_size), &tlbc_size) != 0 || tlbc_size <= 0) {
|
|
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) {
|
|
return -1; // Memory error
|
|
}
|
|
|
|
if (_Py_RemoteDebug_PagedReadRemoteMemory(&self->handle, tlbc_array_ptr, sizeof(Py_ssize_t) + array_data_size, tlbc_array) != 0) {
|
|
PyMem_RawFree(tlbc_array);
|
|
return 0; // Read error
|
|
}
|
|
|
|
// Create cache entry
|
|
entry = PyMem_RawMalloc(sizeof(TLBCCacheEntry));
|
|
if (!entry) {
|
|
PyMem_RawFree(tlbc_array);
|
|
return -1; // 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(self->tlbc_cache, key, entry) < 0) {
|
|
tlbc_cache_entry_destroy(entry);
|
|
return -1; // 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);
|
|
uint64_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;
|
|
|
|
#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)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
func = read_py_str(unwinder,
|
|
GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.qualname), 1024);
|
|
if (!func) {
|
|
goto error;
|
|
}
|
|
|
|
file = read_py_str(unwinder,
|
|
GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.filename), 1024);
|
|
if (!file) {
|
|
goto error;
|
|
}
|
|
|
|
linetable = read_py_bytes(unwinder,
|
|
GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.linetable), 4096);
|
|
if (!linetable) {
|
|
goto error;
|
|
}
|
|
|
|
meta = PyMem_RawMalloc(sizeof(CachedCodeMetadata));
|
|
if (!meta) {
|
|
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 + unwinder->debug_offsets.code_object.co_code_adaptive;
|
|
|
|
if (unwinder && unwinder->code_object_cache && _Py_hashtable_set(unwinder->code_object_cache, key, meta) < 0) {
|
|
cached_code_metadata_destroy(meta);
|
|
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) > 0) {
|
|
tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
|
|
}
|
|
}
|
|
|
|
if (tlbc_entry && tlbc_index < tlbc_entry->tlbc_array_size) {
|
|
// 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) {
|
|
goto error;
|
|
}
|
|
|
|
tuple = PyTuple_New(3);
|
|
if (!tuple) {
|
|
goto error;
|
|
}
|
|
|
|
Py_INCREF(meta->func_name);
|
|
Py_INCREF(meta->file_name);
|
|
PyTuple_SET_ITEM(tuple, 0, meta->func_name);
|
|
PyTuple_SET_ITEM(tuple, 1, meta->file_name);
|
|
PyTuple_SET_ITEM(tuple, 2, lineno);
|
|
|
|
*result = 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(
|
|
proc_handle_t *handle,
|
|
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();
|
|
return -1;
|
|
}
|
|
|
|
if (_Py_RemoteDebug_PagedReadRemoteMemory(handle, chunk_addr, current_size, this_chunk) < 0) {
|
|
PyMem_RawFree(this_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) {
|
|
this_chunk = PyMem_RawRealloc(this_chunk, actual_size);
|
|
if (!this_chunk) {
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
|
|
if (_Py_RemoteDebug_PagedReadRemoteMemory(handle, chunk_addr, actual_size, this_chunk) < 0) {
|
|
PyMem_RawFree(this_chunk);
|
|
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->handle, tstate_addr + unwinder->debug_offsets.thread_state.datastack_chunk, &chunk_addr)) {
|
|
return -1;
|
|
}
|
|
|
|
chunks = PyMem_RawMalloc(max_chunks * sizeof(StackChunkInfo));
|
|
if (!chunks) {
|
|
PyErr_NoMemory();
|
|
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();
|
|
goto error;
|
|
}
|
|
chunks = new_chunks;
|
|
}
|
|
|
|
// Process this chunk
|
|
if (process_single_stack_chunk(&unwinder->handle, chunk_addr, &chunks[count]) < 0) {
|
|
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) {
|
|
uintptr_t base = chunks->chunks[i].remote_addr + offsetof(_PyStackChunk, data);
|
|
size_t payload = chunks->chunks[i].size - offsetof(_PyStackChunk, data);
|
|
|
|
if (remote_ptr >= base && remote_ptr < base + payload) {
|
|
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) {
|
|
return -1;
|
|
}
|
|
|
|
char *frame = (char *)frame_ptr;
|
|
*previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
|
|
|
|
if (GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) >= FRAME_OWNED_BY_INTERPRETER ||
|
|
!GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable)) {
|
|
return 0;
|
|
}
|
|
|
|
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, GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable),
|
|
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
|
|
) {
|
|
uint64_t interpreter_state_list_head =
|
|
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) {
|
|
return -1;
|
|
}
|
|
|
|
if (address_of_interpreter_state == 0) {
|
|
PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
|
|
return -1;
|
|
}
|
|
|
|
*interpreter_state = address_of_interpreter_state;
|
|
|
|
if (all_threads) {
|
|
*tstate = 0;
|
|
return 0;
|
|
}
|
|
|
|
uintptr_t address_of_thread = address_of_interpreter_state +
|
|
unwinder->debug_offsets.interpreter_state.threads_main;
|
|
|
|
if (_Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
address_of_thread,
|
|
sizeof(void*),
|
|
tstate) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
find_running_frame(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t runtime_start_address,
|
|
uintptr_t *frame
|
|
) {
|
|
uint64_t interpreter_state_list_head =
|
|
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) {
|
|
return -1;
|
|
}
|
|
|
|
if (address_of_interpreter_state == 0) {
|
|
PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
|
|
return -1;
|
|
}
|
|
|
|
uintptr_t address_of_thread;
|
|
bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
address_of_interpreter_state +
|
|
unwinder->debug_offsets.interpreter_state.threads_main,
|
|
sizeof(void*),
|
|
&address_of_thread);
|
|
if (bytes_read < 0) {
|
|
return -1;
|
|
}
|
|
|
|
// No Python frames are available for us (can happen at tear-down).
|
|
if ((void*)address_of_thread != NULL) {
|
|
int err = read_ptr(
|
|
&unwinder->handle,
|
|
address_of_thread + unwinder->debug_offsets.thread_state.current_frame,
|
|
frame);
|
|
if (err) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
*frame = (uintptr_t)NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
find_running_task(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t *running_task_addr
|
|
) {
|
|
*running_task_addr = (uintptr_t)NULL;
|
|
|
|
uint64_t interpreter_state_list_head =
|
|
unwinder->debug_offsets.runtime_state.interpreters_head;
|
|
|
|
uintptr_t address_of_interpreter_state;
|
|
int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
unwinder->runtime_start_address + interpreter_state_list_head,
|
|
sizeof(void*),
|
|
&address_of_interpreter_state);
|
|
if (bytes_read < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (address_of_interpreter_state == 0) {
|
|
PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
|
|
return -1;
|
|
}
|
|
|
|
uintptr_t address_of_thread;
|
|
bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
address_of_interpreter_state +
|
|
unwinder->debug_offsets.interpreter_state.threads_head,
|
|
sizeof(void*),
|
|
&address_of_thread);
|
|
if (bytes_read < 0) {
|
|
return -1;
|
|
}
|
|
|
|
uintptr_t address_of_running_loop;
|
|
// No Python frames are available for us (can happen at tear-down).
|
|
if ((void*)address_of_thread == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
bytes_read = read_py_ptr(
|
|
&unwinder->handle,
|
|
address_of_thread
|
|
+ unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_loop,
|
|
&address_of_running_loop);
|
|
if (bytes_read == -1) {
|
|
return -1;
|
|
}
|
|
|
|
// no asyncio loop is now running
|
|
if ((void*)address_of_running_loop == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
int err = read_ptr(
|
|
&unwinder->handle,
|
|
address_of_thread
|
|
+ unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_task,
|
|
running_task_addr);
|
|
if (err) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
find_running_task_and_coro(
|
|
RemoteUnwinderObject *self,
|
|
uintptr_t *running_task_addr,
|
|
uintptr_t *running_coro_addr,
|
|
uintptr_t *running_task_code_obj
|
|
) {
|
|
*running_task_addr = (uintptr_t)NULL;
|
|
if (find_running_task(
|
|
self, running_task_addr) < 0) {
|
|
chain_exceptions(PyExc_RuntimeError, "Failed to find running task");
|
|
return -1;
|
|
}
|
|
|
|
if ((void*)*running_task_addr == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "No running task found");
|
|
return -1;
|
|
}
|
|
|
|
if (read_py_ptr(
|
|
&self->handle,
|
|
*running_task_addr + self->async_debug_offsets.asyncio_task_object.task_coro,
|
|
running_coro_addr) < 0) {
|
|
chain_exceptions(PyExc_RuntimeError, "Failed to read running task coro");
|
|
return -1;
|
|
}
|
|
|
|
if ((void*)*running_coro_addr == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "Running task coro is NULL");
|
|
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(
|
|
&self->handle,
|
|
*running_coro_addr + self->debug_offsets.gen_object.gi_iframe,
|
|
running_task_code_obj) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if ((void*)*running_task_code_obj == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ============================================================================
|
|
* FRAME PARSING FUNCTIONS
|
|
* ============================================================================ */
|
|
|
|
static int
|
|
parse_frame_object(
|
|
RemoteUnwinderObject *unwinder,
|
|
PyObject** result,
|
|
uintptr_t address,
|
|
uintptr_t* previous_frame
|
|
) {
|
|
char frame[SIZEOF_INTERP_FRAME];
|
|
|
|
Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
address,
|
|
SIZEOF_INTERP_FRAME,
|
|
frame
|
|
);
|
|
if (bytes_read < 0) {
|
|
return -1;
|
|
}
|
|
|
|
*previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
|
|
|
|
if (GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) >= FRAME_OWNED_BY_INTERPRETER) {
|
|
return 0;
|
|
}
|
|
|
|
if ((void*)GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable) == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
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, GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable),
|
|
instruction_pointer, previous_frame, tlbc_index);
|
|
}
|
|
|
|
static int
|
|
parse_async_frame_object(
|
|
RemoteUnwinderObject *unwinder,
|
|
PyObject** result,
|
|
uintptr_t address,
|
|
uintptr_t* previous_frame,
|
|
uintptr_t* code_object
|
|
) {
|
|
char frame[SIZEOF_INTERP_FRAME];
|
|
|
|
Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
address,
|
|
SIZEOF_INTERP_FRAME,
|
|
frame
|
|
);
|
|
if (bytes_read < 0) {
|
|
return -1;
|
|
}
|
|
|
|
*previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
|
|
|
|
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));
|
|
return -1;
|
|
}
|
|
|
|
*code_object = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable);
|
|
// Strip tag bits for consistent comparison
|
|
*code_object &= ~Py_TAG_BITS;
|
|
|
|
assert(code_object != NULL);
|
|
if ((void*)*code_object == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
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
|
|
|
|
if (parse_code_object(
|
|
unwinder, result, *code_object, instruction_pointer, previous_frame, tlbc_index)) {
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
parse_async_frame_chain(
|
|
RemoteUnwinderObject *self,
|
|
PyObject *calls,
|
|
uintptr_t running_task_code_obj
|
|
) {
|
|
uintptr_t address_of_current_frame;
|
|
if (find_running_frame(self, self->runtime_start_address, &address_of_current_frame) < 0) {
|
|
chain_exceptions(PyExc_RuntimeError, "Failed to find running frame");
|
|
return -1;
|
|
}
|
|
|
|
uintptr_t address_of_code_object;
|
|
while ((void*)address_of_current_frame != NULL) {
|
|
PyObject* frame_info = NULL;
|
|
int res = parse_async_frame_object(
|
|
self,
|
|
&frame_info,
|
|
address_of_current_frame,
|
|
&address_of_current_frame,
|
|
&address_of_code_object
|
|
);
|
|
|
|
if (res < 0) {
|
|
chain_exceptions(PyExc_RuntimeError, "Failed to parse async frame object");
|
|
return -1;
|
|
}
|
|
|
|
if (!frame_info) {
|
|
continue;
|
|
}
|
|
|
|
if (PyList_Append(calls, frame_info) == -1) {
|
|
Py_DECREF(frame_info);
|
|
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) {
|
|
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");
|
|
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");
|
|
return -1;
|
|
}
|
|
|
|
uintptr_t task_addr = (uintptr_t)GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next)
|
|
- unwinder->async_debug_offsets.asyncio_task_object.task_node;
|
|
|
|
if (process_single_task_node(unwinder, task_addr, result) < 0) {
|
|
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) {
|
|
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) {
|
|
return -1;
|
|
}
|
|
|
|
PyObject *result_item = PyTuple_New(2);
|
|
if (result_item == NULL) {
|
|
Py_DECREF(tid_py);
|
|
return -1;
|
|
}
|
|
|
|
PyObject* awaited_by_for_thread = PyList_New(0);
|
|
if (awaited_by_for_thread == NULL) {
|
|
Py_DECREF(tid_py);
|
|
Py_DECREF(result_item);
|
|
return -1;
|
|
}
|
|
|
|
PyTuple_SET_ITEM(result_item, 0, tid_py); // steals ref
|
|
PyTuple_SET_ITEM(result_item, 1, awaited_by_for_thread); // steals ref
|
|
if (PyList_Append(result, result_item)) {
|
|
Py_DECREF(result_item);
|
|
return -1;
|
|
}
|
|
Py_DECREF(result_item);
|
|
|
|
if (append_awaited_by_for_thread(unwinder, head_addr, 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;
|
|
const size_t MAX_FRAMES = 1024;
|
|
size_t frame_count = 0;
|
|
|
|
while ((void*)frame_addr != NULL) {
|
|
PyObject *frame = NULL;
|
|
uintptr_t next_frame_addr = 0;
|
|
|
|
if (++frame_count > MAX_FRAMES) {
|
|
PyErr_SetString(PyExc_RuntimeError, "Too many stack frames (possible infinite loop)");
|
|
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();
|
|
if (parse_frame_object(unwinder, &frame, frame_addr, &next_frame_addr) < 0) {
|
|
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);
|
|
return -1;
|
|
}
|
|
|
|
if (PyList_Append(frame_info, frame) == -1) {
|
|
Py_DECREF(frame);
|
|
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, unwinder->debug_offsets.thread_state.size, ts);
|
|
if (bytes_read < 0) {
|
|
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) {
|
|
goto error;
|
|
}
|
|
|
|
if (copy_stack_chunks(unwinder, *current_tstate, &chunks) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (process_frame_chain(unwinder, frame_addr, &chunks, frame_info) < 0) {
|
|
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) {
|
|
goto error;
|
|
}
|
|
|
|
result = PyTuple_New(2);
|
|
if (result == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
PyTuple_SET_ITEM(result, 0, thread_id); // Steals reference
|
|
PyTuple_SET_ITEM(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
|
|
|
|
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.
|
|
|
|
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
|
|
[clinic start generated code]*/
|
|
|
|
static int
|
|
_remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
|
|
int pid, int all_threads)
|
|
/*[clinic end generated code: output=b8027cb247092081 input=6a2056b04e6f050e]*/
|
|
{
|
|
if (_Py_RemoteDebug_InitProcHandle(&self->handle, pid) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
self->runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(&self->handle);
|
|
if (self->runtime_start_address == 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (_Py_RemoteDebug_ReadDebugOffsets(&self->handle,
|
|
&self->runtime_start_address,
|
|
&self->debug_offsets) < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// Try to read async debug offsets, but don't fail if they're not available
|
|
self->async_debug_offsets_available = 1;
|
|
if (read_async_debug(self) < 0) {
|
|
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)
|
|
{
|
|
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();
|
|
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) {
|
|
_Py_hashtable_destroy(self->code_object_cache);
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*[clinic input]
|
|
@critical_section
|
|
_remote_debugging.RemoteUnwinder.get_stack_trace
|
|
|
|
Returns a list of stack traces for all 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
|
|
|
|
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=331dbe370578badf]*/
|
|
{
|
|
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) {
|
|
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);
|
|
}
|
|
|
|
#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->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) {
|
|
goto exit;
|
|
}
|
|
|
|
while (current_tstate != 0) {
|
|
PyObject* frame_info = unwind_stack_for_thread(self, ¤t_tstate);
|
|
if (!frame_info) {
|
|
Py_CLEAR(result);
|
|
goto exit;
|
|
}
|
|
|
|
if (PyList_Append(result, frame_info) == -1) {
|
|
Py_DECREF(frame_info);
|
|
Py_CLEAR(result);
|
|
goto exit;
|
|
}
|
|
Py_DECREF(frame_info);
|
|
|
|
// We are targeting a single tstate, break here
|
|
if (self->tstate_addr) {
|
|
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");
|
|
return NULL;
|
|
}
|
|
|
|
PyObject *result = PyList_New(0);
|
|
if (result == NULL) {
|
|
goto result_err;
|
|
}
|
|
|
|
uintptr_t thread_state_addr;
|
|
unsigned long tid = 0;
|
|
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&self->handle,
|
|
self->interpreter_addr
|
|
+ self->debug_offsets.interpreter_state.threads_main,
|
|
sizeof(void*),
|
|
&thread_state_addr))
|
|
{
|
|
goto result_err;
|
|
}
|
|
|
|
uintptr_t head_addr;
|
|
while (thread_state_addr != 0) {
|
|
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&self->handle,
|
|
thread_state_addr
|
|
+ self->debug_offsets.thread_state.native_thread_id,
|
|
sizeof(tid),
|
|
&tid))
|
|
{
|
|
goto result_err;
|
|
}
|
|
|
|
head_addr = thread_state_addr
|
|
+ self->async_debug_offsets.asyncio_thread_state.asyncio_tasks_head;
|
|
|
|
if (append_awaited_by(self, tid, head_addr, result))
|
|
{
|
|
goto result_err;
|
|
}
|
|
|
|
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&self->handle,
|
|
thread_state_addr + self->debug_offsets.thread_state.next,
|
|
sizeof(void*),
|
|
&thread_state_addr))
|
|
{
|
|
goto result_err;
|
|
}
|
|
}
|
|
|
|
head_addr = self->interpreter_addr
|
|
+ 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))
|
|
{
|
|
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
|
|
|
|
Returns information about the currently running async task and its stack trace.
|
|
|
|
Returns a tuple of (task_info, stack_frames) where:
|
|
- task_info is a tuple of (task_id, task_name) identifying the task
|
|
- stack_frames is a list of tuples (function_name, filename, line_number) representing
|
|
the Python stack frames for the task, ordered from most recent to oldest
|
|
|
|
Example:
|
|
((4345585712, 'Task-1'), [
|
|
('run_echo_server', 'server.py', 127),
|
|
('serve_forever', 'server.py', 45),
|
|
('main', 'app.py', 23)
|
|
])
|
|
|
|
Raises:
|
|
RuntimeError: If AsyncioDebug section is not available in the target process
|
|
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_async_stack_trace_impl(RemoteUnwinderObject *self)
|
|
/*[clinic end generated code: output=6433d52b55e87bbe input=11b7150c59d4c60f]*/
|
|
{
|
|
if (!self->async_debug_offsets_available) {
|
|
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
|
|
return NULL;
|
|
}
|
|
|
|
PyObject *result = NULL;
|
|
PyObject *calls = NULL;
|
|
|
|
if (setup_async_result_structure(&result, &calls) < 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
uintptr_t running_task_addr, running_coro_addr, running_task_code_obj;
|
|
if (find_running_task_and_coro(self, &running_task_addr,
|
|
&running_coro_addr, &running_task_code_obj) < 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (parse_async_frame_chain(self, calls, running_task_code_obj) < 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (add_task_info_to_result(self, result, running_task_addr) < 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
_Py_RemoteDebug_ClearCache(&self->handle);
|
|
return result;
|
|
|
|
cleanup:
|
|
_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(RemoteUnwinderObject *self)
|
|
{
|
|
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;
|
|
}
|
|
#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);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_debugging_clear(PyObject *mod)
|
|
{
|
|
RemoteDebuggingState *state = RemoteDebugging_GetState(mod);
|
|
Py_CLEAR(state->RemoteDebugging_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);
|
|
}
|
|
|