root/usr/src/cmd/dis/dis_target.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) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 *
 * Copyright 2011 Jason King.  All rights reserved.
 */

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <gelf.h>
#include <libelf.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>

#include "dis_target.h"
#include "dis_util.h"

/*
 * Standard ELF disassembler target.
 *
 * We only support disassembly of ELF files, though this target interface could
 * be extended in the future.  Each basic type (target, func, section) contains
 * enough information to uniquely identify the location within the file.  The
 * interfaces use libelf(3LIB) to do the actual processing of the file.
 */

/*
 * Symbol table entry type.  We maintain our own symbol table sorted by address,
 * with the symbol name already resolved against the ELF symbol table.
 */
typedef struct sym_entry {
        GElf_Sym        se_sym;         /* value of symbol */
        char            *se_name;       /* name of symbol */
        int             se_shndx;       /* section where symbol is located */
} sym_entry_t;

/*
 * Create a map of the virtual address ranges of every section.  This will
 * allow us to create dummpy mappings for unassigned addresses.  Otherwise
 * multiple sections with unassigned addresses will appear to overlap and
 * mess up symbol resolution (which uses the virtual address).
 */
typedef struct dis_shnmap {
        const char      *dm_name;       /* name of section */
        uint64_t        dm_start;       /* virtual address of section */
        size_t          dm_length;      /* address length */
        boolean_t       dm_mapped;      /* did we assign the mapping */
} dis_shnmap_t;

/*
 * Target data structure.  This structure keeps track of the ELF file
 * information, a few bits of pre-processed section index information, and
 * sorted versions of the symbol table.  We also keep track of the last symbol
 * looked up, as the majority of lookups remain within the same symbol.
 */
struct dis_tgt {
        Elf             *dt_elf;        /* libelf handle */
        Elf             *dt_elf_root;   /* main libelf handle (for archives) */
        const char      *dt_filename;   /* name of file */
        int             dt_fd;          /* underlying file descriptor */
        size_t          dt_shstrndx;    /* section index of .shstrtab */
        size_t          dt_symidx;      /* section index of symbol table */
        sym_entry_t     *dt_symcache;   /* last symbol looked up */
        sym_entry_t     *dt_symtab;     /* sorted symbol table */
        int             dt_symcount;    /* # of symbol table entries */
        struct dis_tgt  *dt_next;       /* next target (for archives) */
        Elf_Arhdr       *dt_arhdr;      /* archive header (for archives) */
        dis_shnmap_t    *dt_shnmap;     /* section address map */
        size_t          dt_shncount;    /* # of sections in target */
};

/*
 * Function data structure.  We resolve the symbol and lookup the associated ELF
 * data when building this structure.  The offset is calculated based on the
 * section's starting address.
 */
struct dis_func {
        sym_entry_t     *df_sym;        /* symbol table reference */
        Elf_Data        *df_data;       /* associated ELF data */
        size_t          df_offset;      /* offset within data */
};

/*
 * Section data structure.  We store the entire section header so that we can
 * determine some properties (such as whether or not it contains text) after
 * building the structure.
 */
struct dis_scn {
        GElf_Shdr       ds_shdr;
        const char      *ds_name;
        Elf_Data        *ds_data;
};

/* Lifted from Psymtab.c, omitting STT_TLS */
#define DATA_TYPES      \
        ((1 << STT_OBJECT) | (1 << STT_FUNC) | (1 << STT_COMMON))
#define IS_DATA_TYPE(tp)        (((1 << (tp)) & DATA_TYPES) != 0)

/*
 * Save the virtual address range for this section and select the
 * best section to use as the symbol table.  We prefer SHT_SYMTAB
 * over SHT_DYNSYM.
 */
/* ARGSUSED */
static void
tgt_scn_init(dis_tgt_t *tgt, dis_scn_t *scn, void *data)
{
        int *index = data;

        *index += 1;

        tgt->dt_shnmap[*index].dm_name = scn->ds_name;
        tgt->dt_shnmap[*index].dm_start = scn->ds_shdr.sh_addr;
        tgt->dt_shnmap[*index].dm_length = scn->ds_shdr.sh_size;
        tgt->dt_shnmap[*index].dm_mapped = B_FALSE;

        /*
         * Prefer SHT_SYMTAB over SHT_DYNSYM
         */
        if (scn->ds_shdr.sh_type == SHT_DYNSYM && tgt->dt_symidx == 0)
                tgt->dt_symidx = *index;
        else if (scn->ds_shdr.sh_type == SHT_SYMTAB)
                tgt->dt_symidx = *index;
}

static int
sym_compare(const void *a, const void *b)
{
        const sym_entry_t *syma = a;
        const sym_entry_t *symb = b;
        const char *aname = syma->se_name;
        const char *bname = symb->se_name;
        size_t alen;
        size_t blen;

        if (syma->se_sym.st_value < symb->se_sym.st_value)
                return (-1);

        if (syma->se_sym.st_value > symb->se_sym.st_value)
                return (1);

        /*
         * Prefer functions over non-functions
         */
        if (GELF_ST_TYPE(syma->se_sym.st_info) !=
            GELF_ST_TYPE(symb->se_sym.st_info)) {
                if (GELF_ST_TYPE(syma->se_sym.st_info) == STT_FUNC)
                        return (-1);
                if (GELF_ST_TYPE(symb->se_sym.st_info) == STT_FUNC)
                        return (1);
        }

        /*
         * For symbols with the same address and type, we sort them according to
         * a hierarchy:
         *
         *      1. weak symbols (common name)
         *      2. global symbols (external name)
         *      3. local symbols
         */
        if (GELF_ST_BIND(syma->se_sym.st_info) !=
            GELF_ST_BIND(symb->se_sym.st_info)) {
                if (GELF_ST_BIND(syma->se_sym.st_info) == STB_WEAK)
                        return (-1);
                if (GELF_ST_BIND(symb->se_sym.st_info) == STB_WEAK)
                        return (1);

                if (GELF_ST_BIND(syma->se_sym.st_info) == STB_GLOBAL)
                        return (-1);
                if (GELF_ST_BIND(symb->se_sym.st_info) == STB_GLOBAL)
                        return (1);
        }

        /*
         * As a last resort, if we have multiple symbols of the same type at the
         * same address, prefer the version with the fewest leading underscores.
         */
        if (aname == NULL)
                return (-1);
        if (bname == NULL)
                return (1);

        while (*aname == '_' && *bname == '_') {
                aname++;
                bname++;
        }

        if (*bname == '_')
                return (-1);
        if (*aname == '_')
                return (1);

        /*
         * Prefer the symbol with the smaller size.
         */
        if (syma->se_sym.st_size < symb->se_sym.st_size)
                return (-1);
        if (syma->se_sym.st_size > symb->se_sym.st_size)
                return (1);

        /*
         * We really do have two identical symbols, choose the one with the
         * shortest name if we can, heuristically taking it to be the most
         * representative.
         */
        alen = strlen(syma->se_name);
        blen = strlen(symb->se_name);

        if (alen < blen)
                return (-1);
        else if (alen > blen)
                return (1);

        /*
         * If all else fails, compare the names, so that we give a stable
         * sort
         */
        return (strcmp(syma->se_name, symb->se_name));
}

/*
 * Construct an optimized symbol table sorted by starting address.
 */
static void
construct_symtab(dis_tgt_t *tgt)
{
        Elf_Scn *scn;
        GElf_Shdr shdr;
        Elf_Data *symdata;
        int i;
        GElf_Word *symshndx = NULL;
        int symshndx_size;
        sym_entry_t *sym;
        sym_entry_t *p_symtab = NULL;
        int nsym = 0; /* count of symbols we're not interested in */

        /*
         * Find the symshndx section, if any
         */
        for (scn = elf_nextscn(tgt->dt_elf, NULL); scn != NULL;
            scn = elf_nextscn(tgt->dt_elf, scn)) {
                if (gelf_getshdr(scn, &shdr) == NULL)
                        break;
                if (shdr.sh_type == SHT_SYMTAB_SHNDX &&
                    shdr.sh_link == tgt->dt_symidx) {
                        Elf_Data        *data;

                        if ((data = elf_getdata(scn, NULL)) != NULL) {
                                symshndx = (GElf_Word *)data->d_buf;
                                symshndx_size = data->d_size /
                                    sizeof (GElf_Word);
                                break;
                        }
                }
        }

        if ((scn = elf_getscn(tgt->dt_elf, tgt->dt_symidx)) == NULL)
                die("%s: failed to get section information", tgt->dt_filename);
        if (gelf_getshdr(scn, &shdr) == NULL)
                die("%s: failed to get section header", tgt->dt_filename);
        if (shdr.sh_entsize == 0)
                die("%s: symbol table has zero size", tgt->dt_filename);

        if ((symdata = elf_getdata(scn, NULL)) == NULL)
                die("%s: failed to get symbol table", tgt->dt_filename);

        tgt->dt_symcount = symdata->d_size / gelf_fsize(tgt->dt_elf, ELF_T_SYM,
            1, EV_CURRENT);

        p_symtab = safe_malloc(tgt->dt_symcount * sizeof (sym_entry_t));

        for (i = 0, sym = p_symtab; i < tgt->dt_symcount; i++) {
                if (gelf_getsym(symdata, i, &(sym->se_sym)) == NULL) {
                        warn("%s: gelf_getsym returned NULL for %d",
                            tgt->dt_filename, i);
                        nsym++;
                        continue;
                }

                /*
                 * We're only interested in data symbols.
                 */
                if (!IS_DATA_TYPE(GELF_ST_TYPE(sym->se_sym.st_info))) {
                        nsym++;
                        continue;
                }

                if (sym->se_sym.st_shndx == SHN_XINDEX && symshndx != NULL) {
                        if (i > symshndx_size) {
                                warn("%s: bad SHNX_XINDEX %d",
                                    tgt->dt_filename, i);
                                sym->se_shndx = -1;
                        } else {
                                sym->se_shndx = symshndx[i];
                        }
                } else {
                        sym->se_shndx = sym->se_sym.st_shndx;
                }

                /* Deal with symbols with special section indicies */
                if (sym->se_shndx == SHN_ABS) {
                        /*
                         * If st_value == 0, references to these
                         * symbols in code are modified in situ
                         * thus we will never attempt to look
                         * them up.
                         */
                        if (sym->se_sym.st_value == 0) {
                                /*
                                 * References to these symbols in code
                                 * are modified in situ by the runtime
                                 * linker and no code on disk will ever
                                 * attempt to look them up.
                                 */
                                nsym++;
                                continue;
                        } else {
                                /*
                                 * If st_value != 0, (such as examining
                                 * something in /system/object/.../object)
                                 * the values should resolve to a value
                                 * within an existing section (such as
                                 * .data).  This also means it never needs
                                 * to have st_value mapped.
                                 */
                                sym++;
                                continue;
                        }
                }

                /*
                 * Ignore the symbol if it has some other special
                 * section index
                 */
                if (sym->se_shndx == SHN_UNDEF ||
                    sym->se_shndx >= SHN_LORESERVE) {
                        nsym++;
                        continue;
                }

                if ((sym->se_name = elf_strptr(tgt->dt_elf, shdr.sh_link,
                    (size_t)sym->se_sym.st_name)) == NULL) {
                        warn("%s: failed to lookup symbol %d name",
                            tgt->dt_filename, i);
                        nsym++;
                        continue;
                }

                /*
                 * If we had to map this section, its symbol value
                 * also needs to be mapped.
                 */
                if (tgt->dt_shnmap[sym->se_shndx].dm_mapped)
                        sym->se_sym.st_value +=
                            tgt->dt_shnmap[sym->se_shndx].dm_start;

                sym++;
        }

        tgt->dt_symcount -= nsym;
        tgt->dt_symtab = realloc(p_symtab, tgt->dt_symcount *
            sizeof (sym_entry_t));

        qsort(tgt->dt_symtab, tgt->dt_symcount, sizeof (sym_entry_t),
            sym_compare);
}

/*
 * Assign virtual address ranges for sections that need it
 */
static void
create_addrmap(dis_tgt_t *tgt)
{
        uint64_t addr;
        int i;

        if (tgt->dt_shnmap == NULL)
                return;

        /* find the greatest used address */
        for (addr = 0, i = 1; i < tgt->dt_shncount; i++)
                if (tgt->dt_shnmap[i].dm_start > addr)
                        addr = tgt->dt_shnmap[i].dm_start +
                            tgt->dt_shnmap[i].dm_length;

        addr = P2ROUNDUP(addr, 0x1000);

        /*
         * Assign section a starting address beyond the largest mapped section
         * if no address was given.
         */
        for (i = 1; i < tgt->dt_shncount; i++) {
                if (tgt->dt_shnmap[i].dm_start != 0)
                        continue;

                tgt->dt_shnmap[i].dm_start = addr;
                tgt->dt_shnmap[i].dm_mapped = B_TRUE;
                addr = P2ROUNDUP(addr + tgt->dt_shnmap[i].dm_length, 0x1000);
        }
}

/*
 * Create a target backed by an ELF file.
 */
dis_tgt_t *
dis_tgt_create(const char *file)
{
        dis_tgt_t *tgt, *current;
        int idx;
        Elf *elf;
        GElf_Ehdr ehdr;
        Elf_Arhdr *arhdr = NULL;
        int cmd;

        if (elf_version(EV_CURRENT) == EV_NONE)
                die("libelf out of date");

        tgt = safe_malloc(sizeof (dis_tgt_t));

        if ((tgt->dt_fd = open(file, O_RDONLY)) < 0) {
                warn("%s: failed opening file, reason: %s", file,
                    strerror(errno));
                free(tgt);
                return (NULL);
        }

        if ((tgt->dt_elf_root =
            elf_begin(tgt->dt_fd, ELF_C_READ, NULL)) == NULL) {
                warn("%s: invalid or corrupt ELF file", file);
                dis_tgt_destroy(tgt);
                return (NULL);
        }

        current = tgt;
        cmd = ELF_C_READ;
        while ((elf = elf_begin(tgt->dt_fd, cmd, tgt->dt_elf_root)) != NULL) {
                size_t shnum = 0;

                if (elf_kind(tgt->dt_elf_root) == ELF_K_AR &&
                    (arhdr = elf_getarhdr(elf)) == NULL) {
                        warn("%s: malformed archive", file);
                        dis_tgt_destroy(tgt);
                        return (NULL);
                }

                /*
                 * Make sure that this Elf file is sane
                 */
                if (gelf_getehdr(elf, &ehdr) == NULL) {
                        if (arhdr != NULL) {
                                /*
                                 * For archives, we drive on in the face of bad
                                 * members.  The "/" and "//" members are
                                 * special, and should be silently ignored.
                                 */
                                if (strcmp(arhdr->ar_name, "/") != 0 &&
                                    strcmp(arhdr->ar_name, "//") != 0)
                                        warn("%s[%s]: invalid file type",
                                            file, arhdr->ar_name);
                                cmd = elf_next(elf);
                                (void) elf_end(elf);
                                continue;
                        }

                        warn("%s: invalid file type", file);
                        dis_tgt_destroy(tgt);
                        return (NULL);
                }

                /*
                 * If we're seeing a new Elf object, then we have an
                 * archive. In this case, we create a new target, and chain it
                 * off the master target.  We can later iterate over these
                 * targets using dis_tgt_next().
                 */
                if (current->dt_elf != NULL) {
                        dis_tgt_t *next = safe_malloc(sizeof (dis_tgt_t));
                        next->dt_elf_root = tgt->dt_elf_root;
                        next->dt_fd = -1;
                        current->dt_next = next;
                        current = next;
                }
                current->dt_elf = elf;
                current->dt_arhdr = arhdr;

                if (elf_getshdrstrndx(elf, &current->dt_shstrndx) == -1) {
                        warn("%s: failed to get section string table for "
                            "file", file);
                        dis_tgt_destroy(tgt);
                        return (NULL);
                }

                if (elf_getshdrnum(elf, &shnum) == -1) {
                        warn("%s: failed to get number of sections in file",
                            file);
                        dis_tgt_destroy(tgt);
                        return (NULL);
                }

                current->dt_shnmap = safe_malloc(sizeof (dis_shnmap_t) *
                    shnum);
                current->dt_shncount = shnum;

                idx = 0;
                dis_tgt_section_iter(current, tgt_scn_init, &idx);
                current->dt_filename = file;

                create_addrmap(current);
                if (current->dt_symidx != 0)
                        construct_symtab(current);

                cmd = elf_next(elf);
        }

        /*
         * Final sanity check.  If we had an archive with no members, then bail
         * out with a nice message.
         */
        if (tgt->dt_elf == NULL) {
                warn("%s: empty archive\n", file);
                dis_tgt_destroy(tgt);
                return (NULL);
        }

        return (tgt);
}

/*
 * Return the filename associated with the target.
 */
const char *
dis_tgt_name(dis_tgt_t *tgt)
{
        return (tgt->dt_filename);
}

/*
 * Return the archive member name, if any.
 */
const char *
dis_tgt_member(dis_tgt_t *tgt)
{
        if (tgt->dt_arhdr)
                return (tgt->dt_arhdr->ar_name);
        else
                return (NULL);
}

/*
 * Return the Elf_Ehdr associated with this target.  Needed to determine which
 * disassembler to use.
 */
void
dis_tgt_ehdr(dis_tgt_t *tgt, GElf_Ehdr *ehdr)
{
        (void) gelf_getehdr(tgt->dt_elf, ehdr);
}

/*
 * Return the next target in the list, if this is an archive.
 */
dis_tgt_t *
dis_tgt_next(dis_tgt_t *tgt)
{
        return (tgt->dt_next);
}

/*
 * Destroy a target and free up any associated memory.
 */
void
dis_tgt_destroy(dis_tgt_t *tgt)
{
        dis_tgt_t *current, *next;

        current = tgt->dt_next;
        while (current != NULL) {
                next = current->dt_next;
                if (current->dt_elf)
                        (void) elf_end(current->dt_elf);
                if (current->dt_symtab)
                        free(current->dt_symtab);
                free(current);
                current = next;
        }

        if (tgt->dt_elf)
                (void) elf_end(tgt->dt_elf);
        if (tgt->dt_elf_root)
                (void) elf_end(tgt->dt_elf_root);

        if (tgt->dt_symtab)
                free(tgt->dt_symtab);

        free(tgt);
}

/*
 * Given an address, return the section it is in and set the offset within
 * the section.
 */
const char *
dis_find_section(dis_tgt_t *tgt, uint64_t addr, off_t *offset)
{
        int i;

        for (i = 1; i < tgt->dt_shncount; i++) {
                if ((addr >= tgt->dt_shnmap[i].dm_start) &&
                    (addr < tgt->dt_shnmap[i].dm_start +
                    tgt->dt_shnmap[i].dm_length)) {
                        *offset = addr - tgt->dt_shnmap[i].dm_start;
                        return (tgt->dt_shnmap[i].dm_name);
                }
        }

        *offset = 0;
        return (NULL);
}

/*
 * Given an address, returns the name of the corresponding symbol, as well as
 * the offset within that symbol.  If no matching symbol is found, then NULL is
 * returned.
 *
 * If 'cache_result' is specified, then we keep track of the resulting symbol.
 * This cached result is consulted first on subsequent lookups in order to avoid
 * unecessary lookups.  This flag should be used for resolving the current PC,
 * as the majority of addresses stay within the current function.
 */
const char *
dis_tgt_lookup(dis_tgt_t *tgt, uint64_t addr, off_t *offset, int cache_result,
    size_t *size, int *isfunc)
{
        int lo, hi, mid;
        sym_entry_t *sym, *osym, *match;
        int found;

        *offset = 0;
        *size = 0;
        if (isfunc != NULL)
                *isfunc = 0;

        if (tgt->dt_symcache != NULL &&
            addr >= tgt->dt_symcache->se_sym.st_value &&
            addr < tgt->dt_symcache->se_sym.st_value +
            tgt->dt_symcache->se_sym.st_size) {
                sym = tgt->dt_symcache;
                *offset = addr - sym->se_sym.st_value;
                *size = sym->se_sym.st_size;
                if (isfunc != NULL)
                        *isfunc = (GELF_ST_TYPE(sym->se_sym.st_info) ==
                            STT_FUNC);
                return (sym->se_name);
        }

        lo = 0;
        hi = (tgt->dt_symcount - 1);
        found = 0;
        match = osym = NULL;
        while (lo <= hi) {
                mid = (lo + hi) / 2;

                sym = &tgt->dt_symtab[mid];

                if (addr >= sym->se_sym.st_value &&
                    addr < sym->se_sym.st_value + sym->se_sym.st_size &&
                    (!found || sym->se_sym.st_value > osym->se_sym.st_value)) {
                        osym = sym;
                        found = 1;
                } else if (addr == sym->se_sym.st_value) {
                        /*
                         * Particularly for .plt objects, it's possible to have
                         * a zero sized object.  We want to return this, but we
                         * want it to be a last resort.
                         */
                        match = sym;
                }

                if (addr < sym->se_sym.st_value)
                        hi = mid - 1;
                else
                        lo = mid + 1;
        }

        if (!found) {
                if (match)
                        osym = match;
                else
                        return (NULL);
        }

        /*
         * Walk backwards to find the best match.
         */
        do {
                sym = osym;

                if (osym == tgt->dt_symtab)
                        break;

                osym = osym - 1;
        } while ((sym->se_sym.st_value == osym->se_sym.st_value) &&
            (addr >= osym->se_sym.st_value) &&
            (addr < osym->se_sym.st_value + osym->se_sym.st_size));

        if (cache_result)
                tgt->dt_symcache = sym;

        *offset = addr - sym->se_sym.st_value;
        *size = sym->se_sym.st_size;
        if (isfunc)
                *isfunc = (GELF_ST_TYPE(sym->se_sym.st_info) == STT_FUNC);

        return (sym->se_name);
}

/*
 * Given an address, return the starting offset of the next symbol in the file.
 * Only needed on variable length instruction architectures.
 */
off_t
dis_tgt_next_symbol(dis_tgt_t *tgt, uint64_t addr)
{
        sym_entry_t *sym;

        sym = (tgt->dt_symcache != NULL) ? tgt->dt_symcache : tgt->dt_symtab;

        while (sym != (tgt->dt_symtab + tgt->dt_symcount)) {
                if (sym->se_sym.st_value >= addr)
                        return (sym->se_sym.st_value - addr);
                sym++;
        }

        return (0);
}

/*
 * Iterate over all sections in the target, executing the given callback for
 * each.
 */
void
dis_tgt_section_iter(dis_tgt_t *tgt, section_iter_f func, void *data)
{
        dis_scn_t sdata;
        Elf_Scn *scn;
        int idx;

        for (scn = elf_nextscn(tgt->dt_elf, NULL), idx = 1; scn != NULL;
            scn = elf_nextscn(tgt->dt_elf, scn), idx++) {

                if (gelf_getshdr(scn, &sdata.ds_shdr) == NULL) {
                        warn("%s: failed to get section %d header",
                            tgt->dt_filename, idx);
                        continue;
                }

                if ((sdata.ds_name = elf_strptr(tgt->dt_elf, tgt->dt_shstrndx,
                    sdata.ds_shdr.sh_name)) == NULL) {
                        warn("%s: failed to get section %d name",
                            tgt->dt_filename, idx);
                        continue;
                }

                if ((sdata.ds_data = elf_getdata(scn, NULL)) == NULL) {
                        warn("%s: failed to get data for section '%s'",
                            tgt->dt_filename, sdata.ds_name);
                        continue;
                }

                /*
                 * dis_tgt_section_iter is also used before the section map
                 * is initialized, so only check when we need to.  If the
                 * section map is uninitialized, it will return 0 and have
                 * no net effect.
                 */
                if (sdata.ds_shdr.sh_addr == 0)
                        sdata.ds_shdr.sh_addr = tgt->dt_shnmap[idx].dm_start;

                func(tgt, &sdata, data);
        }
}

/*
 * Return 1 if the given section contains text, 0 otherwise.
 */
int
dis_section_istext(dis_scn_t *scn)
{
        return ((scn->ds_shdr.sh_type == SHT_PROGBITS) &&
            (scn->ds_shdr.sh_flags == (SHF_ALLOC | SHF_EXECINSTR)));
}

/*
 * Return a pointer to the section data.
 */
void *
dis_section_data(dis_scn_t *scn)
{
        return (scn->ds_data->d_buf);
}

/*
 * Return the size of the section data.
 */
size_t
dis_section_size(dis_scn_t *scn)
{
        return (scn->ds_data->d_size);
}

/*
 * Return the address for the given section.
 */
uint64_t
dis_section_addr(dis_scn_t *scn)
{
        return (scn->ds_shdr.sh_addr);
}

/*
 * Return the name of the current section.
 */
const char *
dis_section_name(dis_scn_t *scn)
{
        return (scn->ds_name);
}

/*
 * Create an allocated copy of the given section
 */
dis_scn_t *
dis_section_copy(dis_scn_t *scn)
{
        dis_scn_t *new;

        new = safe_malloc(sizeof (dis_scn_t));
        (void) memcpy(new, scn, sizeof (dis_scn_t));

        return (new);
}

/*
 * Free section memory
 */
void
dis_section_free(dis_scn_t *scn)
{
        free(scn);
}

/*
 * Iterate over all functions in the target, executing the given callback for
 * each one.
 */
void
dis_tgt_function_iter(dis_tgt_t *tgt, function_iter_f func, void *data)
{
        int i;
        sym_entry_t *sym;
        dis_func_t df;
        Elf_Scn *scn;
        GElf_Shdr       shdr;

        for (i = 0, sym = tgt->dt_symtab; i < tgt->dt_symcount; i++, sym++) {

                /* ignore non-functions */
                if ((GELF_ST_TYPE(sym->se_sym.st_info) != STT_FUNC) ||
                    (sym->se_name == NULL) ||
                    (sym->se_sym.st_size == 0) ||
                    (sym->se_shndx >= SHN_LORESERVE))
                        continue;

                /* get the ELF data associated with this function */
                if ((scn = elf_getscn(tgt->dt_elf, sym->se_shndx)) == NULL ||
                    gelf_getshdr(scn, &shdr) == NULL ||
                    (df.df_data = elf_getdata(scn, NULL)) == NULL ||
                    df.df_data->d_size == 0) {
                        warn("%s: failed to read section %d",
                            tgt->dt_filename, sym->se_shndx);
                        continue;
                }

                if (tgt->dt_shnmap[sym->se_shndx].dm_mapped)
                        shdr.sh_addr = tgt->dt_shnmap[sym->se_shndx].dm_start;

                /*
                 * Verify that the address lies within the section that we think
                 * it does.
                 */
                if (sym->se_sym.st_value < shdr.sh_addr ||
                    (sym->se_sym.st_value + sym->se_sym.st_size) >
                    (shdr.sh_addr + shdr.sh_size)) {
                        warn("%s: bad section %d for address %p",
                            tgt->dt_filename, sym->se_sym.st_shndx,
                            sym->se_sym.st_value);
                        continue;
                }

                df.df_sym = sym;
                df.df_offset = sym->se_sym.st_value - shdr.sh_addr;

                func(tgt, &df, data);
        }
}

/*
 * Return the data associated with a given function.
 */
void *
dis_function_data(dis_func_t *func)
{
        return ((char *)func->df_data->d_buf + func->df_offset);
}

/*
 * Return the size of a function.
 */
size_t
dis_function_size(dis_func_t *func)
{
        return (func->df_sym->se_sym.st_size);
}

/*
 * Return the address of a function.
 */
uint64_t
dis_function_addr(dis_func_t *func)
{
        return (func->df_sym->se_sym.st_value);
}

/*
 * Return the name of the function
 */
const char *
dis_function_name(dis_func_t *func)
{
        return (func->df_sym->se_name);
}

/*
 * Return a copy of a function.
 */
dis_func_t *
dis_function_copy(dis_func_t *func)
{
        dis_func_t *new;

        new = safe_malloc(sizeof (dis_func_t));
        (void) memcpy(new, func, sizeof (dis_func_t));

        return (new);
}

/*
 * Free function memory
 */
void
dis_function_free(dis_func_t *func)
{
        free(func);
}