| #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_CLEAR(result); |
| goto result_err; |
| } |
| |
| if (!frame_info) { |
| continue; |
| } |
| |
| if (PyList_Append(result, frame_info) == -1) { |
| Py_CLEAR(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 pid"}, |
| {"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; |
| } |