/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   Stack trace tools
 * @license GPL-3.0-only
 */

#include <stdint.h>
#include <kernel.h>
#include <stddef.h>

extern struct init_status init;

/*
 * debug_stack_trace - Prints the stack trace
 * @max_frames: Maximum amount of stack frames to walk
 *
 * Walks back the stack and gets all return values (RIP)
 * and prints them to the DEBUG interface.
 */
void debug_stack_trace(unsigned int max_frames)
{
    printf("\r\n\x1b[48;5;232m\x1b[38;5;231m*** begin stack trace ***\r\n");

    // Thanks GCC :)
    uintptr_t* rbp = (uintptr_t*)__builtin_frame_address(0);

    for (unsigned int frame=0; frame<max_frames && rbp != NULL; frame++) {
        // Return address, 1 word above saved rbp
        uintptr_t rip = rbp[1];
        uintptr_t offset = 0;
        const char* name = debug_find_symbol(rip, &offset);
        printf("[%u] <0x%p> (%s+0x%x)\r\n", frame, (void*)rip, name, offset);

        uintptr_t* next_rbp = (uintptr_t*)rbp[0];

        // Invalid rbp or we're at the end
        if (next_rbp <= rbp || next_rbp == NULL) {
            break;
        }

        rbp = next_rbp;
    }

    printf("*** end stack trace ***\r\n[end Kernel panic]\r\nHalting system...\x1b[0m");
}

typedef struct {
    uint64_t addr;
    const char *name;
} __attribute__((packed)) kernel_symbol_t;

__attribute__((weak)) extern kernel_symbol_t symbol_table[];
__attribute__((weak)) extern uint64_t symbol_count;

/*
 * debug_find_symbol - Finds the symbol name associated to an address
 * @rip:    Pointer to executable code
 * @offset: Out pointer to reference the offset in the found function, if any
 *
 * Return:
 * <symbol name> - symbol name
 * "???"         - no symbol table found
 * "unknown"     - symbol table found, but address isn't in the table
 */
const char* debug_find_symbol(uintptr_t rip, uintptr_t* offset)
{
    if (!symbol_table || symbol_count == 0) {
        if (offset) *offset = 0;
        return "???";
    }

    int low = 0, high = (int)symbol_count - 1;
    int best = -1;

    while (low <= high) {
        int mid = (low + high) / 2;
        if (symbol_table[mid].addr <= rip) {
            best = mid;
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }

    if (best != -1) {
        if (offset) {
            *offset = rip - symbol_table[best].addr;
        }
        return symbol_table[best].name;
    }

    if (offset) {
        *offset = 0;
    }
    return "unknown";
}