root/usr/src/cmd/sgs/demo_rdb/common/syms.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 (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdio.h>
#include <string.h>
#include <libelf.h>
#include "rdb.h"

/*
 * Given a symbol index, look up the corresponding symbol from the
 * given symbol table.
 *
 * This function allows the caller to treat the symbol table as a single
 * logical entity even though there may be 2 actual ELF symbol tables
 * involved. See the comments in Pcontrol.h for details.
 */
static GElf_Sym *
symtab_getsym(sym_tbl_t *symtab, int ndx, GElf_Sym *dst)
{
        /* If index is in range of primary symtab, look it up there */
        if (ndx >= symtab->st_symn_aux) {
                return (gelf_getsym(symtab->st_syms_pri,
                    ndx - symtab->st_symn_aux, dst));
        }

        /* Not in primary: Look it up in the auxiliary symtab */
        return (gelf_getsym(symtab->st_syms_aux, ndx, dst));
}

retc_t
str_map_sym(const char *symname, map_info_t *mp, GElf_Sym *symptr, char **str)
{
        sym_tbl_t       *symp;
        char            *strs;
        int             i;

        if (mp->mi_symtab.st_syms_pri)
                symp = &(mp->mi_symtab);
        else if (mp->mi_dynsym.st_syms_pri)
                symp = &(mp->mi_dynsym);
        else
                return (RET_FAILED);

        strs = symp->st_strs;

        for (i = 0; i < (int)symp->st_symn; i++) {
                GElf_Sym sym;

                if (symtab_getsym(symp, i, &sym) == NULL) {
                        (void) printf("symtab_getsym(): %s\n", elf_errmsg(-1));
                        return (RET_FAILED);
                }

                if (sym.st_name == 0)
                        continue;
                if ((sym.st_shndx == SHN_UNDEF) ||
                    (strcmp(strs + sym.st_name, symname) != 0))
                        continue;
                *symptr = sym;
                if (str != NULL)
                        *str = (char *)strs + symptr->st_name;
                if ((mp->mi_flags & FLG_MI_EXEC) == 0)
                        symptr->st_value += (GElf_Addr)(mp->mi_addr);
                return (RET_OK);
        }

        return (RET_FAILED);
}

/*
 * If two syms are of equal value this routine will
 * favor one over the other based off of it's symbol
 * type.
 */
static GElf_Sym
sym_swap(GElf_Sym * s1, GElf_Sym * s2)
{
        int     t1 = GELF_ST_TYPE(s1->st_info);
        int     t2 = GELF_ST_TYPE(s2->st_info);

        if ((t1 == STT_FUNC) || (t2 == STT_FUNC)) {
                if (t1 == STT_FUNC)
                        return (*s1);
                return (*s2);
        }

        if ((t1 == STT_OBJECT) || (t2 == STT_OBJECT)) {
                if (t1 == STT_OBJECT)
                        return (*s1);
                return (*s2);
        }

        if ((t1 == STT_OBJECT) || (t2 == STT_OBJECT)) {
                if (t1 == STT_OBJECT)
                        return (*s1);
                return (*s2);
        }
        return (*s1);
}

static retc_t
addr_map_sym(map_info_t *mp, ulong_t addr, GElf_Sym *symptr, char **str)
{
        sym_tbl_t       *symp;
        GElf_Sym        sym;
        GElf_Sym        *symr = NULL;
        GElf_Sym        *lsymr = NULL;
        GElf_Sym        rsym;
        GElf_Sym        lsym;
        ulong_t         baseaddr = 0;
        int             i;

        if ((mp->mi_flags & FLG_MI_EXEC) == 0)
                baseaddr = (ulong_t)mp->mi_addr;

        if (mp->mi_symtab.st_syms_pri)
                symp = &(mp->mi_symtab);
        else if (mp->mi_dynsym.st_syms_pri)
                symp = &(mp->mi_dynsym);
        else
                return (RET_FAILED);

        /*
         * normalize address
         */
        addr -= baseaddr;
        for (i = 0; i < (int)symp->st_symn; i++) {
                ulong_t svalue;

                if (symtab_getsym(symp, i, &sym) == NULL) {
                        (void) printf("symtab_getsym(): %s\n", elf_errmsg(-1));
                        return (RET_FAILED);
                }
                if ((sym.st_name == 0) || (sym.st_shndx == SHN_UNDEF))
                        continue;

                svalue = (ulong_t)sym.st_value;

                if (svalue <= addr) {
                        /*
                         * track both the best local and best
                         * global fit for this address.  Later
                         * we will favor the global over the local
                         */
                        if ((GELF_ST_BIND(sym.st_info) == STB_LOCAL) &&
                            ((lsymr == NULL) ||
                            (svalue >= (ulong_t)lsymr->st_value))) {
                                if (lsymr && (lsymr->st_value == svalue))
                                        *lsymr = sym_swap(lsymr, &sym);
                                else {
                                        lsymr = &lsym;
                                        *lsymr = sym;
                                }
                        } else if ((symr == NULL) ||
                            (svalue >= (ulong_t)symr->st_value)) {
                                if (symr && (symr->st_value == svalue))
                                        *symr = sym_swap(symr, &sym);
                                else {
                                        symr = &rsym;
                                        *symr = sym;
                                }
                        }
                }
        }
        if ((symr == NULL) && (lsymr == NULL))
                return (RET_FAILED);

        if (lsymr) {
                /*
                 * If a possible local symbol was found should
                 * we use it.
                 */
                if (symr && (lsymr->st_value > symr->st_value))
                        symr = lsymr;
                else if (symr == NULL)
                        symr = lsymr;
        }

        *symptr = *symr;
        *str = (char *)(symp->st_strs + symptr->st_name);
        symptr->st_value += baseaddr;
        return (RET_OK);
}

retc_t
addr_to_sym(struct ps_prochandle *ph, ulong_t addr,
        GElf_Sym *symp, char **str)
{
        map_info_t      *mip;

        if ((mip = addr_to_map(ph, addr)) == NULL)
                return (RET_FAILED);

        return (addr_map_sym(mip, addr, symp, str));
}

retc_t
str_to_sym(struct ps_prochandle *ph, const char *name, GElf_Sym *symp)
{
        map_info_t      *mip;

        if (ph->pp_lmaplist.ml_head == NULL) {
                if (str_map_sym(name, &(ph->pp_ldsomap), symp, NULL) == RET_OK)
                        return (RET_OK);

                return (str_map_sym(name, &(ph->pp_execmap), symp, NULL));
        }

        for (mip = ph->pp_lmaplist.ml_head; mip; mip = mip->mi_next)
                if (str_map_sym(name, mip, symp, NULL) == RET_OK)
                        return (RET_OK);

        return (RET_FAILED);
}