#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"
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 (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;
}
}
void
_dl_add_object(elf_object_t *object)
{
_dl_handle_nodelete(object);
if (object->prev != NULL)
return;
if (_dl_objects == NULL) {
_dl_last_object = _dl_objects = object;
object_count = 2;
} else {
_dl_last_object->next = object;
object->prev = _dl_last_object;
_dl_last_object = object;
if (object->obj_type != OBJTYPE_LDR)
object_count++;
}
}
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);
}
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)) {
while (*pp != '\0' && *pp != '$' &&
(tp - tmp_path) < sizeof(tmp_path))
*tp++ = *pp++;
if (*pp == '$' && (tp - tmp_path) < sizeof(tmp_path)) {
pp++;
if ((skip_brace = (*pp == '{')))
pp++;
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++;
}
}
if (tp - tmp_path >= sizeof(tmp_path))
return;
*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;
}
static int
_dl_origin_path(elf_object_t *object, char *origin_path)
{
const char *dirname_path;
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;
}
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;
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);
}
}
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) {
_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);
}
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];
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 (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;
object->grprefcount = 0;
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);
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:
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
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;
}
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;
bloom_word = obj->bloom_gnu[(hash / ELFSIZE) &
obj->mask_bm_gnu];
h1 = hash & (ELFSIZE - 1);
h2 = (hash >> obj->shift2_gnu) & (ELFSIZE - 1);
if (((bloom_word >> h1) & (bloom_word >> h2) & 1) == 0)
return 0;
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,
};
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;
if (sl.sl_weak_out.sym != NULL)
goto found;
vec = req_obj->load_object->grpsym_vec;
for (i = 0; i < vec.len; i++) {
if (vec.vec[i] == req_obj)
continue;
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;
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)
{
}
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;
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++;
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;
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);
}