/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/isa_defs.h>
#include <sys/link.h>
#include <strings.h>
#include <stdlib.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_io_impl.h>
#include <mdb/mdb_gelf.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb.h>
#define GST_GROW 2 /* Mutable symbol table growth multiplier */
#define GST_DEFSZ 16 /* Mutable symbol table initial size */
#define GST_NVFLG (MDB_NV_EXTNAME | MDB_NV_SILENT)
static const char *gelf_strtab; /* Active string table for qsort callbacks */
static mdb_gelf_file_t *
gelf_sect_init(mdb_gelf_file_t *gf)
{
mdb_gelf_sect_t *gsp, *shstr = &gf->gf_sects[gf->gf_shstrndx];
size_t i;
GElf_Half npbit = 0;
GElf_Shdr *shp;
GElf_Phdr *gpp;
if (gf->gf_mode == GF_PROGRAM)
gf->gf_shnum = 0; /* Simplifies other code paths */
if (gf->gf_shnum == 0)
return (gf); /* If no section headers we're done here */
if (IOP_SEEK(gf->gf_io, shstr->gs_shdr.sh_offset, SEEK_SET) == -1) {
warn("failed to seek %s to shdr strings", IOP_NAME(gf->gf_io));
return (NULL);
}
shstr->gs_data = mdb_zalloc(shstr->gs_shdr.sh_size + 1, UM_SLEEP);
if (IOP_READ(gf->gf_io, shstr->gs_data, shstr->gs_shdr.sh_size) !=
shstr->gs_shdr.sh_size) {
warn("failed to read %s shdr strings", IOP_NAME(gf->gf_io));
mdb_free(shstr->gs_data, shstr->gs_shdr.sh_size);
return (NULL);
}
for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) {
shp = &gsp->gs_shdr;
gsp->gs_name = (const char *)shstr->gs_data + shp->sh_name;
if (shp->sh_name >= shstr->gs_shdr.sh_size) {
warn("section name for %s:[%u] is corrupt: %u\n",
IOP_NAME(gf->gf_io), i, shp->sh_name);
gsp->gs_name = shstr->gs_data; /* empty string */
}
if (shp->sh_type == SHT_PROGBITS && (shp->sh_flags & SHF_ALLOC))
npbit++; /* Keep count for ET_REL code below */
}
/*
* If the file is of type ET_REL, we would still like to provide file
* i/o using the mdb_gelf_rw() function defined below. To simplify
* things, we forge up a sequence of Phdrs based on Shdrs which have
* been marked SHF_ALLOC and are of type SHT_PROGBITS. We convert
* relevant Shdr fields to their Phdr equivalents, and then set the
* p_vaddr (virtual base address) to the section's file offset.
* This allows us to relocate a given symbol by simply incrementing
* its st_value by the file offset of the section corresponding to
* its st_shndx, and then perform i/o to read or write the symbol's
* value in the object file.
*/
if (gf->gf_ehdr.e_type == ET_REL && npbit != 0) {
gf->gf_phdrs = mdb_zalloc(sizeof (GElf_Phdr) * npbit, UM_SLEEP);
gf->gf_phnum = npbit;
gf->gf_npload = npbit;
gpp = gf->gf_phdrs;
gsp = gf->gf_sects;
for (i = 0; i < gf->gf_shnum; i++, gsp++) {
shp = &gsp->gs_shdr;
if ((shp->sh_type == SHT_PROGBITS) &&
(shp->sh_flags & SHF_ALLOC)) {
gpp->p_type = PT_LOAD;
gpp->p_flags = PF_R;
if (shp->sh_flags & SHF_EXECINSTR)
gpp->p_flags |= PF_X;
if (shp->sh_flags & SHF_WRITE)
gpp->p_flags |= PF_W;
gpp->p_offset = shp->sh_offset;
gpp->p_vaddr = shp->sh_offset;
gpp->p_filesz = shp->sh_size;
gpp->p_memsz = shp->sh_size;
gpp->p_align = shp->sh_addralign;
gpp++;
}
}
}
return (gf);
}
void *
mdb_gelf_sect_load(mdb_gelf_file_t *gf, mdb_gelf_sect_t *gsp)
{
ssize_t nbytes;
if (gsp->gs_data != NULL)
return (gsp->gs_data);
mdb_dprintf(MDB_DBG_ELF, "loading %s:%s (%lu bytes)\n",
IOP_NAME(gf->gf_io), gsp->gs_name, (ulong_t)gsp->gs_shdr.sh_size);
gsp->gs_data = mdb_alloc(gsp->gs_shdr.sh_size, UM_SLEEP);
if (IOP_SEEK(gf->gf_io, gsp->gs_shdr.sh_offset, SEEK_SET) == -1) {
warn("failed to seek to start of %s:%s",
IOP_NAME(gf->gf_io), gsp->gs_name);
goto err;
}
nbytes = IOP_READ(gf->gf_io, gsp->gs_data, gsp->gs_shdr.sh_size);
if (nbytes < 0) {
warn("failed to read %s:%s", IOP_NAME(gf->gf_io), gsp->gs_name);
goto err;
}
if (nbytes < gsp->gs_shdr.sh_size) {
mdb_dprintf(MDB_DBG_ELF, "only %ld of %llu bytes of %s:%s "
"could be read\n", (long)nbytes, (u_longlong_t)
gsp->gs_shdr.sh_size, IOP_NAME(gf->gf_io), gsp->gs_name);
bzero((char *)gsp->gs_data + nbytes,
(size_t)gsp->gs_shdr.sh_size - nbytes);
}
return (gsp->gs_data);
err:
mdb_free(gsp->gs_data, sizeof (gsp->gs_shdr.sh_size));
gsp->gs_data = NULL;
return (NULL);
}
void
mdb_gelf_ehdr_to_gehdr(Ehdr *src, GElf_Ehdr *dst)
{
bcopy(src->e_ident, dst->e_ident, sizeof (dst->e_ident));
dst->e_type = src->e_type;
dst->e_machine = src->e_machine;
dst->e_version = src->e_version;
dst->e_entry = src->e_entry;
dst->e_phoff = src->e_phoff;
dst->e_shoff = src->e_shoff;
dst->e_flags = src->e_flags;
dst->e_ehsize = src->e_ehsize;
dst->e_phentsize = src->e_phentsize;
dst->e_phnum = src->e_phnum;
dst->e_shentsize = src->e_shentsize;
dst->e_shnum = src->e_shnum;
dst->e_shstrndx = src->e_shstrndx;
}
static GElf_Shdr *
gelf32_to_shdr(const Elf32_Shdr *src, GElf_Shdr *dst)
{
if (src != NULL) {
dst->sh_name = src->sh_name;
dst->sh_type = src->sh_type;
dst->sh_flags = src->sh_flags;
dst->sh_addr = src->sh_addr;
dst->sh_offset = src->sh_offset;
dst->sh_size = src->sh_size;
dst->sh_link = src->sh_link;
dst->sh_info = src->sh_info;
dst->sh_addralign = src->sh_addralign;
dst->sh_entsize = src->sh_entsize;
return (dst);
}
return (NULL);
}
static GElf_Shdr *
gelf64_to_shdr(const Elf64_Shdr *src, GElf_Shdr *dst)
{
if (src != NULL) {
bcopy(src, dst, sizeof (Elf64_Shdr));
return (dst);
}
return (NULL);
}
static mdb_gelf_file_t *
gelf_shdrs_init(mdb_gelf_file_t *gf, size_t shdr_size,
GElf_Shdr *(*elf2gelf)(const void *, GElf_Shdr *))
{
caddr_t shdrs, shp;
size_t i;
mdb_gelf_sect_t *gsp;
size_t nbytes;
mdb_dprintf(MDB_DBG_ELF, "loading %s section headers (%u entries)\n",
IOP_NAME(gf->gf_io), gf->gf_shnum);
if (gf->gf_shnum == 0)
return (gf);
if (IOP_SEEK(gf->gf_io, (off64_t)gf->gf_ehdr.e_shoff, SEEK_SET) == -1) {
warn("failed to seek %s to shdrs", IOP_NAME(gf->gf_io));
return (NULL);
}
nbytes = shdr_size * gf->gf_shnum;
shdrs = mdb_alloc(nbytes, UM_SLEEP);
if (IOP_READ(gf->gf_io, shdrs, nbytes) != nbytes) {
warn("failed to read %s section headers", IOP_NAME(gf->gf_io));
mdb_free(shdrs, nbytes);
return (NULL);
}
gf->gf_sects = mdb_zalloc(sizeof (mdb_gelf_sect_t) * gf->gf_shnum,
UM_SLEEP);
shp = shdrs;
gsp = gf->gf_sects;
for (i = 0; i < gf->gf_shnum; i++, shp += shdr_size, gsp++)
(void) elf2gelf(shp, &gsp->gs_shdr);
mdb_free(shdrs, nbytes);
return (gf);
}
static GElf_Phdr *
gelf32_to_phdr(const Elf32_Phdr *src, GElf_Phdr *dst)
{
if (src != NULL) {
dst->p_type = src->p_type;
dst->p_offset = src->p_offset;
dst->p_vaddr = src->p_vaddr;
dst->p_paddr = src->p_paddr;
dst->p_filesz = src->p_filesz;
dst->p_memsz = src->p_memsz;
dst->p_flags = src->p_flags;
dst->p_align = src->p_align;
return (dst);
}
return (NULL);
}
static GElf_Phdr *
gelf64_to_phdr(const Elf64_Phdr *src, GElf_Phdr *dst)
{
if (src != NULL) {
bcopy(src, dst, sizeof (Elf64_Phdr));
return (dst);
}
return (NULL);
}
static int
gelf_phdr_compare(const void *lp, const void *rp)
{
GElf_Phdr *lhs = (GElf_Phdr *)lp;
GElf_Phdr *rhs = (GElf_Phdr *)rp;
/*
* If both p_type fields are PT_LOAD, we want to sort by vaddr.
* Exception is that p_vaddr == 0 means ignore this (put at end).
*/
if (lhs->p_type == PT_LOAD && rhs->p_type == PT_LOAD) {
if (lhs->p_vaddr != rhs->p_vaddr) {
if (lhs->p_vaddr == 0)
return (1); /* lhs is "greater" */
if (rhs->p_vaddr == 0)
return (-1); /* rhs is "greater" */
return (lhs->p_vaddr > rhs->p_vaddr ? 1 : -1);
}
return (0);
}
/*
* If the p_type fields don't match, we need to make sure that PT_LOAD
* entries are considered "less" (i.e. move towards the beginning
* of the array we are sorting)
*/
if (lhs->p_type != rhs->p_type) {
if (lhs->p_type == PT_LOAD)
return (-1); /* rhs is "greater" */
if (rhs->p_type == PT_LOAD)
return (1); /* lhs is "greater" */
return (lhs->p_type > rhs->p_type ? 1 : -1);
}
/*
* If the p_type is the same but neither is PT_LOAD, then
* just sort by file offset (doesn't really matter)
*/
if (lhs->p_offset != rhs->p_offset)
return (lhs->p_offset > rhs->p_offset ? 1 : -1);
return (0);
}
static mdb_gelf_file_t *
gelf_phdrs_init(mdb_gelf_file_t *gf, size_t phdr_size,
GElf_Phdr *(*elf2gelf)(const void *, GElf_Phdr *))
{
caddr_t phdrs, php;
size_t i;
GElf_Phdr *gpp;
size_t nbytes;
mdb_dprintf(MDB_DBG_ELF, "loading %s program headers (%lu entries)\n",
IOP_NAME(gf->gf_io), gf->gf_phnum);
if (gf->gf_phnum == 0)
return (gf);
if (IOP_SEEK(gf->gf_io, (off64_t)gf->gf_ehdr.e_phoff, SEEK_SET) == -1) {
warn("failed to seek %s to phdrs", IOP_NAME(gf->gf_io));
return (NULL);
}
nbytes = phdr_size * gf->gf_phnum;
phdrs = mdb_alloc(nbytes, UM_SLEEP);
if (IOP_READ(gf->gf_io, phdrs, nbytes) != nbytes) {
warn("failed to read %s program headers", IOP_NAME(gf->gf_io));
mdb_free(phdrs, nbytes);
return (NULL);
}
gf->gf_phdrs = mdb_zalloc(sizeof (GElf_Phdr) * gf->gf_phnum, UM_SLEEP);
php = phdrs;
gpp = gf->gf_phdrs;
/*
* Iterate through the list of phdrs locating those that are of type
* PT_LOAD; increment gf_npload so we know how many are loadable.
*/
for (i = 0; i < gf->gf_phnum; i++, php += phdr_size, gpp++) {
(void) elf2gelf(php, gpp);
if (gpp->p_type != PT_LOAD)
continue;
mdb_dprintf(MDB_DBG_ELF, "PT_LOAD va=0x%llx flags=0x%x "
"memsz=%llu filesz=%llu off=%llu\n", (u_longlong_t)
gpp->p_vaddr, gpp->p_flags, (u_longlong_t)gpp->p_memsz,
(u_longlong_t)gpp->p_filesz, (u_longlong_t)gpp->p_offset);
gf->gf_npload++;
}
/*
* Now we sort the phdrs array using a comparison routine which
* arranges for the PT_LOAD phdrs with non-zero virtual addresses
* to come first sorted by virtual address. This means that we
* can access the complete phdr table by examining the array
* gf->gf_phdrs[0 .. gf->gf_phnum - 1], and we can access a sorted
* array of valid PT_LOAD pdhrs by examining the array
* gf->gf_phdrs[0 .. gf->gf_npload - 1].
*/
qsort(gf->gf_phdrs, gf->gf_phnum, sizeof (GElf_Phdr),
gelf_phdr_compare);
/*
* Locate the PT_DYNAMIC Phdr if one is present; we save this
* Phdr pointer in gf->gf_dynp for future use.
*/
for (gpp = gf->gf_phdrs, i = 0; i < gf->gf_phnum; i++, gpp++) {
if (gpp->p_type == PT_DYNAMIC) {
mdb_dprintf(MDB_DBG_ELF, "PT_DYNAMIC "
"filesize = %lluULL off=%lluULL\n",
(u_longlong_t)gpp->p_filesz,
(u_longlong_t)gpp->p_offset);
gf->gf_dynp = gpp;
break;
}
}
mdb_free(phdrs, nbytes);
return (gf);
}
static GElf_Dyn *
gelf32_to_dyn(const Elf32_Dyn *src, GElf_Dyn *dst)
{
if (src != NULL) {
dst->d_tag = (GElf_Xword)(Elf32_Word)src->d_tag;
dst->d_un.d_ptr = src->d_un.d_ptr;
return (dst);
}
return (NULL);
}
static GElf_Dyn *
gelf64_to_dyn(const Elf64_Dyn *src, GElf_Dyn *dst)
{
if (src != NULL) {
bcopy(src, dst, sizeof (Elf64_Dyn));
return (dst);
}
return (NULL);
}
static GElf_Xword
gelf_dyn_lookup(mdb_gelf_file_t *gf, GElf_Xword tag)
{
size_t i;
for (i = 0; i < gf->gf_ndyns; i++) {
if (gf->gf_dyns[i].d_tag == tag)
return (gf->gf_dyns[i].d_un.d_val);
}
return ((GElf_Xword)-1L);
}
void
mdb_gelf_dyns_set(mdb_gelf_file_t *gf, void *dyns, size_t dyns_sz)
{
size_t ndyns, i, dyn_size;
caddr_t dp;
GElf_Dyn *gdp;
if (gf->gf_dyns != NULL) {
/* Free the existing dyn entries */
free(gf->gf_dyns);
gf->gf_dyns = NULL;
gf->gf_ndyns = 0;
}
if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32)
dyn_size = sizeof (Elf32_Dyn);
else
dyn_size = sizeof (Elf64_Dyn);
ndyns = dyns_sz / dyn_size;
gf->gf_dyns = mdb_zalloc(sizeof (GElf_Dyn) * ndyns, UM_SLEEP);
gf->gf_ndyns = ndyns;
dp = dyns;
gdp = gf->gf_dyns;
if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
for (i = 0; i < ndyns; i++, dp += dyn_size, gdp++) {
/* LINTED - alignment */
(void) gelf32_to_dyn((Elf32_Dyn *)dp, gdp);
}
} else {
for (i = 0; i < ndyns; i++, dp += dyn_size, gdp++) {
/* LINTED - alignment */
(void) gelf64_to_dyn((Elf64_Dyn *)dp, gdp);
}
}
}
static GElf_Dyn *
gelf_dyns_init(mdb_gelf_file_t *gf, size_t dyn_size,
GElf_Dyn *(*elf2gelf)(const void *, GElf_Dyn *))
{
size_t nbytes, ndyns, i;
caddr_t dyns, dp;
GElf_Dyn *gdp;
off64_t dyn_addr;
if (gf->gf_dyns != NULL)
return (gf->gf_dyns); /* Already loaded */
if (gf->gf_dynp == NULL)
return (NULL); /* No PT_DYNAMIC entry was found */
nbytes = gf->gf_dynp->p_filesz;
ndyns = nbytes / dyn_size;
/*
* If this is an executable in PROGRAM view, then p_vaddr is an
* absolute address; we need to subtract the virtual base address of
* the mapping. In FILE view, dyn_addr is just the file offset.
*/
if (gf->gf_mode == GF_PROGRAM) {
if (gf->gf_ehdr.e_type == ET_EXEC && gf->gf_npload != 0)
dyn_addr = gf->gf_dynp->p_vaddr - gf->gf_phdrs->p_vaddr;
else
dyn_addr = gf->gf_dynp->p_vaddr;
} else {
mdb_gelf_sect_t *gsp = gf->gf_sects;
for (i = 0; i < gf->gf_shnum; i++, gsp++) {
if (gsp->gs_shdr.sh_type == SHT_DYNAMIC) {
dyn_addr = gsp->gs_shdr.sh_offset;
break;
}
}
if (i == gf->gf_shnum)
return (NULL); /* No SHT_DYNAMIC entry was found */
}
mdb_dprintf(MDB_DBG_ELF, "loading _DYNAMIC[] (%lu entries) "
"from offset %llx\n", (ulong_t)ndyns, (longlong_t)dyn_addr);
if (IOP_SEEK(gf->gf_io, dyn_addr, SEEK_SET) == -1) {
warn("failed to seek %s to _DYNAMIC", IOP_NAME(gf->gf_io));
return (NULL);
}
dyns = mdb_alloc(nbytes, UM_SLEEP);
if (IOP_READ(gf->gf_io, dyns, nbytes) != nbytes) {
warn("failed to read %s:_DYNAMIC", IOP_NAME(gf->gf_io));
mdb_free(dyns, nbytes);
return (NULL);
}
gf->gf_dyns = mdb_zalloc(sizeof (GElf_Dyn) * ndyns, UM_SLEEP);
gf->gf_ndyns = ndyns;
dp = dyns;
gdp = gf->gf_dyns;
for (i = 0; i < ndyns; i++, dp += dyn_size, gdp++)
(void) elf2gelf(dp, gdp);
mdb_free(dyns, nbytes);
return (gf->gf_dyns);
}
static mdb_gelf_file_t *
gelf32_init(mdb_gelf_file_t *gf, mdb_io_t *io, const Elf32_Ehdr *ehdr)
{
/*
* Convert the Elf32_Ehdr to a GElf_Ehdr
*/
bcopy(ehdr->e_ident, gf->gf_ehdr.e_ident, EI_NIDENT);
gf->gf_ehdr.e_type = ehdr->e_type;
gf->gf_ehdr.e_machine = ehdr->e_machine;
gf->gf_ehdr.e_version = ehdr->e_version;
gf->gf_ehdr.e_entry = ehdr->e_entry;
gf->gf_ehdr.e_phoff = ehdr->e_phoff;
gf->gf_ehdr.e_shoff = ehdr->e_shoff;
gf->gf_ehdr.e_flags = ehdr->e_flags;
gf->gf_ehdr.e_ehsize = ehdr->e_ehsize;
gf->gf_ehdr.e_phentsize = ehdr->e_phentsize;
gf->gf_ehdr.e_phnum = ehdr->e_phnum;
gf->gf_ehdr.e_shentsize = ehdr->e_shentsize;
gf->gf_ehdr.e_shnum = ehdr->e_shnum;
gf->gf_ehdr.e_shstrndx = ehdr->e_shstrndx;
gf->gf_shnum = gf->gf_ehdr.e_shnum;
gf->gf_shstrndx = gf->gf_ehdr.e_shstrndx;
gf->gf_phnum = gf->gf_ehdr.e_phnum;
if ((gf->gf_shnum == 0 && ehdr->e_shoff != 0) ||
gf->gf_shstrndx == SHN_XINDEX || gf->gf_phnum == PN_XNUM) {
Elf32_Shdr shdr0;
if (ehdr->e_shoff == 0)
return (NULL);
if (IOP_SEEK(io, (off64_t)ehdr->e_shoff, SEEK_SET) == -1) {
warn("failed to seek %s", IOP_NAME(io));
return (NULL);
}
if (IOP_READ(io, &shdr0, sizeof (shdr0)) != sizeof (shdr0)) {
warn("failed to read extended ELF header from %s",
IOP_NAME(io));
return (NULL);
}
if (gf->gf_shnum == 0)
gf->gf_shnum = shdr0.sh_size;
if (gf->gf_shstrndx == SHN_XINDEX)
gf->gf_shstrndx = shdr0.sh_link;
if (gf->gf_phnum == PN_XNUM)
gf->gf_phnum = shdr0.sh_info;
}
/*
* Initialize the section and program headers. We skip initializing
* the section headers if this is a program image because they are
* not loadable and thus we can't get at them.
*/
if (gf->gf_mode == GF_FILE && gelf_shdrs_init(gf, sizeof (Elf32_Shdr),
(GElf_Shdr *(*)(const void *, GElf_Shdr *))gelf32_to_shdr) == NULL)
return (NULL);
if (gelf_phdrs_init(gf, sizeof (Elf32_Phdr),
(GElf_Phdr *(*)(const void *, GElf_Phdr *))gelf32_to_phdr) == NULL)
return (NULL);
(void) gelf_dyns_init(gf, sizeof (Elf32_Dyn),
(GElf_Dyn *(*)(const void *, GElf_Dyn *))gelf32_to_dyn);
return (gf);
}
static mdb_gelf_file_t *
gelf64_init(mdb_gelf_file_t *gf, mdb_io_t *io, Elf64_Ehdr *ehdr)
{
/*
* Save a copy of the ELF file header
*/
bcopy(ehdr, &gf->gf_ehdr, sizeof (Elf64_Ehdr));
gf->gf_shnum = gf->gf_ehdr.e_shnum;
gf->gf_shstrndx = gf->gf_ehdr.e_shstrndx;
gf->gf_phnum = gf->gf_ehdr.e_phnum;
if ((gf->gf_shnum == 0 && ehdr->e_shoff != 0) ||
gf->gf_shstrndx == SHN_XINDEX || gf->gf_phnum == PN_XNUM) {
Elf64_Shdr shdr0;
if (ehdr->e_shoff == 0)
return (NULL);
if (IOP_SEEK(io, (off64_t)ehdr->e_shoff, SEEK_SET) == -1) {
warn("failed to seek %s", IOP_NAME(io));
return (NULL);
}
if (IOP_READ(io, &shdr0, sizeof (shdr0)) != sizeof (shdr0)) {
warn("failed to read extended ELF header from %s",
IOP_NAME(io));
return (NULL);
}
if (gf->gf_shnum == 0)
gf->gf_shnum = shdr0.sh_size;
if (gf->gf_shstrndx == SHN_XINDEX)
gf->gf_shstrndx = shdr0.sh_link;
if (gf->gf_phnum == PN_XNUM)
gf->gf_phnum = shdr0.sh_info;
}
/*
* Initialize the section and program headers. We skip initializing
* the section headers if this is a program image because they are
* not loadable and thus we can't get at them.
*/
if (gf->gf_mode == GF_FILE && gelf_shdrs_init(gf, sizeof (Elf64_Shdr),
(GElf_Shdr *(*)(const void *, GElf_Shdr *))gelf64_to_shdr) == NULL)
return (NULL);
if (gelf_phdrs_init(gf, sizeof (Elf64_Phdr),
(GElf_Phdr *(*)(const void *, GElf_Phdr *))gelf64_to_phdr) == NULL)
return (NULL);
(void) gelf_dyns_init(gf, sizeof (Elf64_Dyn),
(GElf_Dyn *(*)(const void *, GElf_Dyn *))gelf64_to_dyn);
return (gf);
}
int
mdb_gelf_check(mdb_io_t *io, Elf32_Ehdr *ehp, GElf_Half etype)
{
#ifdef _BIG_ENDIAN
uchar_t order = ELFDATA2MSB;
#else
uchar_t order = ELFDATA2LSB;
#endif
ssize_t nbytes;
(void) IOP_SEEK(io, (off64_t)0L, SEEK_SET);
nbytes = IOP_READ(io, ehp, sizeof (Elf32_Ehdr));
if (nbytes == -1) {
if (etype != ET_NONE)
warn("failed to read ELF header from %s", IOP_NAME(io));
return (-1);
}
if (nbytes != sizeof (Elf32_Ehdr) ||
bcmp(&ehp->e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0) {
if (etype != ET_NONE)
warn("%s is not an ELF file\n", IOP_NAME(io));
return (-1);
}
if (ehp->e_ident[EI_DATA] != order) {
warn("ELF file %s has different endianness from debugger\n",
IOP_NAME(io));
return (-1);
}
if (ehp->e_version != EV_CURRENT) {
warn("ELF file %s uses different ELF version (%lu) than "
"debugger (%u)\n", IOP_NAME(io),
(ulong_t)ehp->e_version, EV_CURRENT);
return (-1);
}
if (etype != ET_NONE && ehp->e_type != etype) {
warn("ELF file %s is not of the expected type\n", IOP_NAME(io));
return (-1);
}
return (0);
}
mdb_gelf_file_t *
mdb_gelf_create(mdb_io_t *io, GElf_Half etype, int mode)
{
union {
Elf32_Ehdr h32;
Elf64_Ehdr h64;
} ehdr;
mdb_gelf_file_t *gf = mdb_zalloc(sizeof (mdb_gelf_file_t), UM_SLEEP);
ASSERT(mode == GF_FILE || mode == GF_PROGRAM);
gf->gf_mode = mode;
/*
* Assign the i/o backend now, but don't hold it until we're sure
* we're going to succeed; otherwise the caller will be responsible
* for mdb_io_destroy()ing it.
*/
gf->gf_io = io;
if (mdb_gelf_check(io, &ehdr.h32, etype) == -1)
goto err;
switch (ehdr.h32.e_ident[EI_CLASS]) {
case ELFCLASS32:
gf = gelf32_init(gf, io, &ehdr.h32);
break;
case ELFCLASS64:
if (IOP_SEEK(io, (off64_t)0L, SEEK_SET) == -1) {
warn("failed to seek %s", IOP_NAME(io));
goto err;
}
if (IOP_READ(io, &ehdr.h64, sizeof (ehdr.h64)) !=
sizeof (ehdr.h64)) {
warn("failed to read ELF header from %s", IOP_NAME(io));
goto err;
}
gf = gelf64_init(gf, io, &ehdr.h64);
break;
default:
warn("%s is an unsupported ELF class: %u\n",
IOP_NAME(io), ehdr.h32.e_ident[EI_CLASS]);
goto err;
}
if (gf != NULL && gelf_sect_init(gf) != NULL) {
gf->gf_io = mdb_io_hold(io);
return (gf);
}
err:
if (gf != NULL) {
if (gf->gf_sects != NULL) {
mdb_free(gf->gf_sects, gf->gf_shnum *
sizeof (mdb_gelf_sect_t));
}
mdb_free(gf, sizeof (mdb_gelf_file_t));
}
return (NULL);
}
void
mdb_gelf_destroy(mdb_gelf_file_t *gf)
{
mdb_gelf_sect_t *gsp;
size_t i;
for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) {
if (gsp->gs_data != NULL)
mdb_free(gsp->gs_data, gsp->gs_shdr.sh_size);
}
mdb_free(gf->gf_sects,
gf->gf_shnum * sizeof (mdb_gelf_sect_t));
mdb_free(gf->gf_phdrs, gf->gf_phnum * sizeof (GElf_Phdr));
mdb_io_rele(gf->gf_io);
mdb_free(gf, sizeof (mdb_gelf_file_t));
}
/*
* Sort comparison function for 32-bit symbol address-to-name lookups. We sort
* symbols by value. If values are equal, we prefer the symbol that is
* non-zero sized, typed, not weak, or lexically first, in that order.
*/
static int
gelf32_sym_compare(const void *lp, const void *rp)
{
Elf32_Sym *lhs = *((Elf32_Sym **)lp);
Elf32_Sym *rhs = *((Elf32_Sym **)rp);
if (lhs->st_value != rhs->st_value)
return (lhs->st_value > rhs->st_value ? 1 : -1);
if ((lhs->st_size == 0) != (rhs->st_size == 0))
return (lhs->st_size == 0 ? 1 : -1);
if ((ELF32_ST_TYPE(lhs->st_info) == STT_NOTYPE) !=
(ELF32_ST_TYPE(rhs->st_info) == STT_NOTYPE))
return (ELF32_ST_TYPE(lhs->st_info) == STT_NOTYPE ? 1 : -1);
if ((ELF32_ST_BIND(lhs->st_info) == STB_WEAK) !=
(ELF32_ST_BIND(rhs->st_info) == STB_WEAK))
return (ELF32_ST_BIND(lhs->st_info) == STB_WEAK ? 1 : -1);
return (strcmp(gelf_strtab + lhs->st_name, gelf_strtab + rhs->st_name));
}
/*
* Sort comparison function for 64-bit symbol address-to-name lookups. We sort
* symbols by value. If values are equal, we prefer the symbol that is
* non-zero sized, typed, not weak, or lexically first, in that order.
*/
static int
gelf64_sym_compare(const void *lp, const void *rp)
{
Elf64_Sym *lhs = *((Elf64_Sym **)lp);
Elf64_Sym *rhs = *((Elf64_Sym **)rp);
if (lhs->st_value != rhs->st_value)
return (lhs->st_value > rhs->st_value ? 1 : -1);
if ((lhs->st_size == 0) != (rhs->st_size == 0))
return (lhs->st_size == 0 ? 1 : -1);
if ((ELF64_ST_TYPE(lhs->st_info) == STT_NOTYPE) !=
(ELF64_ST_TYPE(rhs->st_info) == STT_NOTYPE))
return (ELF64_ST_TYPE(lhs->st_info) == STT_NOTYPE ? 1 : -1);
if ((ELF64_ST_BIND(lhs->st_info) == STB_WEAK) !=
(ELF64_ST_BIND(rhs->st_info) == STB_WEAK))
return (ELF64_ST_BIND(lhs->st_info) == STB_WEAK ? 1 : -1);
return (strcmp(gelf_strtab + lhs->st_name, gelf_strtab + rhs->st_name));
}
static void
gelf32_symtab_sort(mdb_gelf_symtab_t *gst)
{
Elf32_Sym **sympp = (Elf32_Sym **)gst->gst_asmap;
mdb_var_t *v;
mdb_nv_rewind(&gst->gst_nv);
while ((v = mdb_nv_advance(&gst->gst_nv)) != NULL) {
Elf32_Sym *sym = MDB_NV_COOKIE(v);
if (sym->st_value != 0 &&
(ELF32_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size))
*sympp++ = sym;
}
gst->gst_aslen = (size_t)(sympp - (Elf32_Sym **)gst->gst_asmap);
ASSERT(gst->gst_aslen <= gst->gst_asrsv);
gelf_strtab = gst->gst_ssect ? gst->gst_ssect->gs_data : NULL;
qsort(gst->gst_asmap, gst->gst_aslen,
sizeof (Elf32_Sym *), gelf32_sym_compare);
gelf_strtab = NULL;
}
static void
gelf32_symtab_init(mdb_gelf_symtab_t *gst)
{
#if STT_NUM != (STT_TLS + 1)
#error "STT_NUM has grown. update gelf32_symtab_init()"
#endif
const char *base = (const char *)gst->gst_ssect->gs_data;
Elf32_Sym *sym = gst->gst_dsect->gs_data;
mdb_nv_t *nv = &gst->gst_nv;
Elf32_Word ss_size = gst->gst_ssect->gs_shdr.sh_size;
size_t asrsv = 0;
GElf_Word i, n;
if (gst->gst_dsect->gs_shdr.sh_entsize != sizeof (Elf32_Sym)) {
warn("%s sh_entsize %llu != sizeof (Elf32_Sym); "
"using %u instead\n", gst->gst_dsect->gs_name,
(u_longlong_t)gst->gst_dsect->gs_shdr.sh_entsize,
(uint_t)sizeof (Elf32_Sym));
gst->gst_dsect->gs_shdr.sh_entsize = sizeof (Elf32_Sym);
}
n = gst->gst_dsect->gs_shdr.sh_size /
gst->gst_dsect->gs_shdr.sh_entsize;
for (i = 0; i < n; i++, sym++) {
const char *name = base + sym->st_name;
uchar_t type = ELF32_ST_TYPE(sym->st_info);
if (type >= STT_NUM || type == STT_SECTION)
continue; /* skip sections and unknown types */
if (sym->st_name >= ss_size || name[0] < '!' || name[0] > '~') {
if (sym->st_name >= ss_size || name[0] != '\0') {
warn("ignoring %s symbol [%u]: invalid name\n",
gst->gst_dsect->gs_name, i);
sym->st_name = 0;
}
continue; /* skip corrupt or empty names */
}
(void) mdb_nv_insert(nv, name, NULL, (uintptr_t)sym, GST_NVFLG);
if (sym->st_value != 0 &&
(ELF32_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size))
asrsv++; /* reserve space in the address map */
}
if (gst->gst_ehdr->e_type == ET_REL && gst->gst_file != NULL) {
GElf_Word smax = gst->gst_file->gf_shnum;
mdb_gelf_sect_t *gsp;
for (sym = gst->gst_dsect->gs_data, i = 0; i < n; i++, sym++) {
if (sym->st_shndx > SHN_UNDEF && sym->st_shndx < smax) {
gsp = &gst->gst_file->gf_sects[sym->st_shndx];
sym->st_value += gsp->gs_shdr.sh_offset;
if (ELF32_ST_BIND(sym->st_info) != STB_LOCAL ||
sym->st_size != 0)
asrsv++; /* reserve space in asmap */
}
}
}
gst->gst_asmap = mdb_alloc(sizeof (Elf32_Sym *) * asrsv, UM_SLEEP);
gst->gst_asrsv = asrsv;
gelf32_symtab_sort(gst);
}
static void
gelf64_symtab_sort(mdb_gelf_symtab_t *gst)
{
Elf64_Sym **sympp = (Elf64_Sym **)gst->gst_asmap;
mdb_var_t *v;
mdb_nv_rewind(&gst->gst_nv);
while ((v = mdb_nv_advance(&gst->gst_nv)) != NULL) {
Elf64_Sym *sym = MDB_NV_COOKIE(v);
if (sym->st_value != 0 &&
(ELF64_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size))
*sympp++ = sym;
}
gst->gst_aslen = (size_t)(sympp - (Elf64_Sym **)gst->gst_asmap);
ASSERT(gst->gst_aslen <= gst->gst_asrsv);
gelf_strtab = gst->gst_ssect ? gst->gst_ssect->gs_data : NULL;
qsort(gst->gst_asmap, gst->gst_aslen,
sizeof (Elf64_Sym *), gelf64_sym_compare);
gelf_strtab = NULL;
}
static void
gelf64_symtab_init(mdb_gelf_symtab_t *gst)
{
#if STT_NUM != (STT_TLS + 1)
#error "STT_NUM has grown. update gelf64_symtab_init()"
#endif
const char *base = (const char *)gst->gst_ssect->gs_data;
Elf64_Sym *sym = gst->gst_dsect->gs_data;
mdb_nv_t *nv = &gst->gst_nv;
Elf64_Xword ss_size = gst->gst_ssect->gs_shdr.sh_size;
size_t asrsv = 0;
GElf_Word i, n;
if (gst->gst_dsect->gs_shdr.sh_entsize != sizeof (Elf64_Sym)) {
warn("%s sh_entsize %llu != sizeof (Elf64_Sym); "
"using %u instead\n", gst->gst_dsect->gs_name,
(u_longlong_t)gst->gst_dsect->gs_shdr.sh_entsize,
(uint_t)sizeof (Elf64_Sym));
gst->gst_dsect->gs_shdr.sh_entsize = sizeof (Elf64_Sym);
}
n = gst->gst_dsect->gs_shdr.sh_size /
gst->gst_dsect->gs_shdr.sh_entsize;
for (i = 0; i < n; i++, sym++) {
const char *name = base + sym->st_name;
uchar_t type = ELF64_ST_TYPE(sym->st_info);
if (type >= STT_NUM || type == STT_SECTION)
continue; /* skip sections and unknown types */
if (sym->st_name >= ss_size || name[0] < '!' || name[0] > '~') {
if (sym->st_name >= ss_size || name[0] != '\0') {
warn("ignoring %s symbol [%u]: invalid name\n",
gst->gst_dsect->gs_name, i);
sym->st_name = 0;
}
continue; /* skip corrupt or empty names */
}
(void) mdb_nv_insert(nv, name, NULL, (uintptr_t)sym, GST_NVFLG);
if (sym->st_value != 0 &&
(ELF64_ST_BIND(sym->st_info) != STB_LOCAL || sym->st_size))
asrsv++; /* reserve space in the address map */
}
if (gst->gst_ehdr->e_type == ET_REL && gst->gst_file != NULL) {
GElf_Word smax = gst->gst_file->gf_shnum;
mdb_gelf_sect_t *gsp;
for (sym = gst->gst_dsect->gs_data, i = 0; i < n; i++, sym++) {
if (sym->st_shndx > SHN_UNDEF && sym->st_shndx < smax) {
gsp = &gst->gst_file->gf_sects[sym->st_shndx];
sym->st_value += gsp->gs_shdr.sh_offset;
if (ELF64_ST_BIND(sym->st_info) != STB_LOCAL ||
sym->st_size != 0)
asrsv++; /* reserve space in asmap */
}
}
}
gst->gst_asmap = mdb_alloc(sizeof (Elf64_Sym *) * asrsv, UM_SLEEP);
gst->gst_asrsv = asrsv;
gelf64_symtab_sort(gst);
}
mdb_gelf_symtab_t *
mdb_gelf_symtab_create_file(mdb_gelf_file_t *gf, GElf_Word elftype,
uint_t tabid)
{
mdb_gelf_sect_t *gsp;
const char *dsname = NULL;
const char *ssname;
size_t i;
GElf_Word link;
/*
* Examine the sh_link field in the the Elf header to get the name
* of the corresponding strings section
*/
for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) {
if (gsp->gs_shdr.sh_type == elftype) {
dsname = gsp->gs_name;
link = gsp->gs_shdr.sh_link;
break;
}
}
if (dsname == NULL)
return (NULL);
if (link > gf->gf_shnum) {
/*
* Invalid link number due to corrupt elf file.
*/
warn("link number %ud larger than number of sections %d\n",
link, gf->gf_shnum);
return (NULL);
}
ssname = (gf->gf_sects + link)->gs_name;
return (mdb_gelf_symtab_create_file_by_name(gf, dsname, ssname, tabid));
}
mdb_gelf_symtab_t *
mdb_gelf_symtab_create_file_by_name(mdb_gelf_file_t *gf,
const char *dsname, const char *ssname, uint_t tabid)
{
mdb_gelf_symtab_t *gst;
mdb_gelf_sect_t *gsp;
size_t i;
gst = mdb_alloc(sizeof (mdb_gelf_symtab_t), UM_SLEEP);
(void) mdb_nv_create(&gst->gst_nv, UM_SLEEP);
gst->gst_asmap = NULL;
gst->gst_aslen = 0;
gst->gst_asrsv = 0;
gst->gst_ehdr = &gf->gf_ehdr;
gst->gst_file = gf;
gst->gst_dsect = NULL;
gst->gst_ssect = NULL;
gst->gst_id = 0;
gst->gst_tabid = tabid;
for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) {
if (strcmp(gsp->gs_name, dsname) == 0) {
gst->gst_dsect = gsp;
break;
}
}
for (gsp = gf->gf_sects, i = 0; i < gf->gf_shnum; i++, gsp++) {
if (strcmp(gsp->gs_name, ssname) == 0) {
gst->gst_ssect = gsp;
break;
}
}
if (gst->gst_dsect == NULL || gst->gst_ssect == NULL)
goto err; /* Failed to locate data or string section */
if (mdb_gelf_sect_load(gf, gst->gst_dsect) == NULL)
goto err; /* Failed to load data section */
if (mdb_gelf_sect_load(gf, gst->gst_ssect) == NULL)
goto err; /* Failed to load string section */
if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32)
gelf32_symtab_init(gst);
else
gelf64_symtab_init(gst);
return (gst);
err:
mdb_nv_destroy(&gst->gst_nv);
mdb_free(gst, sizeof (mdb_gelf_symtab_t));
return (NULL);
}
mdb_gelf_symtab_t *
mdb_gelf_symtab_create_raw(const GElf_Ehdr *ehdr, const void *dshdr,
void *ddata, const void *sshdr, void *sdata, uint_t tabid)
{
mdb_gelf_symtab_t *gst;
gst = mdb_alloc(sizeof (mdb_gelf_symtab_t), UM_SLEEP);
(void) mdb_nv_create(&gst->gst_nv, UM_SLEEP);
gst->gst_asmap = NULL;
gst->gst_aslen = 0;
gst->gst_asrsv = 0;
gst->gst_ehdr = ehdr;
gst->gst_file = NULL; /* Flag for raw symtab */
gst->gst_id = 0;
gst->gst_tabid = tabid;
gst->gst_dsect = mdb_zalloc(sizeof (mdb_gelf_sect_t), UM_SLEEP);
gst->gst_dsect->gs_name = ".symtab";
gst->gst_dsect->gs_data = ddata;
gst->gst_ssect = mdb_zalloc(sizeof (mdb_gelf_sect_t), UM_SLEEP);
gst->gst_ssect->gs_name = ".strtab";
gst->gst_ssect->gs_data = sdata;
if (ehdr->e_ident[EI_CLASS] == ELFCLASS32) {
(void) gelf32_to_shdr(dshdr, &gst->gst_dsect->gs_shdr);
(void) gelf32_to_shdr(sshdr, &gst->gst_ssect->gs_shdr);
gelf32_symtab_init(gst);
} else {
(void) gelf64_to_shdr(dshdr, &gst->gst_dsect->gs_shdr);
(void) gelf64_to_shdr(sshdr, &gst->gst_ssect->gs_shdr);
gelf64_symtab_init(gst);
}
return (gst);
}
mdb_gelf_symtab_t *
mdb_gelf_symtab_create_dynamic(mdb_gelf_file_t *gf, uint_t tabid)
{
GElf_Addr dt_symtab, dt_strtab, dt_hash;
GElf_Xword dt_syment, dt_strsz;
mdb_gelf_symtab_t *gst;
uint_t hash_h[2];
off64_t base = 0;
ASSERT(gf->gf_mode == GF_PROGRAM);
/*
* Read in and cache the array of GElf_Dyn structures from the
* PT_DYNAMIC phdr. Abort if this is not possible.
*/
if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
(void) gelf_dyns_init(gf, sizeof (Elf32_Dyn),
(GElf_Dyn *(*)(const void *, GElf_Dyn *))gelf32_to_dyn);
} else {
(void) gelf_dyns_init(gf, sizeof (Elf64_Dyn),
(GElf_Dyn *(*)(const void *, GElf_Dyn *))gelf64_to_dyn);
}
/*
* Pre-fetch all the DT_* entries we will need for creating the
* dynamic symbol table; abort if any are missing.
*/
if ((dt_hash = gelf_dyn_lookup(gf, DT_HASH)) == -1L) {
warn("failed to get DT_HASH for %s\n", IOP_NAME(gf->gf_io));
return (NULL);
}
if ((dt_symtab = gelf_dyn_lookup(gf, DT_SYMTAB)) == -1L) {
warn("failed to get DT_SYMTAB for %s\n", IOP_NAME(gf->gf_io));
return (NULL);
}
if ((dt_syment = gelf_dyn_lookup(gf, DT_SYMENT)) == -1L) {
warn("failed to get DT_SYMENT for %s\n", IOP_NAME(gf->gf_io));
return (NULL);
}
if ((dt_strtab = gelf_dyn_lookup(gf, DT_STRTAB)) == -1L) {
warn("failed to get DT_STRTAB for %s\n", IOP_NAME(gf->gf_io));
return (NULL);
}
if ((dt_strsz = gelf_dyn_lookup(gf, DT_STRSZ)) == -1L) {
warn("failed to get DT_STRSZ for %s\n", IOP_NAME(gf->gf_io));
return (NULL);
}
/*
* If this is an executable, then DT_HASH is an absolute address;
* we need to subtract the virtual base address of the mapping.
*/
if (gf->gf_ehdr.e_type == ET_EXEC && gf->gf_npload != 0)
base = (off64_t)gf->gf_phdrs->p_vaddr;
/*
* Read in the header for the DT_HASH: this consists of nbucket
* and nchain values (nchain is the number of hashed symbols).
*/
if (IOP_SEEK(gf->gf_io, (off64_t)dt_hash - base, SEEK_SET) == -1) {
warn("failed to seek ELF file to start of DT_HASH");
return (NULL);
}
if (IOP_READ(gf->gf_io, hash_h, sizeof (hash_h)) != sizeof (hash_h)) {
warn("failed to read DT_HASH header");
return (NULL);
}
gst = mdb_zalloc(sizeof (mdb_gelf_symtab_t), UM_SLEEP);
(void) mdb_nv_create(&gst->gst_nv, UM_SLEEP);
gst->gst_asmap = NULL;
gst->gst_aslen = 0;
gst->gst_asrsv = 0;
gst->gst_ehdr = &gf->gf_ehdr;
gst->gst_file = gf;
gst->gst_id = 0;
gst->gst_tabid = tabid;
gst->gst_dsect = mdb_zalloc(sizeof (mdb_gelf_sect_t), UM_SLEEP);
gst->gst_dsect->gs_name = ".dynsym";
gst->gst_dsect->gs_shdr.sh_offset = dt_symtab - (GElf_Addr)base;
gst->gst_dsect->gs_shdr.sh_size = hash_h[1] * dt_syment;
gst->gst_dsect->gs_shdr.sh_entsize = dt_syment;
gst->gst_ssect = mdb_zalloc(sizeof (mdb_gelf_sect_t), UM_SLEEP);
gst->gst_ssect->gs_name = ".dynstr";
gst->gst_ssect->gs_shdr.sh_offset = dt_strtab - (GElf_Addr)base;
gst->gst_ssect->gs_shdr.sh_size = dt_strsz;
gst->gst_ssect->gs_shdr.sh_entsize = 0;
if (mdb_gelf_sect_load(gf, gst->gst_dsect) == NULL)
goto err;
if (mdb_gelf_sect_load(gf, gst->gst_ssect) == NULL)
goto err;
if (gf->gf_ehdr.e_ident[EI_CLASS] == ELFCLASS32)
gelf32_symtab_init(gst);
else
gelf64_symtab_init(gst);
return (gst);
err:
mdb_gelf_symtab_destroy(gst);
return (NULL);
}
mdb_gelf_symtab_t *
mdb_gelf_symtab_create_mutable(void)
{
mdb_gelf_symtab_t *gst;
static GElf_Ehdr ehdr;
gst = mdb_zalloc(sizeof (mdb_gelf_symtab_t), UM_SLEEP);
(void) mdb_nv_create(&gst->gst_nv, UM_SLEEP);
gst->gst_ehdr = &ehdr;
if (ehdr.e_version == 0) {
#ifdef _LP64
uchar_t class = ELFCLASS64;
#else
uchar_t class = ELFCLASS32;
#endif
#ifdef _BIG_ENDIAN
uchar_t data = ELFDATA2MSB;
#else
uchar_t data = ELFDATA2LSB;
#endif
/*
* Since all mutable symbol tables will use a native Ehdr,
* we can just have a single static copy which they all
* point to and we only need initialize once.
*/
ehdr.e_ident[EI_MAG0] = ELFMAG0;
ehdr.e_ident[EI_MAG1] = ELFMAG1;
ehdr.e_ident[EI_MAG2] = ELFMAG2;
ehdr.e_ident[EI_MAG3] = ELFMAG3;
ehdr.e_ident[EI_CLASS] = class;
ehdr.e_ident[EI_DATA] = data;
ehdr.e_ident[EI_VERSION] = EV_CURRENT;
ehdr.e_type = ET_NONE;
ehdr.e_version = EV_CURRENT;
}
return (gst);
}
void
mdb_gelf_symtab_destroy(mdb_gelf_symtab_t *gst)
{
if (gst->gst_file == NULL) {
if (gst->gst_dsect == NULL && gst->gst_ssect == NULL) {
mdb_var_t *v;
mdb_nv_rewind(&gst->gst_nv);
while ((v = mdb_nv_advance(&gst->gst_nv)) != NULL) {
char *name = (char *)mdb_nv_get_name(v);
mdb_gelf_dsym_t *dsp = mdb_nv_get_cookie(v);
mdb_free(name, strlen(name) + 1);
mdb_free(dsp, sizeof (mdb_gelf_dsym_t));
}
} else {
mdb_free(gst->gst_dsect, sizeof (mdb_gelf_sect_t));
mdb_free(gst->gst_ssect, sizeof (mdb_gelf_sect_t));
}
} else if (gst->gst_file->gf_mode == GF_PROGRAM) {
mdb_gelf_sect_t *dsect = gst->gst_dsect;
mdb_gelf_sect_t *ssect = gst->gst_ssect;
if (dsect->gs_data != NULL)
mdb_free(dsect->gs_data, dsect->gs_shdr.sh_size);
if (ssect->gs_data != NULL)
mdb_free(ssect->gs_data, ssect->gs_shdr.sh_size);
mdb_free(gst->gst_dsect, sizeof (mdb_gelf_sect_t));
mdb_free(gst->gst_ssect, sizeof (mdb_gelf_sect_t));
}
mdb_nv_destroy(&gst->gst_nv);
mdb_free(gst->gst_asmap, gst->gst_asrsv * sizeof (void *));
mdb_free(gst, sizeof (mdb_gelf_symtab_t));
}
size_t
mdb_gelf_symtab_size(mdb_gelf_symtab_t *gst)
{
return (mdb_nv_size(&gst->gst_nv));
}
static GElf_Sym *
gelf32_to_sym(const Elf32_Sym *src, GElf_Sym *dst)
{
if (src != NULL) {
dst->st_name = src->st_name;
dst->st_info = src->st_info;
dst->st_other = src->st_other;
dst->st_shndx = src->st_shndx;
dst->st_value = src->st_value;
dst->st_size = src->st_size;
return (dst);
}
return (NULL);
}
static GElf_Sym *
gelf64_to_sym(const Elf64_Sym *src, GElf_Sym *dst)
{
if (src != NULL) {
bcopy(src, dst, sizeof (GElf_Sym));
return (dst);
}
return (NULL);
}
/*ARGSUSED*/
static GElf_Sym *
gelf64_nocopy(const Elf64_Sym *src, GElf_Sym *dst)
{
return ((GElf_Sym *)src);
}
static const void *
gelf32_sym_search(const Elf32_Sym **asmap, size_t aslen, uintptr_t addr)
{
ulong_t i, mid, lo = 0, hi = aslen - 1;
const Elf32_Sym *symp;
Elf32_Addr v;
size_t size;
if (aslen == 0)
return (NULL);
while (hi - lo > 1) {
mid = (lo + hi) / 2;
if (addr >= asmap[mid]->st_value)
lo = mid;
else
hi = mid;
}
i = addr < asmap[hi]->st_value ? lo : hi;
symp = asmap[i];
v = symp->st_value;
/*
* If the previous entry has the same value, improve our choice. The
* order of equal-valued symbols is determined by gelf32_sym_compare().
*/
while (i-- != 0 && asmap[i]->st_value == v)
symp = asmap[i];
/*
* If an absolute symbol distance was specified, use that; otherwise
* use the ELF symbol size, or 1 byte if the ELF size is zero.
*/
if (mdb.m_symdist == 0)
size = MAX(symp->st_size, 1);
else
size = mdb.m_symdist;
if (addr - symp->st_value < size)
return (symp);
return (NULL);
}
static const void *
gelf64_sym_search(const Elf64_Sym **asmap, size_t aslen, uintptr_t addr)
{
ulong_t i, mid, lo = 0, hi = aslen - 1;
const Elf64_Sym *symp;
Elf64_Addr v;
size_t size;
if (aslen == 0)
return (NULL);
while (hi - lo > 1) {
mid = (lo + hi) / 2;
if (addr >= asmap[mid]->st_value)
lo = mid;
else
hi = mid;
}
i = addr < asmap[hi]->st_value ? lo : hi;
symp = asmap[i];
v = symp->st_value;
/*
* If the previous entry has the same value, improve our choice. The
* order of equal-valued symbols is determined by gelf64_sym_compare().
*/
while (i-- != 0 && asmap[i]->st_value == v)
symp = asmap[i];
/*
* If an absolute symbol distance was specified, use that; otherwise
* use the ELF symbol size, or 1 byte if the ELF size is zero.
*/
if (mdb.m_symdist == 0)
size = MAX(symp->st_size, 1);
else
size = mdb.m_symdist;
if (addr - symp->st_value < size)
return (symp);
return (NULL);
}
const char *
mdb_gelf_sym_name(mdb_gelf_symtab_t *gst, const GElf_Sym *sym)
{
const mdb_gelf_dsym_t *dsp;
if (gst->gst_ssect != NULL)
return ((const char *)gst->gst_ssect->gs_data + sym->st_name);
if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32)
dsp = gelf32_sym_search(gst->gst_asmap,
gst->gst_aslen, sym->st_value);
else
dsp = gelf64_sym_search(gst->gst_asmap,
gst->gst_aslen, sym->st_value);
if (dsp != NULL)
return (mdb_nv_get_name(dsp->ds_var));
return (NULL);
}
int
mdb_gelf_sym_closer(const GElf_Sym *s1, const GElf_Sym *s2, uintptr_t addr)
{
uintptr_t v1 = (uintptr_t)s1->st_value;
uintptr_t v2 = (uintptr_t)s2->st_value;
uintptr_t d1 = v1 > addr ? v1 - addr : addr - v1;
uintptr_t d2 = v2 > addr ? v2 - addr : addr - v2;
return (d1 < d2);
}
int
mdb_gelf_symtab_lookup_by_addr(mdb_gelf_symtab_t *gst, uintptr_t addr,
uint_t flags, char *buf, size_t nbytes, GElf_Sym *sym, uint_t *idp)
{
union {
const mdb_gelf_dsym_t *dsp;
const Elf32_Sym *s32;
const Elf64_Sym *s64;
caddr_t sp;
} u;
const char *name;
if (gst == NULL)
return (set_errno(EMDB_NOSYMADDR));
if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) {
u.s32 = gelf32_sym_search(gst->gst_asmap, gst->gst_aslen, addr);
if (gelf32_to_sym(u.s32, sym) == NULL)
return (set_errno(EMDB_NOSYMADDR));
} else {
u.s64 = gelf64_sym_search(gst->gst_asmap, gst->gst_aslen, addr);
if (gelf64_to_sym(u.s64, sym) == NULL)
return (set_errno(EMDB_NOSYMADDR));
}
if ((flags & GST_EXACT) && (sym->st_value != addr))
return (set_errno(EMDB_NOSYMADDR));
if (gst->gst_ssect != NULL) {
name = (const char *)gst->gst_ssect->gs_data + sym->st_name;
if (idp != NULL) {
*idp = (u.sp - (caddr_t)gst->gst_dsect->gs_data) /
gst->gst_dsect->gs_shdr.sh_entsize;
}
} else {
name = mdb_nv_get_name(u.dsp->ds_var);
if (idp != NULL)
*idp = u.dsp->ds_id;
}
if (nbytes > 0) {
(void) strncpy(buf, name, nbytes - 1);
buf[nbytes - 1] = '\0';
}
return (0);
}
int
mdb_gelf_symtab_lookup_by_name(mdb_gelf_symtab_t *gst, const char *name,
GElf_Sym *sym, uint_t *idp)
{
mdb_var_t *v;
if (gst != NULL && (v = mdb_nv_lookup(&gst->gst_nv, name)) != NULL) {
if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32)
(void) gelf32_to_sym(mdb_nv_get_cookie(v), sym);
else
(void) gelf64_to_sym(mdb_nv_get_cookie(v), sym);
if (idp != NULL) {
if (gst->gst_file == NULL && gst->gst_dsect == NULL) {
mdb_gelf_dsym_t *dsp = mdb_nv_get_cookie(v);
*idp = dsp->ds_id;
} else {
*idp = ((uintptr_t)mdb_nv_get_cookie(v) -
(uintptr_t)gst->gst_dsect->gs_data) /
gst->gst_dsect->gs_shdr.sh_entsize;
}
}
return (0);
}
return (set_errno(EMDB_NOSYM));
}
int
mdb_gelf_symtab_lookup_by_file(mdb_gelf_symtab_t *gst, const char *file,
const char *name, GElf_Sym *sym, uint_t *idp)
{
GElf_Sym *(*s2gelf)(const void *, GElf_Sym *);
size_t sym_size;
caddr_t sp, ep;
mdb_var_t *v;
if (gst == NULL)
return (set_errno(EMDB_NOSYM));
if ((v = mdb_nv_lookup(&gst->gst_nv, file)) == NULL)
return (set_errno(EMDB_NOOBJ));
if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) {
s2gelf = (GElf_Sym *(*)(const void *, GElf_Sym *))gelf32_to_sym;
sym_size = sizeof (Elf32_Sym);
} else {
s2gelf = (GElf_Sym *(*)(const void *, GElf_Sym *))gelf64_to_sym;
sym_size = sizeof (Elf64_Sym);
}
(void) s2gelf(mdb_nv_get_cookie(v), sym);
if (GELF_ST_TYPE(sym->st_info) != STT_FILE)
return (set_errno(EMDB_NOOBJ));
ep = (caddr_t)gst->gst_dsect->gs_data + gst->gst_dsect->gs_shdr.sh_size;
sp = (caddr_t)mdb_nv_get_cookie(v);
/*
* We assume that symbol lookups scoped by source file name are only
* relevant for userland debugging and are a relatively rare request,
* and so we use a simple but inefficient linear search with copying.
*/
for (sp += sym_size; sp < ep; sp += sym_size) {
(void) s2gelf(sp, sym); /* Convert native symbol to GElf */
if (GELF_ST_TYPE(sym->st_info) == STT_SECTION ||
GELF_ST_TYPE(sym->st_info) == STT_FILE ||
GELF_ST_BIND(sym->st_info) != STB_LOCAL)
break; /* End of this file's locals */
if (strcmp(mdb_gelf_sym_name(gst, sym), name) == 0) {
if (idp != NULL) {
*idp = (sp - (caddr_t)
gst->gst_dsect->gs_data) / sym_size;
}
return (0);
}
}
return (set_errno(EMDB_NOSYM));
}
void
mdb_gelf_symtab_iter(mdb_gelf_symtab_t *gst, int (*func)(void *,
const GElf_Sym *, const char *, uint_t), void *private)
{
GElf_Sym *(*s2gelf)(const void *, GElf_Sym *);
GElf_Sym sym, *symp;
size_t sym_size;
if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) {
s2gelf = (GElf_Sym *(*)(const void *, GElf_Sym *))gelf32_to_sym;
sym_size = sizeof (Elf32_Sym);
} else {
s2gelf = (GElf_Sym *(*)(const void *, GElf_Sym *))gelf64_nocopy;
sym_size = sizeof (Elf64_Sym);
}
/*
* If this is a mutable symbol table, we iterate over the hash table
* of symbol names; otherwise we go iterate over the data buffer. For
* non-mutable tables, this means that ::nm will show all symbols,
* including those with duplicate names (not present in gst_nv).
*/
if (gst->gst_file == NULL && gst->gst_dsect == NULL) {
mdb_gelf_dsym_t *dsp;
mdb_var_t *v;
mdb_nv_rewind(&gst->gst_nv);
while ((v = mdb_nv_advance(&gst->gst_nv)) != NULL) {
dsp = mdb_nv_get_cookie(v);
symp = s2gelf(dsp, &sym);
if (func(private, symp, mdb_nv_get_name(v),
dsp->ds_id) == -1)
break;
}
} else {
const char *sbase = gst->gst_ssect->gs_data;
caddr_t sp = gst->gst_dsect->gs_data;
caddr_t ep = sp + gst->gst_dsect->gs_shdr.sh_size;
uint_t i;
for (i = 0; sp < ep; sp += sym_size, i++) {
symp = s2gelf(sp, &sym);
if (func(private, symp, sbase + symp->st_name, i) == -1)
break;
}
}
}
static void
gelf_sym_to_32(const GElf_Sym *src, Elf32_Sym *dst)
{
dst->st_name = src->st_name;
dst->st_info = src->st_info;
dst->st_other = src->st_other;
dst->st_shndx = src->st_shndx;
dst->st_value = (Elf32_Addr)src->st_value;
dst->st_size = (Elf32_Word)src->st_size;
}
static void
gelf_sym_to_64(const GElf_Sym *src, Elf64_Sym *dst)
{
bcopy(src, dst, sizeof (Elf64_Sym));
}
void
mdb_gelf_symtab_insert(mdb_gelf_symtab_t *gst,
const char *name, const GElf_Sym *symp)
{
mdb_gelf_dsym_t *dsp;
mdb_var_t *v;
ASSERT(gst->gst_file == NULL && gst->gst_dsect == NULL);
v = mdb_nv_lookup(&gst->gst_nv, name);
if (v == NULL) {
char *s = mdb_alloc(strlen(name) + 1, UM_SLEEP);
(void) strcpy(s, name);
dsp = mdb_alloc(sizeof (mdb_gelf_dsym_t), UM_SLEEP);
dsp->ds_id = gst->gst_id++;
dsp->ds_var = mdb_nv_insert(&gst->gst_nv, s, NULL,
(uintptr_t)dsp, GST_NVFLG);
gst->gst_aslen++;
ASSERT(gst->gst_aslen == mdb_nv_size(&gst->gst_nv));
if (gst->gst_aslen > gst->gst_asrsv) {
mdb_free(gst->gst_asmap,
sizeof (void *) * gst->gst_asrsv);
gst->gst_asrsv = gst->gst_asrsv != 0 ?
gst->gst_asrsv * GST_GROW : GST_DEFSZ;
gst->gst_asmap = mdb_alloc(sizeof (void *) *
gst->gst_asrsv, UM_SLEEP);
}
} else
dsp = mdb_nv_get_cookie(v);
mdb_dprintf(MDB_DBG_ELF, "added symbol (\"%s\", %llx)\n",
name, (u_longlong_t)symp->st_value);
bcopy(symp, &dsp->ds_sym, sizeof (GElf_Sym));
dsp->ds_sym.st_name = (uintptr_t)mdb_nv_get_name(dsp->ds_var);
if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32) {
gelf_sym_to_32(symp, &dsp->ds_u.ds_s32);
gelf32_symtab_sort(gst);
} else {
gelf_sym_to_64(symp, &dsp->ds_u.ds_s64);
gelf64_symtab_sort(gst);
}
}
void
mdb_gelf_symtab_delete(mdb_gelf_symtab_t *gst,
const char *name, GElf_Sym *symp)
{
mdb_var_t *v;
ASSERT(gst->gst_file == NULL && gst->gst_dsect == NULL);
v = mdb_nv_lookup(&gst->gst_nv, name);
if (v != NULL) {
char *name = (char *)mdb_nv_get_name(v);
mdb_gelf_dsym_t *dsp = mdb_nv_get_cookie(v);
if (symp != NULL)
bcopy(&dsp->ds_sym, symp, sizeof (GElf_Sym));
mdb_dprintf(MDB_DBG_ELF, "removed symbol (\"%s\", %llx)\n",
name, (u_longlong_t)dsp->ds_sym.st_value);
mdb_nv_remove(&gst->gst_nv, v);
gst->gst_aslen--;
ASSERT(gst->gst_aslen == mdb_nv_size(&gst->gst_nv));
mdb_free(name, strlen(name) + 1);
mdb_free(dsp, sizeof (mdb_gelf_dsym_t));
if (gst->gst_ehdr->e_ident[EI_CLASS] == ELFCLASS32)
gelf32_symtab_sort(gst);
else
gelf64_symtab_sort(gst);
}
}
static const GElf_Phdr *
gelf_phdr_lookup(mdb_gelf_file_t *gf, uintptr_t addr)
{
const GElf_Phdr *gpp = gf->gf_phdrs;
size_t i;
for (i = 0; i < gf->gf_npload; i++, gpp++) {
if (addr >= gpp->p_vaddr && addr < gpp->p_vaddr + gpp->p_memsz)
return (gpp);
}
return (NULL);
}
ssize_t
mdb_gelf_rw(mdb_gelf_file_t *gf, void *buf, size_t nbytes, uintptr_t addr,
ssize_t (*prw)(mdb_io_t *, void *, size_t), mdb_gelf_rw_t rw)
{
ssize_t resid = nbytes;
while (resid != 0) {
const GElf_Phdr *php = gelf_phdr_lookup(gf, addr);
uintptr_t mapoff;
ssize_t memlen, filelen, len = 0;
off64_t off;
if (php == NULL)
break; /* No mapping for this address */
mapoff = addr - php->p_vaddr;
memlen = MIN(resid, php->p_memsz - mapoff);
filelen = MIN(resid, php->p_filesz - mapoff);
off = (off64_t)php->p_offset + mapoff;
if (filelen > 0 && (IOP_SEEK(gf->gf_io, off, SEEK_SET) != off ||
(len = prw(gf->gf_io, buf, filelen)) <= 0))
break;
if (rw == GIO_READ && len == filelen && filelen < memlen) {
bzero((char *)buf + len, memlen - filelen);
len += memlen - filelen;
}
resid -= len;
addr += len;
buf = (char *)buf + len;
}
if (resid == nbytes && nbytes != 0)
return (set_errno(EMDB_NOMAP));
return (nbytes - resid);
}
mdb_gelf_sect_t *
mdb_gelf_sect_by_name(mdb_gelf_file_t *gf, const char *name)
{
size_t i;
for (i = 0; i < gf->gf_shnum; i++) {
if (strcmp(gf->gf_sects[i].gs_name, name) == 0)
return (&gf->gf_sects[i]);
}
return (NULL);
}