root/usr/src/lib/libc/amd64/unwind/call_frame_inst.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2012 Milan Jurik. All rights reserved.
 * Copyright 2020 Oxide Computer Company
 */

/*
 * interface used by unwind support to query frame descriptor info
 */

#ifndef _LIBCRUN_
#include "lint.h"
#endif
#include <sys/types.h>
#include "stack_unwind.h"
#include "unwind_context.h"
#include "reg_num.h"

enum CFA_ops {
        DW_CFA_nop = 0x00,
        DW_CFA_set_loc = 0x01,
        DW_CFA_advance_loc1 = 0x02,
        DW_CFA_advance_loc2 = 0x03,
        DW_CFA_advance_loc4 = 0x04,
        DW_CFA_offset_extended = 0x05,
        DW_CFA_restore_extended = 0x06,
        DW_CFA_undefined = 0x07,
        DW_CFA_same_value = 0x08,
        DW_CFA_register = 0x09,
        DW_CFA_remember_state = 0x0a,
        DW_CFA_restore_state = 0x0b,
        DW_CFA_def_cfa = 0x0c,
        DW_CFA_def_cfa_register = 0x0d,
        DW_CFA_def_cfa_offset = 0x0e,
        DW_CFA_def_cfa_expression = 0x0f,
        DW_CFA_expression = 0x10,
        DW_CFA_offset_extended_sf = 0x11,
        DW_CFA_def_cfa_sf = 0x12,
        DW_CFA_def_cfa_offset_sf = 0x13,
        /* skip 9 values */
        DW_CFA_SUNW_advance_loc = 0x1d,
        DW_CFA_SUNW_offset = 0x1e,
        DW_CFA_SUNW_restore = 0x1f,
        DW_CFA_advance_loc = 0x40,
        DW_CFA_offset = 0x80,
        DW_CFA_restore = 0xc0
};

struct operation_desc {
        enum operand_desc op1;
        enum operand_desc op2;
};

struct operation_desc cfa_operations[] = {
        {NO_OPR, NO_OPR},       /* DW_CFA_nop */
        {ADDR, NO_OPR},         /* DW_CFA_set_loc - address */
        {UNUM8, NO_OPR},        /* DW_CFA_advance_loc1 - delta */
        {UNUM16, NO_OPR},       /* DW_CFA_advance_loc2 - delta */
        {UNUM32, NO_OPR},       /* DW_CFA_advance_loc4 - delta */
        {ULEB128, ULEB128_FAC}, /* DW_CFA_offset_extended - reg, */
                                /* data factored offset */
        {ULEB128, NO_OPR},      /* DW_CFA_restore_extended - register */
        {ULEB128, NO_OPR},      /* DW_CFA_undefined - register */
        {ULEB128, NO_OPR},      /* DW_CFA_same_value - register */
        {ULEB128, ULEB128_SREG}, /* DW_CFA_register - register, register */
        {NO_OPR, NO_OPR},       /* DW_CFA_remember_state */
        {NO_OPR, NO_OPR},       /* DW_CFA_restore_state */
        {ULEB128_SREG, ULEB128}, /* DW_CFA_def_cfa - register, offset */
        {ULEB128_SREG, NO_OPR}, /* DW_CFA_def_cfa_register - register */
        {ULEB128, NO_OPR},      /* DW_CFA_def_cfa_offset - offset */
        {BLOCK, NO_OPR},        /* DW_CFA_def_cfa_expression - expression */
        {ULEB128, BLOCK},       /* DW_CFA_expression - reg, expression */
        {ULEB128, SLEB128_FAC}, /* DW_CFA_offset_extended_sf - reg, */
                                /* data factored offset */
        {ULEB128_SREG, SLEB128_FAC},    /* DW_CFA_def_cfa_sf - reg, */
                                        /* data factored offset */
        {SLEB128_FAC, NO_OPR},  /* DW_CFA_def_cfa_offset_sf - */
                                /* data fctored offset */
        {NO_OPR, NO_OPR},
        {NO_OPR, NO_OPR},
        {NO_OPR, NO_OPR},
        {NO_OPR, NO_OPR},
        {NO_OPR, NO_OPR},
        {NO_OPR, NO_OPR},
        {NO_OPR, NO_OPR},
        {NO_OPR, NO_OPR},
        {NO_OPR, NO_OPR},
        {UNUM6_CFAC, NO_OPR},   /* DW_CFA_SUNW_advance_loc - */
                                /* code factored delta */
        {UNUM6, ULEB128_FAC},   /* DW_CFA_SUNW_offset - reg */
                                /* data factored offset */
        {UNUM6, NO_OPR}         /* DW_CFA_SUNW_restore */
};

uint64_t interpret_ops(void *data, void *data_end,
                ptrdiff_t reloc, uint64_t current_loc, uint64_t pc,
                struct register_state f_state[],
                struct register_state f_start_state[],
                int daf, int caf, int enc);

/*
 * The entry-point state of old_ctx defines the current
 * suspended state of the caller (in new_ctx). If the old info
 * will not be refered to again, old_ctx == new_ctx is OK
 */
void
_Unw_Propagate_Registers(struct _Unwind_Context *old_ctx,
    struct _Unwind_Context *new_ctx)
{
        new_ctx->current_regs[SP_RSP] = old_ctx->cfa;
        new_ctx->pc = old_ctx->ra;
        new_ctx->current_regs[FP_RBP] = old_ctx->entry_regs[FP_RBP];
        new_ctx->current_regs[GPR_RBX] = old_ctx->entry_regs[GPR_RBX];
        new_ctx->current_regs[EIR_R12] = old_ctx->entry_regs[EIR_R12];
        new_ctx->current_regs[EIR_R13] = old_ctx->entry_regs[EIR_R13];
        new_ctx->current_regs[EIR_R14] = old_ctx->entry_regs[EIR_R14];
        new_ctx->current_regs[EIR_R15] = old_ctx->entry_regs[EIR_R15];
}

void
fix_cfa(struct _Unwind_Context *ctx, struct register_state *rs)
{
        switch (rs[CF_ADDR].rule) {
        default:
                ctx->cfa = 0;
                break;
        case register_rule:     /* CFA = offset + source_reg */
                ctx->cfa = (ctx->current_regs)[rs[CF_ADDR].source_reg] +
                    rs[CF_ADDR].offset;
                break;
        case constant_rule:     /* CFA = offset */
                ctx->cfa = rs[CF_ADDR].offset;
                break;
        case indirect_rule:     /* CFA = *(offset + source_reg) */
                ctx->cfa = *(uint64_t *)
                    (ctx->current_regs[rs[CF_ADDR].source_reg] +
                    rs[CF_ADDR].offset);
                break;
        }
        ctx->entry_regs[SP_RSP] = ctx->cfa;
}

void
fix_ra(struct _Unwind_Context *ctx, struct register_state *rs)
{
        switch (rs[RET_ADD].rule) {
        case undefined_rule:
        default:
                ctx->ra = 0;
                break;
        case offset_rule:       /* RA = *(offset + CFA) */
                ctx->ra = *(uint64_t *)(ctx->cfa + rs[RET_ADD].offset);
                break;
        case register_rule:     /* RA = offset + source_reg */
                ctx->ra = ctx->current_regs[rs[RET_ADD].source_reg] +
                    rs[RET_ADD].offset;
                break;
        case indirect_rule:     /* RA = *(offset + source_reg) */
                ctx->ra = *(uint64_t *)
                    (ctx->current_regs[rs[RET_ADD].source_reg] +
                    rs[RET_ADD].offset);
                break;
        }
}

void
fix_reg(struct _Unwind_Context *ctx, struct register_state *rs, int index)
{
        switch (rs[index].rule) {
        default:
                ctx->entry_regs[index] = ctx->current_regs[index];
                break;
        case offset_rule:       /* target_reg = *(offset + CFA) */
                ctx->entry_regs[index] = *(uint64_t *)
                    (ctx->cfa + rs[index].offset);
                break;
        case is_offset_rule:    /* target_reg = offset + CFA */
                ctx->entry_regs[index] = ctx->cfa + rs[index].offset;
                break;
        case register_rule:     /* target_reg = offset + source_reg */
                ctx->entry_regs[index] =
                    ctx->current_regs[rs[index].source_reg] +
                    rs[index].offset;
                break;
        case constant_rule:     /* target_reg = offset */
                ctx->entry_regs[index] = rs[index].offset;
                break;
        case indirect_rule:     /* target_reg = *(offset + source_reg) */
                ctx->entry_regs[index] = *(uint64_t *)
                    (ctx->current_regs[rs[index].source_reg] +
                    rs[index].offset);
                break;
        }
}


/*
 * Input: f->{cie_ops, cie_ops_end, fde_ops, fde_ops_end}
 *                      + location of DWARF opcodes
 *                ctx->{current_regs, pc}
 *                      + register values and pc at point of suspension
 * Output: ctx->{entry_regs, cfa, ra}
 *                      + register values when function was entered
 *                      + Cannonical Frame Address
 *                      + return address
 */
uint64_t
_Unw_Rollback_Registers(struct eh_frame_fields *f,
    struct _Unwind_Context *ctx)
{
        /* GPRs, RET_ADD, and CF_ADDR */
        struct register_state func_state[18];
        struct register_state func_start_state[18];
        struct register_state nop = { 0, undefined_rule, 0 };
        int i;
        uint64_t  first_pc;

        if (f == 0) {
                /*
                 * When no FDE we assume all routines have a frame pointer
                 * and pass back existing callee saves registers
                 */
                if (ctx->current_regs[FP_RBP] < ctx->current_regs[SP_RSP]) {
                        ctx->cfa = 0;
                        ctx->ra = 0;
                        ctx->pc = 0;
                        return (0);
                }
                ctx->entry_regs[FP_RBP] = ((uint64_t *)
                    (ctx->current_regs[FP_RBP]))[0];
                ctx->cfa = ctx->current_regs[FP_RBP] + 16;
                ctx->entry_regs[SP_RSP] = ctx->cfa;
                ctx->entry_regs[GPR_RBX] = ctx->current_regs[GPR_RBX];
                ctx->entry_regs[EIR_R12] = ctx->current_regs[EIR_R12];
                ctx->entry_regs[EIR_R13] = ctx->current_regs[EIR_R13];
                ctx->entry_regs[EIR_R14] = ctx->current_regs[EIR_R14];
                ctx->entry_regs[EIR_R15] = ctx->current_regs[EIR_R15];
                ctx->ra = ((uint64_t *)ctx->cfa)[-1];
                return (ctx->cfa);
        }

        for (i = 0; i < 18; i++)
                func_start_state[i] = nop;
        first_pc = interpret_ops(f->cie_ops, f->cie_ops_end,
            f->cie_reloc, ctx->func, ctx->pc, func_start_state, 0,
            f->data_align, f->code_align, f->code_enc);
        for (i = 0; i < 18; i++)
                func_state[i] = func_start_state[i];
        (void) interpret_ops(f->fde_ops, f->fde_ops_end,
            f->fde_reloc, first_pc, ctx->pc, func_state, func_start_state,
            f->data_align, f->code_align, f->code_enc);

        fix_cfa(ctx, func_state);
        if (ctx->cfa < ctx->current_regs[SP_RSP]) {
                ctx->cfa = 0;
                ctx->ra = 0;
                ctx->pc = 0;
                return (0);
        }
        fix_ra(ctx, func_state);
        fix_reg(ctx, func_state, GPR_RBX);
        fix_reg(ctx, func_state, FP_RBP);
        fix_reg(ctx, func_state, EIR_R12);
        fix_reg(ctx, func_state, EIR_R13);
        fix_reg(ctx, func_state, EIR_R14);
        fix_reg(ctx, func_state, EIR_R15);

        return (ctx->cfa);
}

/*
 * remap two-bit opcodes into a separate range or grab eight-bit opcode
 * and advance pointer past it.
 */
static enum CFA_ops
separate_op(void **pp)
{
        uint8_t c = **((uint8_t **)pp);

        if (c & 0xc0) {
                switch (c & 0xc0) {
                case DW_CFA_advance_loc:
                        return (DW_CFA_SUNW_advance_loc);
                case DW_CFA_offset:
                        return (DW_CFA_SUNW_offset);
                case DW_CFA_restore:
                        return (DW_CFA_SUNW_restore);
                }
        } else {
                *pp = (void *)((*(intptr_t *)pp) + 1);
        }
        return (c);
}

static uint64_t
extractuleb(void **datap)
{
        uint8_t *data = *(uint8_t **)datap;
        uint64_t res = 0;
        int more = 1;
        int shift = 0;
        int val;

        while (more) {
                val = (*data) & 0x7f;
                more = ((*data++) & 0x80) >> 7;
                res = res | val << shift;
                shift += 7;
        }
        *datap = (void *)data;
        return (res);
}

static uint64_t
extractsleb(void** datap)
{
        uint8_t *data = *datap;
        int64_t res = 0;
        int more = 1;
        int shift = 0;
        unsigned int val;

        while (more) {
                val = (*data) & 0x7f;
                more = ((*data++) & 0x80) >> 7;
                res = res | val<< shift;
                shift += 7;
        }
        *datap = (void*) data;
        res = (res << (64 - shift)) >> (64 - shift);
        return (res);
}

static uint64_t get_encoded_val(void **datap, ptrdiff_t reloc, int enc);

/*
 * do all field extractions needed for CFA operands and encoded FDE
 * fields
 */
uint64_t
_Unw_get_val(void **datap, ptrdiff_t reloc,
    enum operand_desc opr, int daf, int caf, int enc)
{
        intptr_t data = (intptr_t)*datap;
        uint64_t res;
        char *dp, *rp;

        switch (opr) {
        case NO_OPR:
                res = 0;
                break;
        case ULEB128_FAC:
                return (daf * extractuleb(datap));
        case ULEB128:
                return (extractuleb(datap));
        case ULEB128_SREG:
                res = (uint64_t)(*((uint8_t *)data));
                data += 1;
                switch (res) {
                        /* verify that register is one which is being tracked */
                case GPR_RBX:
                case FP_RBP:
                case SP_RSP:
                case EIR_R12:
                case EIR_R13:
                case EIR_R14:
                case EIR_R15:
                        break;
                default:
                        res = BAD_REG;
                        break;
                }
                break;
        case UNUM6:
                res = (uint64_t)(0x3f & *((uint8_t *)data));
                data += 1;
                break;
        case UNUM8:
                res = (uint64_t)(*((uint8_t *)data));
                data += 1;
                break;
        case UNUM16:
                res = (uint64_t)(*((uint16_t *)data));
                data += 2;
                break;
        case UNUM32:
                res = (uint64_t)(*((uint32_t *)data));
                data += 4;
                break;
        case UNUM6_CFAC:
                res = caf * (uint64_t)(0x3f & *((uint8_t *)data));
                data += 1;
                break;
        case UNUM8_CFAC:
                res = caf * (uint64_t)(*((uint8_t *)data));
                data += 1;
                break;
        case UNUM16_CFAC:
                res = caf * (uint64_t)(*((uint16_t *)data));
                data += 2;
                break;
        case UNUM32_CFAC:
                res = caf * (uint64_t)(*((uint32_t *)data));
                data += 4;
                break;
        case UNUM64:
                res = (uint64_t)(*((uint64_t *)data));
                data += 8;
                break;
        case SNUM8:
                res = (uint64_t)(int64_t)(*((int8_t *)data));
                data += 1;
                break;
        case SNUM16:
                res = (uint64_t)(int64_t)(*((int16_t *)data));
                data += 2;
                break;
        case SNUM32:
                res = (uint64_t)(int64_t)(*((int32_t *)data));
                data += 4;
                break;
        case SNUM64:
                res = (uint64_t)(*((int64_t *)data));
                data += 8;
                break;
        case SLEB128_FAC:
                return (daf * extractsleb(datap));
        case SLEB128:
                return (extractsleb(datap));
        case ZTSTRING:
                /* max length of augmentation string is 4 */
                rp = (char *)&res;
                dp = (char *)data;
                while ((*rp++ = *dp++) != '\0')
                        ;
                data = (intptr_t)dp;
                break;
        case ADDR:
                return (get_encoded_val(datap, reloc, enc));
        case SIZE:
                return (get_encoded_val(datap, reloc, enc & 0x7));
        case BLOCK:
                res = 0;  /* not implemented */
                break;
        }
        *datap = (void*)data;
        return (res);
}

static uint64_t
get_encoded_val(void **datap, ptrdiff_t reloc, int enc)
{
        const uint8_t val = enc & 0xf;
        const uint8_t rel = enc & 0x70;
        const boolean_t indirect = (enc & 0x80) != 0;
        intptr_t loc = ((intptr_t)*datap) + reloc;
        uint64_t res = 0;

        /*
         * Calculate the offset represented by the pointer encoding.  These
         * DWARF extensions are defined in the Core Generic document set of the
         * LSB specification.
         */
        switch (val) {
        case 0x01:
                res = _Unw_get_val(datap, reloc, ULEB128, 1, 1, 0);
                break;
        case 0x02:
                res = _Unw_get_val(datap, reloc, UNUM16, 1, 1, 0);
                break;
        case 0x03:
                res = _Unw_get_val(datap, reloc, UNUM32, 1, 1, 0);
                break;
        case 0x04:
                res = _Unw_get_val(datap, reloc, UNUM64, 1, 1, 0);
                break;
        case 0x09:
                res = _Unw_get_val(datap, reloc, SLEB128, 1, 1, 0);
                break;
        case 0x0a:
                res = _Unw_get_val(datap, reloc, SNUM16, 1, 1, 0);
                break;
        case 0x0b:
                res = _Unw_get_val(datap, reloc, SNUM32, 1, 1, 0);
                break;
        case 0x0c:
                res = _Unw_get_val(datap, reloc, SNUM64, 1, 1, 0);
                break;
        }
        switch (rel) {
        case 0x00:
                break;
        case 0x10:
                /* DW_EH_PE_pcrel */
                if (res != 0)
                        res += loc;
                break;
        default:
                /* remainder not implemented */
                break;
        }

        /*
         * The high bit of the pointer encoding (DW_EH_PE_indirect = 0x80)
         * indicates that a pointer-sized value should be read from the
         * calculated address as the final result.
         *
         * Shockingly, this is not documented in any specification to date, but
         * has been implemented in various unwind implementations through
         * reverse-engineering of GCC.
         */
        if (indirect) {
                void *addr = (void *)(uintptr_t)res;

                /*
                 * Built only for amd64, we can count on a 64-bit pointer size
                 * for the indirect handling.
                 */
                res = _Unw_get_val(&addr, reloc, UNUM64, 1, 1, 0);
        }

        return (res);
}


int interpret_op(void **datap, ptrdiff_t reloc,
        uint64_t *reached_pc_p, uint64_t pc,
        struct register_state f_state[],
        struct register_state f_start_state[],
        int daf, int caf, int enc);

uint64_t
interpret_ops(void *data, void *data_end,
    ptrdiff_t reloc,
    uint64_t start_pc, uint64_t pc,
    struct register_state f_state[],
    struct register_state f_start_state[],
    int daf, int caf, int enc)
{
        void *d = data;
        uint64_t reached_pc = start_pc;

        while (d < data_end) {
                if (interpret_op(&d, reloc, &reached_pc, pc,
                    f_state, f_start_state, daf, caf, enc))
                        break;
        }
        return (reached_pc);
}

int
interpret_op(void **datap, ptrdiff_t reloc,
    uint64_t *reached_pc_p, uint64_t pc,
    struct register_state f_state[],
    struct register_state f_start_state[],
    int daf, int caf, int enc)
{
        enum CFA_ops op = separate_op(datap);
        enum operand_desc opr1 = (cfa_operations[op]).op1;
        enum operand_desc opr2 = (cfa_operations[op]).op2;

        uint64_t val1 = _Unw_get_val(datap, reloc, opr1, daf, caf, enc);
        uint64_t val2 = _Unw_get_val(datap, reloc, opr2, daf, caf, enc);
        if ((opr1 == ULEB128_SREG && val1 == BAD_REG) ||
            (opr2 == ULEB128_SREG && val2 == BAD_REG))
                return (0);
        switch (op) {
        case DW_CFA_nop:
                break;
        case DW_CFA_set_loc:
                if (val1 > pc)
                        return (1);
                *reached_pc_p = val1;
                break;
        case DW_CFA_advance_loc1:
        case DW_CFA_advance_loc2:
        case DW_CFA_advance_loc4:
                if (*reached_pc_p + val1 > pc)
                        return (1);
                *reached_pc_p += val1;
                break;
        case DW_CFA_offset_extended:
                f_state[val1].rule = offset_rule;
                f_state[val1].source_reg = CF_ADDR;
                f_state[val1].offset = val2;
                break;
        case DW_CFA_restore_extended:
                if (f_start_state != 0)
                        f_state[val1] = f_start_state[val1];
                break;
        case DW_CFA_undefined:
                f_state[val1].rule = undefined_rule;
                break;
        case DW_CFA_same_value:
                f_state[val1].rule = same_value_rule;
                break;
        case DW_CFA_register:
                f_state[val1].rule = register_rule;
                f_state[val1].source_reg = val2;
                f_state[val1].offset = 0;
                break;
        case DW_CFA_remember_state:
                break;
        case DW_CFA_restore_state:
                break;
        case DW_CFA_def_cfa:
                f_state[CF_ADDR].rule = register_rule;
                f_state[CF_ADDR].source_reg = val1;
                f_state[CF_ADDR].offset = val2;
                break;
        case DW_CFA_def_cfa_register:
                f_state[CF_ADDR].source_reg = val1;
                break;
        case DW_CFA_def_cfa_offset:
                f_state[CF_ADDR].offset = val1;
                break;
        case DW_CFA_def_cfa_expression:
                break;
        case DW_CFA_expression:
                break;
        case DW_CFA_offset_extended_sf:
                f_state[val1].rule = offset_rule;
                f_state[val1].source_reg = CF_ADDR;
                f_state[val1].offset = val2;
                break;
        case DW_CFA_def_cfa_sf:
                f_state[CF_ADDR].rule = register_rule;
                f_state[CF_ADDR].source_reg = val1;
                f_state[CF_ADDR].offset = val2;
                break;
        case DW_CFA_def_cfa_offset_sf:
                f_state[CF_ADDR].offset = val1;
                break;
        case DW_CFA_SUNW_advance_loc:
                if (*reached_pc_p + val1 > pc)
                        return (1);
                *reached_pc_p += val1;
                break;
        case DW_CFA_SUNW_offset:
                f_state[val1].rule = offset_rule;
                f_state[val1].source_reg = CF_ADDR;
                f_state[val1].offset = val2;
                break;
        case DW_CFA_SUNW_restore:
                if (f_start_state != 0)
                        f_state[val1] = f_start_state[val1];
                break;
        }
        return (0);
}