#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;
}
