root/usr/src/lib/libc/amd64/unwind/eh_frame.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.
 */

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

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

/*
 * CIE:
 *      UNUM32          length
 *      UNUM32          ID
 *      UNUM8           version
 *      ZTSTRING        augmentation
 *      ULEB128         Code Align Factor
 *      SLEB128         Data Align Factor
 *      UNUM8           RA
 *      ULEB128         length
 *      UNUM8           personality enc
 *      ADDR            personality
 *      UNUM8           code_enc
 *      UNUM8           lsda_enc
 *
 * FDE:
 *      UNUM32          length
 *      UNUM32          ID
 *      ADDR            initial loc
 *      SIZE            size
 *      ULEB128         length
 *      ADDR            lsda
 */


struct eh_frame_fields *
_Unw_Decode_FDE(struct eh_frame_fields *f, struct _Unwind_Context *ctx)
{
        void *fde_data;    /* location in this process of fde */
        void *fde_end;
        void *data;
        ptrdiff_t reloc;
        uintptr_t base;
        void *cie_data;    /* location in this process of cie */
        void *cie_end;
        void *cdata;
        ptrdiff_t creloc;
        int lsda_enc = 0;
        int per_enc = 0;
        int code_enc = 0;
        char augment[8];
        char *p;
        uint64_t scratch;

        uint64_t func = 0;
        uint64_t range = 0;
        _Unwind_Personality_Fn  pfn = 0;
        void* lsda = 0;

        /* here is where data mapping would happen ??REMOTE?? */
        fde_data = ctx->fde;
        data = fde_data;
        fde_end = (void *)(((intptr_t)fde_data) + 4 +
            _Unw_get_val(&data, 0, UNUM32, 1, 1, 0));
        reloc = 0;
        base = ((intptr_t)data) + reloc;
        cie_data = (void *)(base -  _Unw_get_val(&data, 0, UNUM32, 1, 1, 0));
        cdata = cie_data;
        cie_end = (void *)(((intptr_t)cie_data) + 4 +
            _Unw_get_val(&cdata, 0, UNUM32, 1, 1, 0));
        creloc = 0;
        /* data mapping has happened */

        f->cie_ops_end = cie_end;
        f->cie_reloc = creloc;
        f->fde_ops_end = fde_end;
        f->fde_reloc = reloc;

        (void) _Unw_get_val(&cdata, creloc, UNUM32, 1, 1, 0);
        (void) _Unw_get_val(&cdata, creloc, UNUM8, 1, 1, 0);
        /* LINTED alignment */
        (*((uint64_t *)(&(augment[0]))))  =
            _Unw_get_val(&cdata, creloc, ZTSTRING, 1, 1, 0);
        f->code_align = _Unw_get_val(&cdata, creloc, ULEB128, 1, 1, 0);
        f->data_align = _Unw_get_val(&cdata, creloc, SLEB128, 1, 1, 0);
        (void) _Unw_get_val(&cdata, creloc, UNUM8, 1, 1, 0);
        if (augment[0] == 'z' &&
            (scratch = _Unw_get_val(&cdata, creloc, ULEB128, 1, 1, 0)) != 0) {
                for (p = &(augment[1]); *p != 0; p++) {
                        switch (*p) {
                        case 'P':
                                per_enc = _Unw_get_val(&cdata, creloc,
                                    UNUM8, 1, 1, 0);
                                if (per_enc == 0)
                                        per_enc = 0x4;
                                pfn = (_Unwind_Personality_Fn)
                                    _Unw_get_val(&cdata, creloc,
                                    ADDR, 1, 1, per_enc);
                                break;
                        case 'R':
                                code_enc = _Unw_get_val(&cdata, creloc,
                                    UNUM8, 1, 1, 0);
                                break;
                        case 'L':
                                lsda_enc = _Unw_get_val(&cdata, creloc,
                                    UNUM8, 1, 1, 0);
                                break;
                        }
                }
        }
        if (code_enc == 0)
                code_enc = 0x4;

        func = _Unw_get_val(&data, reloc, ADDR, 1, 1, code_enc);
        range = _Unw_get_val(&data, reloc, SIZE, 1, 1, code_enc);
        if ((ctx->pc < func) || (ctx->pc > (func+range)))
                return (0);
        ctx->func = func;
        ctx->range = range;
        if (augment[0] == 'z') {
                scratch = _Unw_get_val(&data, reloc, ULEB128, 1, 1, 0);
                if (scratch == 4 && lsda_enc) {
                        /*
                         * without the two work-arounds test would be
                         * (scratch > 0 & lsda_enc)
                         */
                        lsda = (void *)_Unw_get_val(&data, reloc,
                            ADDR, 1, 1, lsda_enc);
                } else if (scratch == 4) {
                        /*
                         * 11/24/04 compiler is sometimes not outputing
                         * lsda_enc
                         */
                        lsda = (void*)_Unw_get_val(&data, reloc,
                            ADDR, 1, 1, 0x1b);
                } else if (scratch == 8) {
                        /*
                         * 11/12/04 - compiler is putting out relative
                         * encoding byte and absolute data - inconsistancy
                         * is caught here.
                         */
                        lsda = (void *)_Unw_get_val(&data, reloc,
                            ADDR, 1, 1, 0x4);
                }
        }
        if (pfn)
                ctx->pfn = pfn;
        if (lsda)
                ctx->lsda = lsda;
        f->fde_ops = data;
        f->cie_ops = cdata;
        f->code_enc = code_enc;
        return (f);
}

static int
table_ent_log_size(int enc)
{
        int val = enc & 0xf;
        int res;

        switch (val) {
        case 0x3:
                res = 3;
                break;
        case 0x04:
                res = 4;
                break;
        case 0x0b:
                res = 3;
                break;
        case 0x0c:
                res = 4;
                break;
        default:
                break;
        }
        return (res);
}

static void
get_table_ent_val(unsigned char *data, unsigned char *data_end,
        int enc, ptrdiff_t reloc, uintptr_t base,
        uint64_t *codep, uint64_t *next_codep, void **fdep)
{
        int val = enc & 0xf;
        int rel = (enc >> 4) & 0xf;
        unsigned char *second = data;
        unsigned char *third = data;
        uint64_t code;
        void *fde;
        uint64_t next_code;

        switch (val) {
        case 0x3:
                /* LINTED alignment */
                code = (uint64_t)(*((uint32_t *)data));
                second += 4;
                /* LINTED alignment */
                fde = (void *)(uint64_t)(*((uint32_t *)second));
                third += 8;
                next_code = (third >= data_end)? ULONG_MAX :
                    /* LINTED alignment */
                    (uint64_t)(*((uint32_t *)third));
                break;
        case 0x04:
                /* LINTED alignment */
                code = (uint64_t)(*((uint64_t *)data));
                second += 8;
                /* LINTED alignment */
                fde = (void *)(uint64_t)(*((uint64_t *)second));
                third += 16;
                next_code = (third >= data_end)? ULONG_MAX :
                    /* LINTED alignment */
                    (uint64_t)(*((uint64_t *)third));
                break;
        case 0x0b:
                /* LINTED alignment */
                code = (uint64_t)(int64_t)(*((int32_t *)data));
                second += 4;
                /* LINTED alignment */
                fde = (void *)(uint64_t)(int64_t)(*((int32_t *)second));
                third += 8;
                next_code = (third >= data_end)? ULONG_MAX :
                    /* LINTED alignment */
                    (uint64_t)(int64_t)(*((int32_t *)third));
                break;
        case 0x0c:
                /* LINTED alignment */
                code = (uint64_t)(*((int64_t *)data));
                second += 8;
                /* LINTED alignment */
                fde = (void *)(uint64_t)(*((int64_t *)second));
                third += 16;
                next_code = (third >= data_end)? ULONG_MAX :
                    /* LINTED alignment */
                    (uint64_t)(*((int64_t *)third));
                break;
        }

        switch (rel) {
        case 0:
                break;
        case 1:
                code += (uint64_t)data + reloc;
                fde = (void *)(((uint64_t)fde) + (uint64_t)second + reloc);
                if (next_code != ULONG_MAX)
                        next_code += (uint64_t)third + reloc;
                break;
        case 3:
                code += base;
                fde = (void *)(((uint64_t)fde) +  base);
                if (next_code != ULONG_MAX)
                        next_code += base;
                break;
        default:
                /* remainder not implemented */
                break;
        }
        *codep = code;
        *fdep = fde;
        *next_codep = next_code;
}


static void *
locate_fde_for_pc(uint64_t pc, int enc,
        unsigned char *table, unsigned char *table_end,
        ptrdiff_t reloc, uintptr_t base);

/*
 * Search the eh_frame info with a given pc.  Return a pointer to a
 * FDE.  The search is performed in two stages.
 * First rtld.so identifies the load module containing the target location.
 * This returns the appropiate eh_frame_hdr, and a binary search is
 * then performed on the eh_frame_hdr to locate the entry with
 * a matching pc value.
 */
void *
_Unw_EhfhLookup(struct _Unwind_Context *ctx)
{
        Dl_amd64_unwindinfo dlef;
        void* data;
        void* data_end;
        uint64_t pc = ctx->pc;
        int fp_enc, fc_enc, ft_enc;
        unsigned char *pi, *pj;
        ptrdiff_t reloc;
        uintptr_t base;

        dlef.dlui_version = 1;

        /* Locate the appropiate exception_range_entry table first */
        if (0 == dlamd64getunwind((void*)pc, &dlef)) {
                return (0);
        }

        /*
         * you now know size and position of block of data needed for
         * binary search ??REMOTE??
         */
        data = dlef.dlui_unwindstart;
        if (0 == data)
                return (0);
        base = (uintptr_t)data;
        data_end = dlef.dlui_unwindend;
        reloc = 0;
        /* ??REMOTE?? */

        (void) _Unw_get_val(&data, reloc, UNUM8, 1, 1, 0);
        fp_enc = _Unw_get_val(&data, reloc, UNUM8, 1, 1, 0);
        fc_enc = _Unw_get_val(&data, reloc, UNUM8, 1, 1, 0);
        ft_enc = _Unw_get_val(&data, reloc, UNUM8, 1, 1, 0);
        (void) _Unw_get_val(&data, reloc, ADDR, 1, 1, fp_enc);
        (void) _Unw_get_val(&data, reloc, SIZE, 1, 1, fc_enc);
        pi = data;
        pj = data_end;
        ctx->fde = locate_fde_for_pc(pc, ft_enc, pi,  pj, reloc, base);
        return ((void *)(ctx->fde));
}

static void *
locate_fde_for_pc(uint64_t pc, int enc,
        unsigned char *table_bg, unsigned char *table_end,
        ptrdiff_t reloc, uintptr_t base)
{
        unsigned char *pi = table_bg;
        unsigned char *pj = table_end;
        uint64_t range_start, range_end;
        void* fde;
        int log_size = table_ent_log_size(enc);

        /*
         * Invariant -- if there is a containing range,
         * it must lie in the interval [pi,pj).  That is,
         * pi <= p < pj, if p exists.
         */
        while (pi < pj) {
                unsigned char *pr =
                    pi + (((pj - pi) >> (log_size + 1)) << log_size);
                                /* Don't use (pi+pj)>>1 */
                get_table_ent_val(pr, table_end, enc, reloc, base,
                    &range_start, &range_end, &fde);

                /* Return fde if tpc is in this range. */

                if (range_start <= pc && pc < range_end) {
                        return ((void*) fde);
                }

                if (range_start < pc)
                        pi = pr + (1 << log_size);
                else
                        pj = pr;
        }
        return (0);
}