root/libexec/ld.so/resolve.c
/*      $OpenBSD: resolve.c,v 1.102 2024/01/22 02:08:31 deraadt Exp $ */

/*
 * Copyright (c) 1998 Per Fogelstrom, Opsycon AB
 *
 * 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.
 *
 * 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.
 *
 */

#define _DYN_LOADER

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/syscall.h>

#include <limits.h>
#include <link.h>

#include "util.h"
#include "path.h"
#include "resolve.h"
#include "syscall.h"

/* substitution types */
typedef enum {
        SUBST_UNKNOWN, SUBST_ORIGIN, SUBST_OSNAME, SUBST_OSREL, SUBST_PLATFORM
} SUBST_TYPES;

struct symlookup {
        const char              *sl_name;
        struct sym_res          sl_out;
        struct sym_res          sl_weak_out;
        unsigned long           sl_elf_hash;
        uint32_t                sl_gnu_hash;
        int                     sl_flags;
};

elf_object_t *_dl_objects;
int object_count;
static elf_object_t *_dl_last_object;
elf_object_t *_dl_loading_object;

void
_dl_handle_nodelete(elf_object_t *object)
{
        /*
         * If a .so is marked nodelete, then the entire load group that it's
         * in needs to be kept around forever, so add a reference there.
         * XXX It would be better if we tracked inter-object dependencies
         * from relocations and didn't leave dangling pointers when a load
         * group was partially unloaded.  That would render this unnecessary.
         */
        if (object->obj_flags & DF_1_NODELETE &&
            (object->load_object->status & STAT_NODELETE) == 0) {
                DL_DEB(("objname %s is nodelete\n", object->load_name));
                object->load_object->opencount++;
                object->load_object->status |= STAT_NODELETE;
        }
}

/*
 * Add a new dynamic object to the object list.
 */
void
_dl_add_object(elf_object_t *object)
{
        _dl_handle_nodelete(object);

        /*
         * if this is a new object, prev will be NULL
         * != NULL if an object already in the list
         * prev == NULL for the first item in the list, but that will
         * be the executable.
         */
        if (object->prev != NULL)
                return;

        if (_dl_objects == NULL) {                      /* First object ? */
                _dl_last_object = _dl_objects = object;
                object_count = 2;                       /* count ld.so early */
        } else {
                _dl_last_object->next = object;
                object->prev = _dl_last_object;
                _dl_last_object = object;
                if (object->obj_type != OBJTYPE_LDR)    /* see above */
                        object_count++;
        }
}

/*
 * Identify substitution sequence name.
 */
static int
_dl_subst_name(const char *name, size_t siz) {
        switch (siz) {
        case 5:
                if (_dl_strncmp(name, "OSREL", 5) == 0)
                        return SUBST_OSREL;
                break;
        case 6:
                if (_dl_strncmp(name, "ORIGIN", 6) == 0)
                        return SUBST_ORIGIN;
                if (_dl_strncmp(name, "OSNAME", 6) == 0)
                        return SUBST_OSNAME;
                break;
        case 8:
                if (_dl_strncmp(name, "PLATFORM", 8) == 0)
                        return SUBST_PLATFORM;
                break;
        }

        return (SUBST_UNKNOWN);
}

/*
 * Perform $ORIGIN substitutions on path
 */
static void
_dl_origin_subst_path(elf_object_t *object, const char *origin_path,
    char **path)
{
        char tmp_path[PATH_MAX];
        char *new_path, *tp;
        const char *pp, *name, *value;
        static struct utsname uts;
        size_t value_len;
        int skip_brace;

        if (uts.sysname[0] == '\0') {
                if (_dl_uname(&uts) != 0)
                        return;
        }

        tp = tmp_path;
        pp = *path;

        while (*pp != '\0' && (tp - tmp_path) < sizeof(tmp_path)) {

                /* copy over chars up to but not including $ */
                while (*pp != '\0' && *pp != '$' &&
                    (tp - tmp_path) < sizeof(tmp_path))
                        *tp++ = *pp++;

                /* substitution sequence detected */
                if (*pp == '$' && (tp - tmp_path) < sizeof(tmp_path)) {
                        pp++;

                        if ((skip_brace = (*pp == '{')))
                                pp++;

                        /* skip over name */
                        name = pp;
                        while (_dl_isalnum((unsigned char)*pp) || *pp == '_')
                                pp++;

                        switch (_dl_subst_name(name, pp - name)) {
                        case SUBST_ORIGIN:
                                value = origin_path;
                                break;
                        case SUBST_OSNAME:
                                value = uts.sysname;
                                break;
                        case SUBST_OSREL:
                                value = uts.release;
                                break;
                        case SUBST_PLATFORM:
                                value = uts.machine;
                                break;
                        default:
                                value = "";
                        }

                        value_len = _dl_strlen(value);
                        if (value_len >= sizeof(tmp_path) - (tp - tmp_path))
                                return;

                        _dl_bcopy(value, tp, value_len);
                        tp += value_len;

                        if (skip_brace && *pp == '}')
                                pp++;
                }
        }

        /* no substitution made if result exceeds sizeof(tmp_path) */
        if (tp - tmp_path >= sizeof(tmp_path))
                return;

        /* NULL terminate tmp_path */
        *tp = '\0';

        if (_dl_strcmp(tmp_path, *path) == 0)
                return;

        new_path = _dl_strdup(tmp_path);
        if (new_path == NULL)
                return;

        DL_DEB(("orig_path %s\n", *path));
        DL_DEB(("new_path  %s\n", new_path));

        _dl_free(*path);
        *path = new_path;
}

/*
 * Determine origin_path from object load_name. The origin_path argument
 * must refer to a buffer capable of storing at least PATH_MAX characters.
 * Returns 0 on success.
 */
static int
_dl_origin_path(elf_object_t *object, char *origin_path)
{
        const char *dirname_path;

        /* syscall in ld.so returns 0/-errno, where libc returns char* */
        if (_dl___realpath(object->load_name, origin_path) < 0)
                return -1;

        dirname_path = _dl_dirname(origin_path);
        if (dirname_path == NULL)
                return -1;

        _dl_strlcpy(origin_path, dirname_path, PATH_MAX);

        return 0;
}

/*
 * Perform $ORIGIN substitutions on runpath and rpath
 */
static void
_dl_origin_subst(elf_object_t *object)
{
        char origin_path[PATH_MAX];
        char **pp;

        if (_dl_origin_path(object, origin_path) != 0)
                return;

        /* perform path substitutions on each segment of runpath and rpath */
        if (object->runpath != NULL) {
                for (pp = object->runpath; *pp != NULL; pp++)
                        _dl_origin_subst_path(object, origin_path, pp);
        }
        if (object->rpath != NULL) {
                for (pp = object->rpath; *pp != NULL; pp++)
                        _dl_origin_subst_path(object, origin_path, pp);
        }
}

/*
 * Initialize a new dynamic object.
 */
elf_object_t *
_dl_finalize_object(const char *objname, Elf_Dyn *dynp, Elf_Phdr *phdrp,
    int phdrc, const int objtype, const long lbase, const long obase)
{
        elf_object_t *object;
        Elf_Addr gnu_hash = 0;

        DL_DEB(("objname [%s], dynp %p, objtype %x lbase %lx, obase %lx\n",
            objname, dynp, objtype, lbase, obase));

        object = _dl_calloc(1, sizeof(elf_object_t));
        if (object == NULL)
                _dl_oom();
        object->prev = object->next = NULL;

        object->load_dyn = dynp;
        while (dynp->d_tag != DT_NULL) {
                if (dynp->d_tag < DT_NUM)
                        object->Dyn.info[dynp->d_tag] = dynp->d_un.d_val;
                else if (dynp->d_tag >= DT_LOPROC &&
                    dynp->d_tag < DT_LOPROC + DT_PROCNUM)
                        object->Dyn.info[dynp->d_tag + DT_NUM - DT_LOPROC] =
                            dynp->d_un.d_val;
                if (dynp->d_tag == DT_TEXTREL)
                        object->dyn.textrel = 1;
                if (dynp->d_tag == DT_SYMBOLIC)
                        object->dyn.symbolic = 1;
                if (dynp->d_tag == DT_BIND_NOW)
                        object->obj_flags |= DF_1_NOW;
                if (dynp->d_tag == DT_FLAGS_1)
                        object->obj_flags |= dynp->d_un.d_val;
                if (dynp->d_tag == DT_FLAGS) {
                        object->dyn.flags |= dynp->d_un.d_val;
                        if (dynp->d_un.d_val & DF_SYMBOLIC)
                                object->dyn.symbolic = 1;
                        if (dynp->d_un.d_val & DF_TEXTREL)
                                object->dyn.textrel = 1;
                        if (dynp->d_un.d_val & DF_ORIGIN)
                                object->obj_flags |= DF_1_ORIGIN;
                        if (dynp->d_un.d_val & DF_BIND_NOW)
                                object->obj_flags |= DF_1_NOW;
                }
                if (dynp->d_tag == DT_RELACOUNT)
                        object->relacount = dynp->d_un.d_val;
                if (dynp->d_tag == DT_RELCOUNT)
                        object->relcount = dynp->d_un.d_val;
                if (dynp->d_tag == DT_GNU_HASH)
                        gnu_hash = dynp->d_un.d_val;
                dynp++;
        }
        DL_DEB((" flags %s = 0x%x\n", objname, object->obj_flags));
        object->obj_type = objtype;

        if (_dl_loading_object == NULL) {
                /*
                 * no loading object, object is the loading object,
                 * as it is either executable, or dlopened()
                 */
                _dl_loading_object = object;
        }

        if ((object->obj_flags & DF_1_NOOPEN) != 0 &&
            _dl_loading_object->obj_type == OBJTYPE_DLO &&
            !_dl_traceld) {
                _dl_free(object);
                _dl_errno = DL_CANT_LOAD_OBJ;
                return(NULL);
        }

        /*
         *  Now relocate all pointer to dynamic info, but only
         *  the ones which have pointer values.
         */
        if (object->Dyn.info[DT_PLTGOT])
                object->Dyn.info[DT_PLTGOT] += obase;
        if (object->Dyn.info[DT_STRTAB])
                object->Dyn.info[DT_STRTAB] += obase;
        if (object->Dyn.info[DT_SYMTAB])
                object->Dyn.info[DT_SYMTAB] += obase;
        if (object->Dyn.info[DT_RELA])
                object->Dyn.info[DT_RELA] += obase;
        if (object->Dyn.info[DT_SONAME])
                object->Dyn.info[DT_SONAME] += object->Dyn.info[DT_STRTAB];
        if (object->Dyn.info[DT_RPATH])
                object->Dyn.info[DT_RPATH] += object->Dyn.info[DT_STRTAB];
        if (object->Dyn.info[DT_RUNPATH])
                object->Dyn.info[DT_RUNPATH] += object->Dyn.info[DT_STRTAB];
        if (object->Dyn.info[DT_REL])
                object->Dyn.info[DT_REL] += obase;
        if (object->Dyn.info[DT_INIT])
                object->Dyn.info[DT_INIT] += obase;
        if (object->Dyn.info[DT_FINI])
                object->Dyn.info[DT_FINI] += obase;
        if (object->Dyn.info[DT_JMPREL])
                object->Dyn.info[DT_JMPREL] += obase;
        if (object->Dyn.info[DT_INIT_ARRAY])
                object->Dyn.info[DT_INIT_ARRAY] += obase;
        if (object->Dyn.info[DT_FINI_ARRAY])
                object->Dyn.info[DT_FINI_ARRAY] += obase;
        if (object->Dyn.info[DT_PREINIT_ARRAY])
                object->Dyn.info[DT_PREINIT_ARRAY] += obase;
        if (object->Dyn.info[DT_RELR])
                object->Dyn.info[DT_RELR] += obase;

        if (gnu_hash) {
                Elf_Word *hashtab = (Elf_Word *)(gnu_hash + obase);
                Elf_Word nbuckets = hashtab[0];
                Elf_Word nmaskwords = hashtab[2];

                /* validity check */
                if (nbuckets > 0 && (nmaskwords & (nmaskwords - 1)) == 0) {
                        Elf_Word symndx = hashtab[1];
                        int bloom_size32 = (ELFSIZE / 32) * nmaskwords;

                        object->nbuckets = nbuckets;
                        object->symndx_gnu = symndx;
                        object->mask_bm_gnu = nmaskwords - 1;
                        object->shift2_gnu = hashtab[3];
                        object->bloom_gnu = (Elf_Addr *)(hashtab + 4);
                        object->buckets_gnu = hashtab + 4 + bloom_size32;
                        object->chains_gnu = object->buckets_gnu + nbuckets
                            - symndx;

                        /*
                         * If the ELF hash is present, get the total symbol
                         * count ("nchains") from there.  Otherwise, count
                         * the entries in the GNU hash chain.
                         */
                        if (object->Dyn.info[DT_HASH] == 0) {
                                Elf_Word n;

                                for (n = 0; n < nbuckets; n++) {
                                        Elf_Word bkt = object->buckets_gnu[n];
                                        const Elf_Word *hashval;
                                        if (bkt == 0)
                                                continue;
                                        hashval = &object->chains_gnu[bkt];
                                        do {
                                                symndx++;
                                        } while ((*hashval++ & 1U) == 0);
                                }
                                object->nchains = symndx;
                        }
                        object->status |= STAT_GNU_HASH;
                }
        }
        if (object->Dyn.info[DT_HASH] != 0) {
                Elf_Hash_Word *hashtab =
                    (Elf_Hash_Word *)(object->Dyn.info[DT_HASH] + obase);

                object->nchains = hashtab[1];
                if (object->nbuckets == 0) {
                        object->nbuckets = hashtab[0];
                        object->buckets_elf = hashtab + 2;
                        object->chains_elf = object->buckets_elf +
                            object->nbuckets;
                }
        }

        object->phdrp = phdrp;
        object->phdrc = phdrc;
        object->load_base = lbase;
        object->obj_base = obase;
        object->load_name = _dl_strdup(objname);
        if (object->load_name == NULL)
                _dl_oom();
        object->load_object = _dl_loading_object;
        if (object->load_object == object)
                DL_DEB(("head %s\n", object->load_name));
        DL_DEB(("obj %s has %s as head\n", object->load_name,
            _dl_loading_object->load_name));
        object->refcount = 0;
        object->opencount = 0;  /* # dlopen() & exe */
        object->grprefcount = 0;
        /* default dev, inode for dlopen-able objects. */
        object->dev = 0;
        object->inode = 0;
        object->grpsym_gen = 0;
        TAILQ_INIT(&object->grpref_list);

        if (object->dyn.runpath)
                object->runpath = _dl_split_path(object->dyn.runpath);
        /*
         * DT_RPATH is ignored if DT_RUNPATH is present...except in
         * the exe, whose DT_RPATH is a fallback for libs that don't
         * use DT_RUNPATH
         */
        if (object->dyn.rpath && (object->runpath == NULL ||
            objtype == OBJTYPE_EXE))
                object->rpath = _dl_split_path(object->dyn.rpath);
        if ((object->obj_flags & DF_1_ORIGIN) && _dl_trust)
                _dl_origin_subst(object);

        _dl_trace_object_setup(object);

        return (object);
}

static void
_dl_tailq_free(struct dep_node *n)
{
        struct dep_node *next;

        while (n != NULL) {
                next = TAILQ_NEXT(n, next_sib);
                _dl_free(n);
                n = next;
        }
}

static elf_object_t *free_objects;

void
_dl_cleanup_objects()
{
        elf_object_t *nobj, *head;
        struct dep_node *n, *next;

        n = TAILQ_FIRST(&_dlopened_child_list);
        while (n != NULL) {
                next = TAILQ_NEXT(n, next_sib);
                if (OBJECT_DLREF_CNT(n->data) == 0) {
                        TAILQ_REMOVE(&_dlopened_child_list, n, next_sib);
                        _dl_free(n);
                }
                n = next;
        }

        head = free_objects;
        free_objects = NULL;
        while (head != NULL) {
                _dl_free(head->load_name);
                _dl_free((char *)head->sod.sod_name);
                _dl_free_path(head->runpath);
                _dl_free_path(head->rpath);
                _dl_free(head->grpsym_vec.vec);
                _dl_free(head->child_vec.vec);
                _dl_tailq_free(TAILQ_FIRST(&head->grpref_list));
                nobj = head->next;
                _dl_free(head);
                head = nobj;
        }
}

void
_dl_remove_object(elf_object_t *object)
{
        object->prev->next = object->next;
        if (object->next)
                object->next->prev = object->prev;

        if (_dl_last_object == object)
                _dl_last_object = object->prev;
        object_count--;

        object->next = free_objects;
        free_objects = object;
}

static int
matched_symbol(elf_object_t *obj, const Elf_Sym *sym, struct symlookup *sl)
{
        switch (ELF_ST_TYPE(sym->st_info)) {
        case STT_FUNC:
                /*
                 * Allow this symbol if we are referring to a function which
                 * has a value, even if section is UNDEF.  This allows &func
                 * to refer to PLT as per the ELF spec.  If flags has SYM_PLT
                 * set, we must have actual symbol, so this symbol is skipped.
                 */
                if ((sl->sl_flags & SYM_PLT) && sym->st_shndx == SHN_UNDEF)
                        return 0;
                if (sym->st_value == 0)
                        return 0;
                break;
        case STT_NOTYPE:
        case STT_OBJECT:
                if (sym->st_value == 0)
                        return 0;
#if 0
                /* FALLTHROUGH */
        case STT_TLS:
#endif
                if (sym->st_shndx == SHN_UNDEF)
                        return 0;
                break;
        default:
                return 0;
        }

        if (sym != sl->sl_out.sym &&
            _dl_strcmp(sl->sl_name, obj->dyn.strtab + sym->st_name))
                return 0;

        if (ELF_ST_BIND(sym->st_info) == STB_GLOBAL) {
                sl->sl_out.sym = sym;
                sl->sl_out.obj = obj;
                return 1;
        } else if (ELF_ST_BIND(sym->st_info) == STB_WEAK) {
                if (sl->sl_weak_out.sym == NULL) {
                        sl->sl_weak_out.sym = sym;
                        sl->sl_weak_out.obj = obj;
                }
                /* done with this object, but need to check other objects */
                return -1;
        }
        return 0;
}

static int
_dl_find_symbol_obj(elf_object_t *obj, struct symlookup *sl)
{
        const Elf_Sym   *symt = obj->dyn.symtab;

        if (obj->status & STAT_GNU_HASH) {
                uint32_t hash = sl->sl_gnu_hash;
                Elf_Addr bloom_word;
                unsigned int h1;
                unsigned int h2;
                Elf_Word bucket;
                const Elf_Word *hashval;

                /* pick right bitmask word from Bloom filter array */
                bloom_word = obj->bloom_gnu[(hash / ELFSIZE) &
                    obj->mask_bm_gnu];

                /* calculate modulus ELFSIZE of gnu hash and its derivative */
                h1 = hash & (ELFSIZE - 1);
                h2 = (hash >> obj->shift2_gnu) & (ELFSIZE - 1);

                /* Filter out the "definitely not in set" queries */
                if (((bloom_word >> h1) & (bloom_word >> h2) & 1) == 0)
                        return 0;

                /* Locate hash chain and corresponding value element */
                bucket = obj->buckets_gnu[hash % obj->nbuckets];
                if (bucket == 0)
                        return 0;
                hashval = &obj->chains_gnu[bucket];
                do {
                        if (((*hashval ^ hash) >> 1) == 0) {
                                const Elf_Sym *sym = symt +
                                    (hashval - obj->chains_gnu);

                                int r = matched_symbol(obj, sym, sl);
                                if (r)
                                        return r > 0;
                        }
                } while ((*hashval++ & 1U) == 0);
        } else {
                Elf_Word si;

                for (si = obj->buckets_elf[sl->sl_elf_hash % obj->nbuckets];
                    si != STN_UNDEF; si = obj->chains_elf[si]) {
                        const Elf_Sym *sym = symt + si;

                        int r = matched_symbol(obj, sym, sl);
                        if (r)
                                return r > 0;
                }
        }
        return 0;
}

struct sym_res
_dl_find_symbol(const char *name, int flags, const Elf_Sym *ref_sym,
    elf_object_t *req_obj)
{
        const unsigned char *p;
        unsigned char c;
        struct symlookup sl = {
                .sl_name = name,
                .sl_out = { .sym = NULL },
                .sl_weak_out = { .sym = NULL },
                .sl_elf_hash = 0,
                .sl_gnu_hash = 5381,
                .sl_flags = flags,
        };

        /* Calculate both hashes in one pass */
        for (p = (const unsigned char *)name; (c = *p) != '\0'; p++) {
                sl.sl_elf_hash = (sl.sl_elf_hash << 4) + c;
                sl.sl_elf_hash ^= (sl.sl_elf_hash >> 24) & 0xf0;
                sl.sl_gnu_hash = sl.sl_gnu_hash * 33 + c;
        }
        sl.sl_elf_hash &= 0x0fffffff;

        if (req_obj->dyn.symbolic)
                if (_dl_find_symbol_obj(req_obj, &sl))
                        goto found;

        if (flags & SYM_DLSYM) {
                struct object_vector vec;
                int i;

                if (_dl_find_symbol_obj(req_obj, &sl))
                        goto found;

                /* weak definition in the specified object is good enough */
                if (sl.sl_weak_out.sym != NULL)
                        goto found;

                /* search dlopened obj and all children */
                vec = req_obj->load_object->grpsym_vec;
                for (i = 0; i < vec.len; i++) {
                        if (vec.vec[i] == req_obj)
                                continue;               /* already searched */
                        if (_dl_find_symbol_obj(vec.vec[i], &sl))
                                goto found;
                }
        } else {
                struct dep_node *n;
                struct object_vector vec;
                int i, skip = 0;

                if ((flags & SYM_SEARCH_SELF) || (flags & SYM_SEARCH_NEXT))
                        skip = 1;

                /*
                 * search dlopened objects: global or req_obj == dlopened_obj
                 * and its children
                 */
                TAILQ_FOREACH(n, &_dlopened_child_list, next_sib) {
                        if (((n->data->obj_flags & DF_1_GLOBAL) == 0) &&
                            (n->data != req_obj->load_object))
                                continue;

                        vec = n->data->grpsym_vec;
                        for (i = 0; i < vec.len; i++) {
                                if (skip == 1) {
                                        if (vec.vec[i] == req_obj) {
                                                skip = 0;
                                                if (flags & SYM_SEARCH_NEXT)
                                                        continue;
                                        } else
                                                continue;
                                }
                                if ((flags & SYM_SEARCH_OTHER) &&
                                    (vec.vec[i] == req_obj))
                                        continue;
                                if (_dl_find_symbol_obj(vec.vec[i], &sl))
                                        goto found;
                        }
                }
        }

found:
        if (sl.sl_out.sym == NULL) {
                if (sl.sl_weak_out.sym != NULL)
                        sl.sl_out = sl.sl_weak_out;
                else {
                        if ((ref_sym == NULL ||
                            (ELF_ST_BIND(ref_sym->st_info) != STB_WEAK)) &&
                            (flags & SYM_WARNNOTFOUND))
                                _dl_printf("%s:%s: undefined symbol '%s'\n",
                                    __progname, req_obj->load_name, name);
                        return (struct sym_res){ NULL, NULL };
                }
        }

        if (ref_sym != NULL && ref_sym->st_size != 0 &&
            (ref_sym->st_size != sl.sl_out.sym->st_size) &&
            (ELF_ST_TYPE(sl.sl_out.sym->st_info) != STT_FUNC)) {
                _dl_printf("%s:%s: %s : WARNING: "
                    "symbol(%s) size mismatch, relink your program\n",
                    __progname, req_obj->load_name, sl.sl_out.obj->load_name,
                    name);
        }

        return sl.sl_out;
}

void
_dl_debug_state(void)
{
        /* Debugger stub */
}

/*
 * Search for DT_SONAME, and check if this is libc
 */
int
_dl_islibc(Elf_Dyn *_dynp, Elf_Addr loff)
{
        Elf_Dyn *d, *dynp = (Elf_Dyn *)((unsigned long)_dynp + loff);
        long base = 0;

        for (d = dynp; d->d_tag != DT_NULL; d++)
                if (d->d_tag == DT_STRTAB) {
                        base = d->d_un.d_ptr + loff;
                        break;
                }
        if (base == 0)
                return 0;
        for (d = dynp; d->d_tag != DT_NULL; d++)
                if (d->d_tag == DT_SONAME) {
                        if (_dl_strncmp((char *)(base + d->d_un.d_ptr),
                            "libc.so.", 8) == 0)
                                return 1;
                        break;
                }
        return 0;
}

void
_dl_pin(int file, Elf_Phdr *phdp, void *base, size_t len,
    void *exec_base, size_t exec_size)
{
        struct pinsyscalls {
                u_int offset;
                u_int sysno;
        } *syscalls;
        int npins = 0, nsyscalls, i;
        u_int *pins = NULL;
        vaddr_t offset;

        if (phdp->p_filesz > SYS_MAXSYSCALL * 2 * sizeof(*syscalls) ||
            phdp->p_filesz % sizeof(*syscalls) != 0 ||
            phdp->p_offset & 0x3)
                return;
        syscalls = _dl_mmap(NULL, phdp->p_filesz, PROT_READ,
            MAP_PRIVATE|MAP_FILE, file, phdp->p_offset);
        if (syscalls == MAP_FAILED)
                return;

        /* Validate, and calculate pintable size */
        nsyscalls = phdp->p_filesz / sizeof(*syscalls);
        for (i = 0; i < nsyscalls; i++) {
                if (syscalls[i].sysno < 0 ||
                    syscalls[i].sysno >= SYS_MAXSYSCALL ||
                    syscalls[i].offset >= len)
                        goto bad;
                npins = MAXIMUM(npins, syscalls[i].sysno);
        }
        npins++;

        /*
         * Fill pintable: 0 = invalid, -1 = accept, else offset
         * from base, rebase to text_start while at it
         */
        pins = _dl_calloc(npins, sizeof(u_int));
        offset = exec_base - base;
        for (i = 0; i < nsyscalls; i++) {
                if (pins[syscalls[i].sysno])
                        pins[syscalls[i].sysno] = (u_int)-1; /* duplicated */
                else
                        pins[syscalls[i].sysno] = syscalls[i].offset - offset;
        }
        base += offset;
        len = len - offset;
bad:
        _dl_munmap(syscalls, phdp->p_filesz);
        if (pins)
                _dl_pinsyscalls(base, len, pins, npins);
        _dl_free(pins);
}