root/lib/libkldelf/ef_obj.c
/*-
 * SPDX-License-Identifier: BSD-4-Clause
 *
 * Copyright (c) 2000, Boris Popov
 * Copyright (c) 1998-2000 Doug Rabson
 * Copyright (c) 2004 Peter Wemm
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Boris Popov.
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 */

#include <sys/param.h>

#include <err.h>
#include <errno.h>
#include <gelf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "kldelf.h"

typedef struct {
        GElf_Addr       addr;
        GElf_Off        offset;
        GElf_Off        size;
        int             flags;
        int             sec;    /* Original section */
        char            *name;
} Elf_progent;

typedef struct {
        GElf_Rel        *rel;
        long            nrel;
        int             sec;
} Elf_relent;

typedef struct {
        GElf_Rela       *rela;
        long            nrela;
        int             sec;
} Elf_relaent;

struct ef_file {
        char            *ef_name;
        struct elf_file *ef_efile;

        Elf_progent     *progtab;
        int             nprogtab;

        Elf_relaent     *relatab;
        int             nrela;

        Elf_relent      *reltab;
        int             nrel;

        GElf_Sym        *ddbsymtab;     /* The symbol table we are using */
        size_t          ddbsymcnt;      /* Number of symbols */
        caddr_t         ddbstrtab;      /* String table */
        long            ddbstrcnt;      /* number of bytes in string table */

        caddr_t         shstrtab;       /* Section name string table */
        long            shstrcnt;       /* number of bytes in string table */

        int             ef_verbose;
};

static void     ef_obj_close(elf_file_t ef);

static int      ef_obj_seg_read_rel(elf_file_t ef, GElf_Addr address,
                    size_t len, void *dest);
static int      ef_obj_seg_read_string(elf_file_t ef, GElf_Addr address,
                    size_t len, char *dest);

static GElf_Addr ef_obj_symaddr(elf_file_t ef, GElf_Size symidx);
static int      ef_obj_lookup_set(elf_file_t ef, const char *name,
                    GElf_Addr *startp, GElf_Addr *stopp, long *countp);
static int      ef_obj_lookup_symbol(elf_file_t ef, const char *name,
                    GElf_Sym **sym, bool see_local);

static struct elf_file_ops ef_obj_file_ops = {
        .close                  = ef_obj_close,
        .seg_read_rel           = ef_obj_seg_read_rel,
        .seg_read_string        = ef_obj_seg_read_string,
        .symaddr                = ef_obj_symaddr,
        .lookup_set             = ef_obj_lookup_set,
        .lookup_symbol          = ef_obj_lookup_symbol,
};

static GElf_Off
ef_obj_get_offset(elf_file_t ef, GElf_Addr addr)
{
        Elf_progent *pt;
        int i;

        for (i = 0; i < ef->nprogtab; i++) {
                pt = &ef->progtab[i];
                if (pt->offset == (GElf_Off)-1)
                        continue;
                if (addr >= pt->addr && addr < pt->addr + pt->size)
                        return (pt->offset + (addr - pt->addr));
        }
        return (0);
}

static int
ef_obj_lookup_symbol(elf_file_t ef, const char *name, GElf_Sym **sym,
    bool see_local)
{
        GElf_Sym *symp;
        const char *strp;
        int i;

        for (i = 0, symp = ef->ddbsymtab; i < ef->ddbsymcnt; i++, symp++) {
                strp = ef->ddbstrtab + symp->st_name;
                if (symp->st_shndx != SHN_UNDEF && strcmp(name, strp) == 0) {
                        if (see_local ||
                            GELF_ST_BIND(symp->st_info) != STB_LOCAL) {
                                *sym = symp;
                                return (0);
                        }
                }
        }
        return (ENOENT);
}

static int
ef_obj_lookup_set(elf_file_t ef, const char *name, GElf_Addr *startp,
    GElf_Addr *stopp, long *countp)
{
        int i;

        for (i = 0; i < ef->nprogtab; i++) {
                if ((strncmp(ef->progtab[i].name, "set_", 4) == 0) &&
                    strcmp(ef->progtab[i].name + 4, name) == 0) {
                        *startp = ef->progtab[i].addr;
                        *stopp = ef->progtab[i].addr + ef->progtab[i].size;
                        *countp = (*stopp - *startp) /
                            elf_pointer_size(ef->ef_efile);
                        return (0);
                }
        }
        return (ESRCH);
}

static GElf_Addr
ef_obj_symaddr(elf_file_t ef, GElf_Size symidx)
{
        const GElf_Sym *sym;

        if (symidx >= ef->ddbsymcnt)
                return (0);
        sym = ef->ddbsymtab + symidx;

        if (sym->st_shndx != SHN_UNDEF)
                return (sym->st_value);
        return (0);
}

static int
ef_obj_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len, void *dest)
{
        GElf_Off secofs;
        GElf_Rel *r;
        GElf_Rela *a;
        GElf_Addr secbase, dataoff;
        int error, i, sec;

        /* Find out which section contains the data. */
        sec = -1;
        for (i = 0; i < ef->nprogtab; i++) {
                if (address < ef->progtab[i].addr)
                        continue;

                dataoff = address - ef->progtab[i].addr;
                if (dataoff + len > ef->progtab[i].size)
                        continue;

                sec = ef->progtab[i].sec;
                secbase = ef->progtab[i].addr;
                secofs = ef->progtab[i].offset;
                break;
        }

        if (sec == -1) {
                if (ef->ef_verbose)
                        warnx("ef_obj_seg_read_rel(%s): bad address (%jx)",
                            ef->ef_name, (uintmax_t)address);
                return (EFAULT);
        }

        if (secofs == (GElf_Off)-1) {
                memset(dest, 0, len);
        } else {
                error = elf_read_raw_data(ef->ef_efile, secofs + dataoff, dest,
                    len);
                if (error != 0)
                        return (error);
        }

        /* Now do the relocations. */
        for (i = 0; i < ef->nrel; i++) {
                if (ef->reltab[i].sec != sec)
                        continue;
                for (r = ef->reltab[i].rel;
                     r < &ef->reltab[i].rel[ef->reltab[i].nrel]; r++) {
                        error = elf_reloc(ef->ef_efile, r, ELF_T_REL, secbase,
                            address, len, dest);
                        if (error != 0)
                                return (error);
                }
        }
        for (i = 0; i < ef->nrela; i++) {
                if (ef->relatab[i].sec != sec)
                        continue;
                for (a = ef->relatab[i].rela;
                     a < &ef->relatab[i].rela[ef->relatab[i].nrela]; a++) {
                        error = elf_reloc(ef->ef_efile, a, ELF_T_RELA, secbase,
                            address, len, dest);
                        if (error != 0)
                                return (error);
                }
        }
        return (0);
}

static int
ef_obj_seg_read_string(elf_file_t ef, GElf_Addr address, size_t len, char *dest)
{
        GElf_Off ofs;

        ofs = ef_obj_get_offset(ef, address);
        if (ofs == 0) {
                if (ef->ef_verbose)
                        warnx("ef_obj_seg_read_string(%s): bad address (%jx)",
                            ef->ef_name, (uintmax_t)address);
                return (EFAULT);
        }

        return (elf_read_raw_string(ef->ef_efile, ofs, dest, len));
}

int
ef_obj_open(struct elf_file *efile, int verbose)
{
        elf_file_t ef;
        GElf_Ehdr *hdr;
        GElf_Shdr *shdr;
        GElf_Sym *es;
        GElf_Addr mapbase;
        size_t i, nshdr;
        int error, pb, ra, rl;
        int j, nsym, symstrindex, symtabindex;

        hdr = &efile->ef_hdr;
        if (hdr->e_type != ET_REL || hdr->e_shnum == 0 || hdr->e_shoff == 0 ||
            hdr->e_shentsize != elf_object_size(efile, ELF_T_SHDR))
                return (EFTYPE);

        ef = calloc(1, sizeof(*ef));
        if (ef == NULL)
                return (errno);

        efile->ef_ef = ef;
        efile->ef_ops = &ef_obj_file_ops;

        ef->ef_verbose = verbose;
        ef->ef_name = strdup(efile->ef_filename);
        ef->ef_efile = efile;

        error = elf_read_shdrs(efile, &nshdr, &shdr);
        if (error != 0) {
                shdr = NULL;
                goto out;
        }

        /* Scan the section headers for information and table sizing. */
        nsym = 0;
        symtabindex = -1;
        symstrindex = -1;
        for (i = 0; i < nshdr; i++) {
                switch (shdr[i].sh_type) {
                case SHT_PROGBITS:
                case SHT_NOBITS:
                        ef->nprogtab++;
                        break;
                case SHT_SYMTAB:
                        nsym++;
                        symtabindex = i;
                        symstrindex = shdr[i].sh_link;
                        break;
                case SHT_REL:
                        ef->nrel++;
                        break;
                case SHT_RELA:
                        ef->nrela++;
                        break;
                case SHT_STRTAB:
                        break;
                }
        }

        if (ef->nprogtab == 0) {
                warnx("%s: file has no contents", ef->ef_name);
                goto out;
        }
        if (nsym != 1) {
                warnx("%s: file has no valid symbol table", ef->ef_name);
                goto out;
        }
        if (symstrindex < 0 || symstrindex > nshdr ||
            shdr[symstrindex].sh_type != SHT_STRTAB) {
                warnx("%s: file has invalid symbol strings", ef->ef_name);
                goto out;
        }

        /* Allocate space for tracking the load chunks */
        if (ef->nprogtab != 0)
                ef->progtab = calloc(ef->nprogtab, sizeof(*ef->progtab));
        if (ef->nrel != 0)
                ef->reltab = calloc(ef->nrel, sizeof(*ef->reltab));
        if (ef->nrela != 0)
                ef->relatab = calloc(ef->nrela, sizeof(*ef->relatab));
        if ((ef->nprogtab != 0 && ef->progtab == NULL) ||
            (ef->nrel != 0 && ef->reltab == NULL) ||
            (ef->nrela != 0 && ef->relatab == NULL)) {
                warnx("malloc failed");
                error = ENOMEM;
                goto out;
        }

        if (elf_read_symbols(efile, symtabindex, &ef->ddbsymcnt,
            &ef->ddbsymtab) != 0) {
                warnx("elf_read_symbols failed");
                goto out;
        }

        if (elf_read_string_table(efile, &shdr[symstrindex], &ef->ddbstrcnt,
            &ef->ddbstrtab) != 0) {
                warnx("elf_read_string_table failed");
                goto out;
        }

        /* Do we have a string table for the section names?  */
        if (hdr->e_shstrndx != 0 &&
            shdr[hdr->e_shstrndx].sh_type == SHT_STRTAB) {
                if (elf_read_string_table(efile, &shdr[hdr->e_shstrndx],
                    &ef->shstrcnt, &ef->shstrtab) != 0) {
                        warnx("elf_read_string_table failed");
                        goto out;
                }
        }

        /*
         * Now allocate address space for code/data(progbits) and
         * bss(nobits) and allocate space for and load relocs.
         */
        pb = 0;
        rl = 0;
        ra = 0;
        mapbase = 0;
        for (i = 0; i < nshdr; i++) {
                switch (shdr[i].sh_type) {
                case SHT_PROGBITS:
                case SHT_NOBITS:
                        mapbase = roundup2(mapbase, shdr[i].sh_addralign);
                        ef->progtab[pb].addr = mapbase;
                        if (shdr[i].sh_type == SHT_PROGBITS) {
                                ef->progtab[pb].name = "<<PROGBITS>>";
                                ef->progtab[pb].offset = shdr[i].sh_offset;
                        } else {
                                ef->progtab[pb].name = "<<NOBITS>>";
                                ef->progtab[pb].offset = (GElf_Off)-1;
                        }
                        ef->progtab[pb].size = shdr[i].sh_size;
                        ef->progtab[pb].sec = i;
                        if (ef->shstrtab && shdr[i].sh_name != 0)
                                ef->progtab[pb].name =
                                    ef->shstrtab + shdr[i].sh_name;

                        /* Update all symbol values with the offset. */
                        for (j = 0; j < ef->ddbsymcnt; j++) {
                                es = &ef->ddbsymtab[j];
                                if (es->st_shndx != i)
                                        continue;
                                es->st_value += ef->progtab[pb].addr;
                        }
                        mapbase += shdr[i].sh_size;
                        pb++;
                        break;
                case SHT_REL:
                        ef->reltab[rl].sec = shdr[i].sh_info;
                        if (elf_read_rel(efile, i, &ef->reltab[rl].nrel,
                            &ef->reltab[rl].rel) != 0) {
                                warnx("elf_read_rel failed");
                                goto out;
                        }
                        rl++;
                        break;
                case SHT_RELA:
                        ef->relatab[ra].sec = shdr[i].sh_info;
                        if (elf_read_rela(efile, i, &ef->relatab[ra].nrela,
                            &ef->relatab[ra].rela) != 0) {
                                warnx("elf_read_rela failed");
                                goto out;
                        }
                        ra++;
                        break;
                }
        }
        error = 0;
out:
        free(shdr);
        if (error != 0)
                ef_obj_close(ef);
        return (error);
}

static void
ef_obj_close(elf_file_t ef)
{
        int i;

        if (ef->ef_name)
                free(ef->ef_name);
        if (ef->nprogtab != 0)
                free(ef->progtab);
        if (ef->nrel != 0) {
                for (i = 0; i < ef->nrel; i++)
                        if (ef->reltab[i].rel != NULL)
                                free(ef->reltab[i].rel);
                free(ef->reltab);
        }
        if (ef->nrela != 0) {
                for (i = 0; i < ef->nrela; i++)
                        if (ef->relatab[i].rela != NULL)
                                free(ef->relatab[i].rela);
                free(ef->relatab);
        }
        if (ef->ddbsymtab != NULL)
                free(ef->ddbsymtab);
        if (ef->ddbstrtab != NULL)
                free(ef->ddbstrtab);
        if (ef->shstrtab != NULL)
                free(ef->shstrtab);
        ef->ef_efile->ef_ops = NULL;
        ef->ef_efile->ef_ef = NULL;
        free(ef);
}