aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Modules/_remote_debugging_module.c
diff options
context:
space:
mode:
Diffstat (limited to 'Modules/_remote_debugging_module.c')
-rw-r--r--Modules/_remote_debugging_module.c1800
1 files changed, 1800 insertions, 0 deletions
diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c
new file mode 100644
index 00000000000..9314ddd9bed
--- /dev/null
+++ b/Modules/_remote_debugging_module.c
@@ -0,0 +1,1800 @@
+#define _GNU_SOURCE
+
+#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
+
+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;
+};
+
+// 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);
+}
+
+// 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");
+#elif defined(__linux__)
+ // On Linux, search for asyncio debug in executable or DLL
+ address = search_linux_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython");
+#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");
+ }
+#else
+ Py_UNREACHABLE();
+#endif
+
+ return address;
+}
+
+static inline int
+read_ptr(proc_handle_t *handle, uintptr_t address, uintptr_t *ptr_addr)
+{
+ int result = _Py_RemoteDebug_ReadRemoteMemory(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_ReadRemoteMemory(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_ReadRemoteMemory(handle, address, sizeof(char), result);
+ if (res < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+read_sized_int(proc_handle_t *handle, uintptr_t address, void *result, size_t size)
+{
+ int res = _Py_RemoteDebug_ReadRemoteMemory(handle, address, size, result);
+ if (res < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+read_unsigned_long(proc_handle_t *handle, uintptr_t address, unsigned long *result)
+{
+ int res = _Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(unsigned long), result);
+ if (res < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+read_pyobj(proc_handle_t *handle, uintptr_t address, PyObject *ptr_addr)
+{
+ int res = _Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(PyObject), ptr_addr);
+ if (res < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *
+read_py_str(
+ proc_handle_t *handle,
+ _Py_DebugOffsets* debug_offsets,
+ uintptr_t address,
+ Py_ssize_t max_len
+) {
+ PyObject *result = NULL;
+ char *buf = NULL;
+
+ Py_ssize_t len;
+ int res = _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ address + debug_offsets->unicode_object.length,
+ sizeof(Py_ssize_t),
+ &len
+ );
+ if (res < 0) {
+ goto err;
+ }
+
+ buf = (char *)PyMem_RawMalloc(len+1);
+ if (buf == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ size_t offset = debug_offsets->unicode_object.asciiobject_size;
+ res = _Py_RemoteDebug_ReadRemoteMemory(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(
+ proc_handle_t *handle,
+ _Py_DebugOffsets* debug_offsets,
+ uintptr_t address
+) {
+ PyObject *result = NULL;
+ char *buf = NULL;
+
+ Py_ssize_t len;
+ int res = _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ address + debug_offsets->bytes_object.ob_size,
+ sizeof(Py_ssize_t),
+ &len
+ );
+ if (res < 0) {
+ goto err;
+ }
+
+ buf = (char *)PyMem_RawMalloc(len+1);
+ if (buf == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ size_t offset = debug_offsets->bytes_object.ob_sval;
+ res = _Py_RemoteDebug_ReadRemoteMemory(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(proc_handle_t *handle, _Py_DebugOffsets* offsets, uintptr_t address)
+{
+ unsigned int shift = PYLONG_BITS_IN_DIGIT;
+
+ Py_ssize_t size;
+ uintptr_t lv_tag;
+
+ int bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
+ handle, address + offsets->long_object.lv_tag,
+ sizeof(uintptr_t),
+ &lv_tag);
+ if (bytes_read < 0) {
+ return -1;
+ }
+
+ int negative = (lv_tag & 3) == 2;
+ size = lv_tag >> 3;
+
+ if (size == 0) {
+ return 0;
+ }
+
+ digit *digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
+ if (!digits) {
+ PyErr_NoMemory();
+ return -1;
+ }
+
+ bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ address + 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;
+}
+
+static PyObject *
+parse_task_name(
+ proc_handle_t *handle,
+ _Py_DebugOffsets* offsets,
+ struct _Py_AsyncioModuleDebugOffsets* async_offsets,
+ uintptr_t task_address
+) {
+ uintptr_t task_name_addr;
+ int err = read_py_ptr(
+ handle,
+ task_address + async_offsets->asyncio_task_object.task_name,
+ &task_name_addr);
+ if (err) {
+ return NULL;
+ }
+
+ // The task name can be a long or a string so we need to check the type
+
+ PyObject task_name_obj;
+ err = read_pyobj(
+ handle,
+ task_name_addr,
+ &task_name_obj);
+ if (err) {
+ return NULL;
+ }
+
+ unsigned long flags;
+ err = read_unsigned_long(
+ handle,
+ (uintptr_t)task_name_obj.ob_type + offsets->type_object.tp_flags,
+ &flags);
+ if (err) {
+ return NULL;
+ }
+
+ if ((flags & Py_TPFLAGS_LONG_SUBCLASS)) {
+ long res = read_py_long(handle, offsets, task_name_addr);
+ if (res == -1) {
+ chain_exceptions(PyExc_RuntimeError, "Failed to get task name");
+ return NULL;
+ }
+ return PyUnicode_FromFormat("Task-%d", res);
+ }
+
+ if(!(flags & Py_TPFLAGS_UNICODE_SUBCLASS)) {
+ PyErr_SetString(PyExc_RuntimeError, "Invalid task name object");
+ return NULL;
+ }
+
+ return read_py_str(
+ handle,
+ offsets,
+ task_name_addr,
+ 255
+ );
+}
+
+static int
+parse_frame_object(
+ proc_handle_t *handle,
+ PyObject** result,
+ struct _Py_DebugOffsets* offsets,
+ uintptr_t address,
+ uintptr_t* previous_frame
+);
+
+static int
+parse_coro_chain(
+ proc_handle_t *handle,
+ struct _Py_DebugOffsets* offsets,
+ struct _Py_AsyncioModuleDebugOffsets* async_offsets,
+ uintptr_t coro_address,
+ PyObject *render_to
+) {
+ assert((void*)coro_address != NULL);
+
+ uintptr_t gen_type_addr;
+ int err = read_ptr(
+ handle,
+ coro_address + offsets->pyobject.ob_type,
+ &gen_type_addr);
+ if (err) {
+ return -1;
+ }
+
+ PyObject* name = NULL;
+ uintptr_t prev_frame;
+ if (parse_frame_object(
+ handle,
+ &name,
+ offsets,
+ coro_address + offsets->gen_object.gi_iframe,
+ &prev_frame)
+ < 0)
+ {
+ return -1;
+ }
+
+ if (PyList_Append(render_to, name)) {
+ Py_DECREF(name);
+ return -1;
+ }
+ Py_DECREF(name);
+
+ int8_t gi_frame_state;
+ err = read_sized_int(
+ handle,
+ coro_address + offsets->gen_object.gi_frame_state,
+ &gi_frame_state,
+ sizeof(int8_t)
+ );
+ if (err) {
+ return -1;
+ }
+
+ if (gi_frame_state == FRAME_SUSPENDED_YIELD_FROM) {
+ char owner;
+ err = read_char(
+ handle,
+ coro_address + offsets->gen_object.gi_iframe +
+ offsets->interpreter_frame.owner,
+ &owner
+ );
+ if (err) {
+ return -1;
+ }
+ if (owner != FRAME_OWNED_BY_GENERATOR) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "generator doesn't own its frame \\_o_/");
+ return -1;
+ }
+
+ uintptr_t stackpointer_addr;
+ err = read_py_ptr(
+ handle,
+ coro_address + offsets->gen_object.gi_iframe +
+ offsets->interpreter_frame.stackpointer,
+ &stackpointer_addr);
+ if (err) {
+ return -1;
+ }
+
+ if ((void*)stackpointer_addr != NULL) {
+ uintptr_t gi_await_addr;
+ err = read_py_ptr(
+ 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;
+ int err = read_ptr(
+ handle,
+ gi_await_addr + 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(
+ handle,
+ offsets,
+ async_offsets,
+ gi_await_addr,
+ render_to
+ );
+ if (err) {
+ return -1;
+ }
+ }
+ }
+ }
+
+ }
+
+ return 0;
+}
+
+
+static int
+parse_task_awaited_by(
+ proc_handle_t *handle,
+ struct _Py_DebugOffsets* offsets,
+ struct _Py_AsyncioModuleDebugOffsets* async_offsets,
+ uintptr_t task_address,
+ PyObject *awaited_by,
+ int recurse_task
+);
+
+
+static int
+parse_task(
+ proc_handle_t *handle,
+ struct _Py_DebugOffsets* offsets,
+ struct _Py_AsyncioModuleDebugOffsets* async_offsets,
+ uintptr_t task_address,
+ PyObject *render_to,
+ int recurse_task
+) {
+ char is_task;
+ int err = read_char(
+ handle,
+ task_address + async_offsets->asyncio_task_object.task_is_task,
+ &is_task);
+ if (err) {
+ return -1;
+ }
+
+ PyObject* result = PyList_New(0);
+ if (result == NULL) {
+ return -1;
+ }
+
+ PyObject *call_stack = PyList_New(0);
+ if (call_stack == NULL) {
+ goto err;
+ }
+ if (PyList_Append(result, call_stack)) {
+ Py_DECREF(call_stack);
+ goto err;
+ }
+ /* we can operate on a borrowed one to simplify cleanup */
+ Py_DECREF(call_stack);
+
+ if (is_task) {
+ PyObject *tn = NULL;
+ if (recurse_task) {
+ tn = parse_task_name(
+ handle, offsets, async_offsets, task_address);
+ } else {
+ tn = PyLong_FromUnsignedLongLong(task_address);
+ }
+ if (tn == NULL) {
+ goto err;
+ }
+ if (PyList_Append(result, tn)) {
+ Py_DECREF(tn);
+ goto err;
+ }
+ Py_DECREF(tn);
+
+ uintptr_t coro_addr;
+ err = read_py_ptr(
+ handle,
+ task_address + async_offsets->asyncio_task_object.task_coro,
+ &coro_addr);
+ if (err) {
+ goto err;
+ }
+
+ if ((void*)coro_addr != NULL) {
+ err = parse_coro_chain(
+ handle,
+ offsets,
+ async_offsets,
+ coro_addr,
+ call_stack
+ );
+ if (err) {
+ goto err;
+ }
+
+ if (PyList_Reverse(call_stack)) {
+ goto err;
+ }
+ }
+ }
+
+ if (PyList_Append(render_to, result)) {
+ goto err;
+ }
+
+ if (recurse_task) {
+ PyObject *awaited_by = PyList_New(0);
+ if (awaited_by == NULL) {
+ goto err;
+ }
+ if (PyList_Append(result, awaited_by)) {
+ Py_DECREF(awaited_by);
+ goto err;
+ }
+ /* we can operate on a borrowed one to simplify cleanup */
+ Py_DECREF(awaited_by);
+
+ if (parse_task_awaited_by(handle, offsets, async_offsets,
+ task_address, awaited_by, 1)
+ ) {
+ goto err;
+ }
+ }
+ Py_DECREF(result);
+
+ return 0;
+
+err:
+ Py_DECREF(result);
+ return -1;
+}
+
+static int
+parse_tasks_in_set(
+ proc_handle_t *handle,
+ struct _Py_DebugOffsets* offsets,
+ struct _Py_AsyncioModuleDebugOffsets* async_offsets,
+ uintptr_t set_addr,
+ PyObject *awaited_by,
+ int recurse_task
+) {
+ uintptr_t set_obj;
+ if (read_py_ptr(
+ handle,
+ set_addr,
+ &set_obj)
+ ) {
+ return -1;
+ }
+
+ Py_ssize_t num_els;
+ if (read_Py_ssize_t(
+ handle,
+ set_obj + offsets->set_object.used,
+ &num_els)
+ ) {
+ return -1;
+ }
+
+ Py_ssize_t set_len;
+ if (read_Py_ssize_t(
+ handle,
+ set_obj + offsets->set_object.mask,
+ &set_len)
+ ) {
+ return -1;
+ }
+ set_len++; // The set contains the `mask+1` element slots.
+
+ uintptr_t table_ptr;
+ if (read_ptr(
+ handle,
+ set_obj + offsets->set_object.table,
+ &table_ptr)
+ ) {
+ return -1;
+ }
+
+ Py_ssize_t i = 0;
+ Py_ssize_t els = 0;
+ while (i < set_len) {
+ uintptr_t key_addr;
+ if (read_py_ptr(handle, table_ptr, &key_addr)) {
+ return -1;
+ }
+
+ if ((void*)key_addr != NULL) {
+ Py_ssize_t ref_cnt;
+ if (read_Py_ssize_t(handle, table_ptr, &ref_cnt)) {
+ return -1;
+ }
+
+ if (ref_cnt) {
+ // if 'ref_cnt=0' it's a set dummy marker
+
+ if (parse_task(
+ handle,
+ offsets,
+ async_offsets,
+ key_addr,
+ awaited_by,
+ recurse_task
+ )
+ ) {
+ return -1;
+ }
+
+ if (++els == num_els) {
+ break;
+ }
+ }
+ }
+
+ table_ptr += sizeof(void*) * 2;
+ i++;
+ }
+ return 0;
+}
+
+
+static int
+parse_task_awaited_by(
+ proc_handle_t *handle,
+ struct _Py_DebugOffsets* offsets,
+ struct _Py_AsyncioModuleDebugOffsets* async_offsets,
+ uintptr_t task_address,
+ PyObject *awaited_by,
+ int recurse_task
+) {
+ uintptr_t task_ab_addr;
+ int err = read_py_ptr(
+ handle,
+ task_address + async_offsets->asyncio_task_object.task_awaited_by,
+ &task_ab_addr);
+ if (err) {
+ return -1;
+ }
+
+ if ((void*)task_ab_addr == NULL) {
+ return 0;
+ }
+
+ char awaited_by_is_a_set;
+ err = read_char(
+ handle,
+ task_address + async_offsets->asyncio_task_object.task_awaited_by_is_set,
+ &awaited_by_is_a_set);
+ if (err) {
+ return -1;
+ }
+
+ if (awaited_by_is_a_set) {
+ if (parse_tasks_in_set(
+ handle,
+ offsets,
+ async_offsets,
+ task_address + async_offsets->asyncio_task_object.task_awaited_by,
+ awaited_by,
+ recurse_task
+ )
+ ) {
+ return -1;
+ }
+ } else {
+ uintptr_t sub_task;
+ if (read_py_ptr(
+ handle,
+ task_address + async_offsets->asyncio_task_object.task_awaited_by,
+ &sub_task)
+ ) {
+ return -1;
+ }
+
+ if (parse_task(
+ handle,
+ offsets,
+ async_offsets,
+ sub_task,
+ awaited_by,
+ recurse_task
+ )
+ ) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+typedef struct
+{
+ int lineno;
+ int end_lineno;
+ int column;
+ int end_column;
+} LocationInfo;
+
+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++);
+ assert((second_byte & 128) == 0);
+ 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;
+}
+
+static int
+read_remote_pointer(proc_handle_t *handle, uintptr_t address, uintptr_t *out_ptr, const char *error_message)
+{
+ int bytes_read = _Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(void *), out_ptr);
+ if (bytes_read < 0) {
+ return -1;
+ }
+
+ if ((void *)(*out_ptr) == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, error_message);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+read_instruction_ptr(proc_handle_t *handle, struct _Py_DebugOffsets *offsets,
+ uintptr_t current_frame, uintptr_t *instruction_ptr)
+{
+ return read_remote_pointer(
+ handle,
+ current_frame + offsets->interpreter_frame.instr_ptr,
+ instruction_ptr,
+ "No instruction ptr found"
+ );
+}
+
+static int
+parse_code_object(proc_handle_t *handle,
+ PyObject **result,
+ struct _Py_DebugOffsets *offsets,
+ uintptr_t address,
+ uintptr_t current_frame,
+ uintptr_t *previous_frame)
+{
+ uintptr_t addr_func_name, addr_file_name, addr_linetable, instruction_ptr;
+
+ if (read_remote_pointer(handle, address + offsets->code_object.qualname, &addr_func_name, "No function name found") < 0 ||
+ read_remote_pointer(handle, address + offsets->code_object.filename, &addr_file_name, "No file name found") < 0 ||
+ read_remote_pointer(handle, address + offsets->code_object.linetable, &addr_linetable, "No linetable found") < 0 ||
+ read_instruction_ptr(handle, offsets, current_frame, &instruction_ptr) < 0) {
+ return -1;
+ }
+
+ int firstlineno;
+ if (_Py_RemoteDebug_ReadRemoteMemory(handle,
+ address + offsets->code_object.firstlineno,
+ sizeof(int),
+ &firstlineno) < 0) {
+ return -1;
+ }
+
+ PyObject *py_linetable = read_py_bytes(handle, offsets, addr_linetable);
+ if (!py_linetable) {
+ return -1;
+ }
+
+ uintptr_t addr_code_adaptive = address + offsets->code_object.co_code_adaptive;
+ ptrdiff_t addrq = (uint16_t *)instruction_ptr - (uint16_t *)addr_code_adaptive;
+
+ LocationInfo info;
+ parse_linetable(addrq, PyBytes_AS_STRING(py_linetable), firstlineno, &info);
+ Py_DECREF(py_linetable); // Done with linetable
+
+ PyObject *py_line = PyLong_FromLong(info.lineno);
+ if (!py_line) {
+ return -1;
+ }
+
+ PyObject *py_func_name = read_py_str(handle, offsets, addr_func_name, 256);
+ if (!py_func_name) {
+ Py_DECREF(py_line);
+ return -1;
+ }
+
+ PyObject *py_file_name = read_py_str(handle, offsets, addr_file_name, 256);
+ if (!py_file_name) {
+ Py_DECREF(py_line);
+ Py_DECREF(py_func_name);
+ return -1;
+ }
+
+ PyObject *result_tuple = PyTuple_New(3);
+ if (!result_tuple) {
+ Py_DECREF(py_line);
+ Py_DECREF(py_func_name);
+ Py_DECREF(py_file_name);
+ return -1;
+ }
+
+ PyTuple_SET_ITEM(result_tuple, 0, py_func_name); // steals ref
+ PyTuple_SET_ITEM(result_tuple, 1, py_file_name); // steals ref
+ PyTuple_SET_ITEM(result_tuple, 2, py_line); // steals ref
+
+ *result = result_tuple;
+ return 0;
+}
+
+static int
+parse_frame_object(
+ proc_handle_t *handle,
+ PyObject** result,
+ struct _Py_DebugOffsets* offsets,
+ uintptr_t address,
+ uintptr_t* previous_frame
+) {
+ int err;
+
+ Py_ssize_t bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ address + offsets->interpreter_frame.previous,
+ sizeof(void*),
+ previous_frame
+ );
+ if (bytes_read < 0) {
+ return -1;
+ }
+
+ char owner;
+ if (read_char(handle, address + offsets->interpreter_frame.owner, &owner)) {
+ return -1;
+ }
+
+ if (owner >= FRAME_OWNED_BY_INTERPRETER) {
+ return 0;
+ }
+
+ uintptr_t address_of_code_object;
+ err = read_py_ptr(
+ handle,
+ address + offsets->interpreter_frame.executable,
+ &address_of_code_object
+ );
+ if (err) {
+ return -1;
+ }
+
+ if ((void*)address_of_code_object == NULL) {
+ return 0;
+ }
+
+ return parse_code_object(
+ handle, result, offsets, address_of_code_object, address, previous_frame);
+}
+
+static int
+parse_async_frame_object(
+ proc_handle_t *handle,
+ PyObject** result,
+ struct _Py_DebugOffsets* offsets,
+ uintptr_t address,
+ uintptr_t* previous_frame,
+ uintptr_t* code_object
+) {
+ int err;
+
+ Py_ssize_t bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ address + offsets->interpreter_frame.previous,
+ sizeof(void*),
+ previous_frame
+ );
+ if (bytes_read < 0) {
+ return -1;
+ }
+
+ char owner;
+ bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
+ handle, address + offsets->interpreter_frame.owner, sizeof(char), &owner);
+ if (bytes_read < 0) {
+ return -1;
+ }
+
+ if (owner == FRAME_OWNED_BY_CSTACK || owner == FRAME_OWNED_BY_INTERPRETER) {
+ return 0; // C frame
+ }
+
+ if (owner != FRAME_OWNED_BY_GENERATOR
+ && owner != FRAME_OWNED_BY_THREAD) {
+ PyErr_Format(PyExc_RuntimeError, "Unhandled frame owner %d.\n", owner);
+ return -1;
+ }
+
+ err = read_py_ptr(
+ handle,
+ address + offsets->interpreter_frame.executable,
+ code_object
+ );
+ if (err) {
+ return -1;
+ }
+
+ assert(code_object != NULL);
+ if ((void*)*code_object == NULL) {
+ return 0;
+ }
+
+ if (parse_code_object(
+ handle, result, offsets, *code_object, address, previous_frame)) {
+ return -1;
+ }
+
+ return 1;
+}
+
+static int
+read_async_debug(
+ proc_handle_t *handle,
+ struct _Py_AsyncioModuleDebugOffsets* async_debug
+) {
+ uintptr_t async_debug_addr = _Py_RemoteDebug_GetAsyncioDebugAddress(handle);
+ if (!async_debug_addr) {
+ return -1;
+ }
+
+ size_t size = sizeof(struct _Py_AsyncioModuleDebugOffsets);
+ int result = _Py_RemoteDebug_ReadRemoteMemory(handle, async_debug_addr, size, async_debug);
+ return result;
+}
+
+static int
+find_running_frame(
+ proc_handle_t *handle,
+ uintptr_t runtime_start_address,
+ _Py_DebugOffsets* local_debug_offsets,
+ uintptr_t *frame
+) {
+ uint64_t interpreter_state_list_head =
+ local_debug_offsets->runtime_state.interpreters_head;
+
+ uintptr_t address_of_interpreter_state;
+ int bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
+ 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_ReadRemoteMemory(
+ handle,
+ address_of_interpreter_state +
+ local_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(
+ handle,
+ address_of_thread + local_debug_offsets->thread_state.current_frame,
+ frame);
+ if (err) {
+ return -1;
+ }
+ return 0;
+ }
+
+ *frame = (uintptr_t)NULL;
+ return 0;
+}
+
+static int
+find_running_task(
+ proc_handle_t *handle,
+ uintptr_t runtime_start_address,
+ _Py_DebugOffsets *local_debug_offsets,
+ struct _Py_AsyncioModuleDebugOffsets *async_offsets,
+ uintptr_t *running_task_addr
+) {
+ *running_task_addr = (uintptr_t)NULL;
+
+ uint64_t interpreter_state_list_head =
+ local_debug_offsets->runtime_state.interpreters_head;
+
+ uintptr_t address_of_interpreter_state;
+ int bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
+ 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_ReadRemoteMemory(
+ handle,
+ address_of_interpreter_state +
+ local_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(
+ handle,
+ address_of_thread
+ + async_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(
+ handle,
+ address_of_thread
+ + async_offsets->asyncio_thread_state.asyncio_running_task,
+ running_task_addr);
+ if (err) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+append_awaited_by_for_thread(
+ proc_handle_t *handle,
+ uintptr_t head_addr,
+ struct _Py_DebugOffsets *debug_offsets,
+ struct _Py_AsyncioModuleDebugOffsets *async_offsets,
+ PyObject *result
+) {
+ struct llist_node task_node;
+
+ if (0 > _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ head_addr,
+ sizeof(task_node),
+ &task_node))
+ {
+ return -1;
+ }
+
+ size_t iteration_count = 0;
+ const size_t MAX_ITERATIONS = 2 << 15; // A reasonable upper bound
+ while ((uintptr_t)task_node.next != head_addr) {
+ if (++iteration_count > MAX_ITERATIONS) {
+ PyErr_SetString(PyExc_RuntimeError, "Task list appears corrupted");
+ return -1;
+ }
+
+ if (task_node.next == NULL) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "Invalid linked list structure reading remote memory");
+ return -1;
+ }
+
+ uintptr_t task_addr = (uintptr_t)task_node.next
+ - async_offsets->asyncio_task_object.task_node;
+
+ PyObject *tn = parse_task_name(
+ handle,
+ debug_offsets,
+ async_offsets,
+ task_addr);
+ if (tn == NULL) {
+ return -1;
+ }
+
+ PyObject *current_awaited_by = PyList_New(0);
+ if (current_awaited_by == NULL) {
+ Py_DECREF(tn);
+ return -1;
+ }
+
+ PyObject* task_id = PyLong_FromUnsignedLongLong(task_addr);
+ if (task_id == NULL) {
+ Py_DECREF(tn);
+ Py_DECREF(current_awaited_by);
+ return -1;
+ }
+
+ PyObject *result_item = PyTuple_New(3);
+ if (result_item == NULL) {
+ Py_DECREF(tn);
+ Py_DECREF(current_awaited_by);
+ Py_DECREF(task_id);
+ return -1;
+ }
+
+ 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
+ if (PyList_Append(result, result_item)) {
+ Py_DECREF(result_item);
+ return -1;
+ }
+ Py_DECREF(result_item);
+
+ if (parse_task_awaited_by(handle, debug_offsets, async_offsets,
+ task_addr, current_awaited_by, 0))
+ {
+ return -1;
+ }
+
+ // onto the next one...
+ if (0 > _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ (uintptr_t)task_node.next,
+ sizeof(task_node),
+ &task_node))
+ {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+append_awaited_by(
+ proc_handle_t *handle,
+ unsigned long tid,
+ uintptr_t head_addr,
+ struct _Py_DebugOffsets *debug_offsets,
+ struct _Py_AsyncioModuleDebugOffsets *async_offsets,
+ 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(
+ handle,
+ head_addr,
+ debug_offsets,
+ async_offsets,
+ awaited_by_for_thread))
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+static PyObject*
+get_all_awaited_by(PyObject* self, PyObject* args)
+{
+#if (!defined(__linux__) && !defined(__APPLE__)) && !defined(MS_WINDOWS) || \
+ (defined(__linux__) && !HAVE_PROCESS_VM_READV)
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "get_all_awaited_by is not implemented on this platform");
+ return NULL;
+#endif
+
+ int pid;
+ if (!PyArg_ParseTuple(args, "i", &pid)) {
+ return NULL;
+ }
+
+ proc_handle_t the_handle;
+ proc_handle_t *handle = &the_handle;
+ if (_Py_RemoteDebug_InitProcHandle(handle, pid) < 0) {
+ return 0;
+ }
+
+ PyObject *result = NULL;
+
+ uintptr_t runtime_start_addr = _Py_RemoteDebug_GetPyRuntimeAddress(handle);
+ if (runtime_start_addr == 0) {
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(
+ PyExc_RuntimeError, "Failed to get .PyRuntime address");
+ }
+ goto result_err;
+ }
+ struct _Py_DebugOffsets local_debug_offsets;
+
+ if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_addr, &local_debug_offsets)) {
+ chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets");
+ goto result_err;
+ }
+
+ struct _Py_AsyncioModuleDebugOffsets local_async_debug;
+ if (read_async_debug(handle, &local_async_debug)) {
+ chain_exceptions(PyExc_RuntimeError, "Failed to read asyncio debug offsets");
+ goto result_err;
+ }
+
+ result = PyList_New(0);
+ if (result == NULL) {
+ goto result_err;
+ }
+
+ uint64_t interpreter_state_list_head =
+ local_debug_offsets.runtime_state.interpreters_head;
+
+ uintptr_t interpreter_state_addr;
+ if (0 > _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ runtime_start_addr + interpreter_state_list_head,
+ sizeof(void*),
+ &interpreter_state_addr))
+ {
+ goto result_err;
+ }
+
+ uintptr_t thread_state_addr;
+ unsigned long tid = 0;
+ if (0 > _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ interpreter_state_addr
+ + local_debug_offsets.interpreter_state.threads_head,
+ sizeof(void*),
+ &thread_state_addr))
+ {
+ goto result_err;
+ }
+
+ uintptr_t head_addr;
+ while (thread_state_addr != 0) {
+ if (0 > _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ thread_state_addr
+ + local_debug_offsets.thread_state.native_thread_id,
+ sizeof(tid),
+ &tid))
+ {
+ goto result_err;
+ }
+
+ head_addr = thread_state_addr
+ + local_async_debug.asyncio_thread_state.asyncio_tasks_head;
+
+ if (append_awaited_by(handle, tid, head_addr, &local_debug_offsets,
+ &local_async_debug, result))
+ {
+ goto result_err;
+ }
+
+ if (0 > _Py_RemoteDebug_ReadRemoteMemory(
+ handle,
+ thread_state_addr + local_debug_offsets.thread_state.next,
+ sizeof(void*),
+ &thread_state_addr))
+ {
+ goto result_err;
+ }
+ }
+
+ head_addr = interpreter_state_addr
+ + local_async_debug.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(handle, 0, head_addr, &local_debug_offsets,
+ &local_async_debug, result))
+ {
+ goto result_err;
+ }
+
+ _Py_RemoteDebug_CleanupProcHandle(handle);
+ return result;
+
+result_err:
+ Py_XDECREF(result);
+ _Py_RemoteDebug_CleanupProcHandle(handle);
+ return NULL;
+}
+
+static PyObject*
+get_stack_trace(PyObject* self, PyObject* args)
+{
+#if (!defined(__linux__) && !defined(__APPLE__)) && !defined(MS_WINDOWS) || \
+ (defined(__linux__) && !HAVE_PROCESS_VM_READV)
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "get_stack_trace is not supported on this platform");
+ return NULL;
+#endif
+
+ int pid;
+ if (!PyArg_ParseTuple(args, "i", &pid)) {
+ return NULL;
+ }
+
+ proc_handle_t the_handle;
+ proc_handle_t *handle = &the_handle;
+ if (_Py_RemoteDebug_InitProcHandle(handle, pid) < 0) {
+ return 0;
+ }
+
+ PyObject* result = NULL;
+
+ uintptr_t runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle);
+ if (runtime_start_address == 0) {
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(
+ PyExc_RuntimeError, "Failed to get .PyRuntime address");
+ }
+ goto result_err;
+ }
+ struct _Py_DebugOffsets local_debug_offsets;
+
+ if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) {
+ chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets");
+ goto result_err;
+ }
+
+ uintptr_t address_of_current_frame;
+ if (find_running_frame(
+ handle, runtime_start_address, &local_debug_offsets,
+ &address_of_current_frame)
+ ) {
+ goto result_err;
+ }
+
+ result = PyList_New(0);
+ if (result == NULL) {
+ goto result_err;
+ }
+
+ while ((void*)address_of_current_frame != NULL) {
+ PyObject* frame_info = NULL;
+ if (parse_frame_object(
+ handle,
+ &frame_info,
+ &local_debug_offsets,
+ address_of_current_frame,
+ &address_of_current_frame)
+ < 0)
+ {
+ Py_DECREF(result);
+ goto result_err;
+ }
+
+ if (!frame_info) {
+ continue;
+ }
+
+ if (PyList_Append(result, frame_info) == -1) {
+ Py_DECREF(result);
+ goto result_err;
+ }
+
+ Py_DECREF(frame_info);
+ frame_info = NULL;
+
+ }
+
+result_err:
+ _Py_RemoteDebug_CleanupProcHandle(handle);
+ return result;
+}
+
+static PyObject*
+get_async_stack_trace(PyObject* self, PyObject* args)
+{
+#if (!defined(__linux__) && !defined(__APPLE__)) && !defined(MS_WINDOWS) || \
+ (defined(__linux__) && !HAVE_PROCESS_VM_READV)
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "get_stack_trace is not supported on this platform");
+ return NULL;
+#endif
+ int pid;
+
+ if (!PyArg_ParseTuple(args, "i", &pid)) {
+ return NULL;
+ }
+
+ proc_handle_t the_handle;
+ proc_handle_t *handle = &the_handle;
+ if (_Py_RemoteDebug_InitProcHandle(handle, pid) < 0) {
+ return 0;
+ }
+
+ PyObject *result = NULL;
+
+ uintptr_t runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle);
+ if (runtime_start_address == 0) {
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(
+ PyExc_RuntimeError, "Failed to get .PyRuntime address");
+ }
+ goto result_err;
+ }
+ struct _Py_DebugOffsets local_debug_offsets;
+
+ if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) {
+ chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets");
+ goto result_err;
+ }
+
+ struct _Py_AsyncioModuleDebugOffsets local_async_debug;
+ if (read_async_debug(handle, &local_async_debug)) {
+ chain_exceptions(PyExc_RuntimeError, "Failed to read asyncio debug offsets");
+ goto result_err;
+ }
+
+ result = PyList_New(1);
+ if (result == NULL) {
+ goto result_err;
+ }
+ PyObject* calls = PyList_New(0);
+ if (calls == NULL) {
+ goto result_err;
+ }
+ if (PyList_SetItem(result, 0, calls)) { /* steals ref to 'calls' */
+ Py_DECREF(calls);
+ goto result_err;
+ }
+
+ uintptr_t running_task_addr = (uintptr_t)NULL;
+ if (find_running_task(
+ handle, runtime_start_address, &local_debug_offsets, &local_async_debug,
+ &running_task_addr)
+ ) {
+ chain_exceptions(PyExc_RuntimeError, "Failed to find running task");
+ goto result_err;
+ }
+
+ if ((void*)running_task_addr == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "No running task found");
+ goto result_err;
+ }
+
+ uintptr_t running_coro_addr;
+ if (read_py_ptr(
+ handle,
+ running_task_addr + local_async_debug.asyncio_task_object.task_coro,
+ &running_coro_addr
+ )) {
+ chain_exceptions(PyExc_RuntimeError, "Failed to read running task coro");
+ goto result_err;
+ }
+
+ if ((void*)running_coro_addr == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "Running task coro is NULL");
+ goto result_err;
+ }
+
+ // note: genobject's gi_iframe is an embedded struct so the address to
+ // the offset leads directly to its first field: f_executable
+ uintptr_t address_of_running_task_code_obj;
+ if (read_py_ptr(
+ handle,
+ running_coro_addr + local_debug_offsets.gen_object.gi_iframe,
+ &address_of_running_task_code_obj
+ )) {
+ goto result_err;
+ }
+
+ if ((void*)address_of_running_task_code_obj == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL");
+ goto result_err;
+ }
+
+ uintptr_t address_of_current_frame;
+ if (find_running_frame(
+ handle, runtime_start_address, &local_debug_offsets,
+ &address_of_current_frame)
+ ) {
+ chain_exceptions(PyExc_RuntimeError, "Failed to find running frame");
+ goto result_err;
+ }
+
+ uintptr_t address_of_code_object;
+ while ((void*)address_of_current_frame != NULL) {
+ PyObject* frame_info = NULL;
+ int res = parse_async_frame_object(
+ handle,
+ &frame_info,
+ &local_debug_offsets,
+ 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");
+ goto result_err;
+ }
+
+ if (!frame_info) {
+ continue;
+ }
+
+ if (PyList_Append(calls, frame_info) == -1) {
+ Py_DECREF(calls);
+ goto result_err;
+ }
+
+ Py_DECREF(frame_info);
+ frame_info = NULL;
+
+ if (address_of_code_object == address_of_running_task_code_obj) {
+ break;
+ }
+ }
+
+ PyObject *tn = parse_task_name(
+ handle, &local_debug_offsets, &local_async_debug, running_task_addr);
+ if (tn == NULL) {
+ goto result_err;
+ }
+ if (PyList_Append(result, tn)) {
+ Py_DECREF(tn);
+ goto result_err;
+ }
+ Py_DECREF(tn);
+
+ PyObject* awaited_by = PyList_New(0);
+ if (awaited_by == NULL) {
+ goto result_err;
+ }
+ if (PyList_Append(result, awaited_by)) {
+ Py_DECREF(awaited_by);
+ goto result_err;
+ }
+ Py_DECREF(awaited_by);
+
+ if (parse_task_awaited_by(
+ handle, &local_debug_offsets, &local_async_debug,
+ running_task_addr, awaited_by, 1)
+ ) {
+ goto result_err;
+ }
+
+ _Py_RemoteDebug_CleanupProcHandle(handle);
+ return result;
+
+result_err:
+ _Py_RemoteDebug_CleanupProcHandle(handle);
+ Py_XDECREF(result);
+ return NULL;
+}
+
+
+static PyMethodDef methods[] = {
+ {"get_stack_trace", get_stack_trace, METH_VARARGS,
+ "Get the Python stack from a given pod"},
+ {"get_async_stack_trace", get_async_stack_trace, METH_VARARGS,
+ "Get the asyncio stack from a given pid"},
+ {"get_all_awaited_by", get_all_awaited_by, METH_VARARGS,
+ "Get all tasks and their awaited_by from a given pid"},
+ {NULL, NULL, 0, NULL},
+};
+
+static struct PyModuleDef module = {
+ .m_base = PyModuleDef_HEAD_INIT,
+ .m_name = "_remote_debugging",
+ .m_size = -1,
+ .m_methods = methods,
+};
+
+PyMODINIT_FUNC
+PyInit__remote_debugging(void)
+{
+ PyObject* mod = PyModule_Create(&module);
+ if (mod == NULL) {
+ return NULL;
+ }
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED);
+#endif
+ int rc = PyModule_AddIntConstant(
+ mod, "PROCESS_VM_READV_SUPPORTED", HAVE_PROCESS_VM_READV);
+ if (rc < 0) {
+ Py_DECREF(mod);
+ return NULL;
+ }
+ return mod;
+}