HelenOS sources

root/uspace/app/taskdump/symtab.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. symtab_load
  2. symtab_delete
  3. symtab_name_to_addr
  4. symtab_addr_to_name
  5. elf_hdr_check
  6. section_hdr_load
  7. chunk_load

/*
 * Copyright (c) 2010 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup debug
 * @{
 */
/** @file Handling of ELF symbol tables.
 *
 * This module allows one to load a symbol table from an ELF file and
 * use it to lookup symbol names/addresses in both directions.
 */

#include <elf/elf.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <str.h>
#include <str_error.h>
#include <vfs/vfs.h>

#include "include/symtab.h"

static errno_t elf_hdr_check(elf_header_t *hdr);
static errno_t section_hdr_load(int fd, const elf_header_t *ehdr, int idx,
    elf_section_header_t *shdr);
static errno_t chunk_load(int fd, off64_t start, size_t size, void **ptr);

/** Load symbol table from an ELF file.
 *
 * @param file_name     Name of the ELF file to read from.
 * @param symtab        Place to save pointer to new symtab structure.
 *
 * @return              EOK on success, ENOENT if file could not be open,
 *                      ENOTSUP if file parsing failed.
 */
errno_t symtab_load(const char *file_name, symtab_t **symtab)
{
        symtab_t *stab;
        elf_header_t elf_hdr;
        elf_section_header_t sec_hdr;
        off64_t shstrt_start;
        size_t shstrt_size;
        char *shstrt, *sec_name;
        void *data;
        aoff64_t pos = 0;

        int fd;
        errno_t rc;
        size_t nread;
        int i;

        bool load_sec, sec_is_symtab;

        *symtab = NULL;

        stab = calloc(1, sizeof(symtab_t));
        if (stab == NULL)
                return ENOMEM;

        rc = vfs_lookup_open(file_name, WALK_REGULAR, MODE_READ, &fd);
        if (rc != EOK) {
                printf("failed opening file '%s': %s\n", file_name, str_error(rc));
                free(stab);
                return ENOENT;
        }

        rc = vfs_read(fd, &pos, &elf_hdr, sizeof(elf_header_t), &nread);
        if (rc != EOK || nread != sizeof(elf_header_t)) {
                printf("failed reading elf header\n");
                free(stab);
                return EIO;
        }

        rc = elf_hdr_check(&elf_hdr);
        if (rc != EOK) {
                printf("failed header check\n");
                free(stab);
                return ENOTSUP;
        }

        /*
         * Load section header string table.
         */

        rc = section_hdr_load(fd, &elf_hdr, elf_hdr.e_shstrndx, &sec_hdr);
        if (rc != EOK) {
                printf("failed reading shstrt header\n");
                free(stab);
                return ENOTSUP;
        }

        shstrt_start = sec_hdr.sh_offset;
        shstrt_size = sec_hdr.sh_size;

        rc = chunk_load(fd, shstrt_start, shstrt_size, (void **) &shstrt);
        if (rc != EOK) {
                printf("failed loading shstrt\n");
                free(stab);
                return ENOTSUP;
        }

        /* Read all section headers. */
        for (i = 0; i < elf_hdr.e_shnum; ++i) {
                rc = section_hdr_load(fd, &elf_hdr, i, &sec_hdr);
                if (rc != EOK) {
                        free(shstrt);
                        free(stab);
                        return ENOTSUP;
                }

                sec_name = shstrt + sec_hdr.sh_name;
                if (str_cmp(sec_name, ".symtab") == 0 &&
                    sec_hdr.sh_type == SHT_SYMTAB) {
                        load_sec = true;
                        sec_is_symtab = true;
                } else if (str_cmp(sec_name, ".strtab") == 0 &&
                    sec_hdr.sh_type == SHT_STRTAB) {
                        load_sec = true;
                        sec_is_symtab = false;
                } else {
                        load_sec = false;
                }

                if (load_sec) {
                        rc = chunk_load(fd, sec_hdr.sh_offset, sec_hdr.sh_size,
                            &data);
                        if (rc != EOK) {
                                free(shstrt);
                                free(stab);
                                return ENOTSUP;
                        }

                        if (sec_is_symtab) {
                                stab->sym = data;
                                stab->sym_size = sec_hdr.sh_size;
                        } else {
                                stab->strtab = data;
                                stab->strtab_size = sec_hdr.sh_size;
                        }
                }
        }

        free(shstrt);
        vfs_put(fd);

        if (stab->sym == NULL || stab->strtab == NULL) {
                /* Tables not found. */
                printf("Symbol table or string table section not found\n");
                free(stab);
                return ENOTSUP;
        }

        *symtab = stab;

        return EOK;
}

/** Delete a symtab structure.
 *
 * Deallocates all resources used by the symbol table.
 */
void symtab_delete(symtab_t *st)
{
        free(st->sym);
        st->sym = NULL;

        free(st->strtab);
        st->strtab = NULL;

        free(st);
}

/** Convert symbol name to address.
 *
 * @param st    Symbol table.
 * @param name  Name of the symbol.
 * @param addr  Place to store address for symbol, if found.
 *
 * @return      EOK on success, ENOENT if no such symbol was found.
 */
errno_t symtab_name_to_addr(symtab_t *st, const char *name, uintptr_t *addr)
{
        size_t i;
        char *sname;
        unsigned stype;

        for (i = 0; i < st->sym_size / sizeof(elf_symbol_t); ++i) {
                if (st->sym[i].st_name == 0)
                        continue;

                stype = elf_st_type(st->sym[i].st_info);
                if (stype != STT_OBJECT && stype != STT_FUNC)
                        continue;

                sname = st->strtab + st->sym[i].st_name;

                if (str_cmp(sname, name) == 0) {
                        *addr = st->sym[i].st_value;
                        return EOK;
                }
        }

        return ENOENT;
}

/** Convert symbol address to name.
 *
 * This function finds the symbol which starts at the highest address
 * less than or equal to @a addr.
 *
 * @param st    Symbol table.
 * @param addr  Address for lookup.
 * @param name  Place to store pointer name of symbol, if found.
 *              This is valid while @a st exists.
 *
 * @return      EOK on success or ENOENT if no matching symbol was found.
 */
errno_t symtab_addr_to_name(symtab_t *st, uintptr_t addr, char **name,
    size_t *offs)
{
        size_t i;
        uintptr_t saddr, best_addr;
        char *sname, *best_name;
        unsigned stype;

        best_name = NULL;
        best_addr = 0;

        for (i = 0; i < st->sym_size / sizeof(elf_symbol_t); ++i) {
                if (st->sym[i].st_name == 0)
                        continue;

                stype = elf_st_type(st->sym[i].st_info);
                if (stype != STT_OBJECT && stype != STT_FUNC &&
                    stype != STT_NOTYPE) {
                        continue;
                }

                saddr = st->sym[i].st_value;
                sname = st->strtab + st->sym[i].st_name;

                /* An ugly hack to filter out some special ARM symbols. */
                if (sname[0] == '$')
                        continue;

                if (saddr <= addr && (best_name == NULL || saddr > best_addr)) {
                        best_name = sname;
                        best_addr = saddr;
                }
        }

        if (best_name == NULL)
                return ENOENT;

        *name = best_name;
        *offs = addr - best_addr;
        return EOK;
}

/** Check if ELF header is valid.
 *
 * @return      EOK on success or an error code.
 */
static errno_t elf_hdr_check(elf_header_t *ehdr)
{
        /* TODO */
        return EOK;
}

/** Load ELF section header.
 *
 * @param fd            File descriptor of ELF file.
 * @param elf_hdr       Pointer to ELF file header in memory.
 * @param idx           Index of section whose header to load (0 = first).
 * @param sec_hdr       Place to store section header data.
 *
 * @return              EOK on success or EIO if I/O failed.
 */
static errno_t section_hdr_load(int fd, const elf_header_t *elf_hdr, int idx,
    elf_section_header_t *sec_hdr)
{
        errno_t rc;
        size_t nread;
        aoff64_t pos = elf_hdr->e_shoff + idx * sizeof(elf_section_header_t);

        rc = vfs_read(fd, &pos, sec_hdr, sizeof(elf_section_header_t), &nread);
        if (rc != EOK || nread != sizeof(elf_section_header_t))
                return EIO;

        return EOK;
}

/** Load a segment of bytes from a file and return it as a new memory block.
 *
 * This function fails if it cannot read exactly @a size bytes from the file.
 *
 * @param fd            File to read from.
 * @param start         Position in file where to start reading.
 * @param size          Number of bytes to read.
 * @param ptr           Place to store pointer to newly allocated block.
 *
 * @return              EOK on success or EIO on failure.
 */
static errno_t chunk_load(int fd, off64_t start, size_t size, void **ptr)
{
        errno_t rc;
        size_t nread;
        aoff64_t pos = start;

        *ptr = malloc(size);
        if (*ptr == NULL) {
                printf("failed allocating memory\n");
                return ENOMEM;
        }

        rc = vfs_read(fd, &pos, *ptr, size, &nread);
        if (rc != EOK || nread != size) {
                printf("failed reading chunk\n");
                free(*ptr);
                *ptr = NULL;
                return EIO;
        }

        return EOK;
}

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub