diff options
Diffstat (limited to 'Modules/_remote_debugging_module.c')
-rw-r--r-- | Modules/_remote_debugging_module.c | 1800 |
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; +} |