/* $OpenBSD: dlfcn.c,v 1.108 2021/10/07 14:47:32 jcs 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 #include #include #include #include #include "syscall.h" #include "archdep.h" #include "resolve.h" #include "sod.h" int _dl_errno; static int _dl_tracelib; static int _dl_real_close(void *handle); static lock_cb *_dl_thread_fnc = NULL; static elf_object_t *obj_from_addr(const void *addr); void * dlopen(const char *libname, int flags) { elf_object_t *object; lock_cb *cb; int failed = 0; int obj_flags; if (flags & ~(RTLD_TRACE|RTLD_LAZY|RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE)) { _dl_errno = DL_INVALID_MODE; return NULL; } if (libname == NULL) return RTLD_DEFAULT; if ((flags & RTLD_TRACE) == RTLD_TRACE) { _dl_traceld = 1; _dl_tracelib = 1; } DL_DEB(("dlopen: loading: %s\n", libname)); cb = _dl_thread_kern_stop(); if (_dl_debug_map && _dl_debug_map->r_brk) { _dl_debug_map->r_state = RT_ADD; (*((void (*)(void))_dl_debug_map->r_brk))(); } _dl_loading_object = NULL; obj_flags = (flags & RTLD_NOW ? DF_1_NOW : 0) | (flags & RTLD_GLOBAL ? DF_1_GLOBAL : 0); object = _dl_load_shlib(libname, _dl_objects, OBJTYPE_DLO, obj_flags); if (object == 0) { DL_DEB(("dlopen: failed to open %s\n", libname)); failed = 1; goto loaded; } if (flags & RTLD_NODELETE) object->obj_flags |= DF_1_NODELETE; _dl_link_dlopen(object); if (OBJECT_REF_CNT(object) > 1) { _dl_handle_nodelete(object); /* if opened but grpsym_vec has not been filled in */ if (object->grpsym_vec.len == 0) _dl_cache_grpsym_list_setup(object); goto loaded; } /* this add_object should not be here, XXX */ _dl_add_object(object); DL_DEB(("head [%s]\n", object->load_name )); if ((failed = _dl_load_dep_libs(object, obj_flags, 0)) == 1) { _dl_real_close(object); object = NULL; _dl_errno = DL_CANT_LOAD_OBJ; } else { int err; DL_DEB(("tail %s\n", object->load_name )); if (_dl_traceld) { _dl_show_objects(); _dl_unload_shlib(object); _dl_exit(0); } err = _dl_rtld(object); if (err != 0) { _dl_real_close(object); _dl_errno = DL_CANT_LOAD_OBJ; object = NULL; failed = 1; } else { _dl_call_init(object); } } loaded: _dl_loading_object = NULL; if (_dl_debug_map && _dl_debug_map->r_brk) { _dl_debug_map->r_state = RT_CONSISTENT; (*((void (*)(void))_dl_debug_map->r_brk))(); } _dl_thread_kern_go(cb); DL_DEB(("dlopen: %s: done (%s).\n", libname, failed ? "failed" : "success")); return((void *)object); } void * dlsym(void *handle, const char *name) { elf_object_t *object; elf_object_t *dynobj; struct sym_res sr; int flags; Elf_Addr addr; if (handle == NULL || handle == RTLD_NEXT || handle == RTLD_SELF || handle == RTLD_DEFAULT) { void *retaddr; retaddr = __builtin_return_address(0); /* __GNUC__ only */ if ((object = obj_from_addr(retaddr)) == NULL) { _dl_errno = DL_CANT_FIND_OBJ; return(0); } if (handle == RTLD_NEXT) flags = SYM_SEARCH_NEXT|SYM_PLT; else if (handle == RTLD_SELF) flags = SYM_SEARCH_SELF|SYM_PLT; else if (handle == RTLD_DEFAULT) flags = SYM_SEARCH_ALL|SYM_PLT; else flags = SYM_DLSYM|SYM_PLT; } else { object = (elf_object_t *)handle; flags = SYM_DLSYM|SYM_PLT; dynobj = _dl_objects; while (dynobj && dynobj != object) dynobj = dynobj->next; if (!dynobj || object != dynobj) { _dl_errno = DL_INVALID_HANDLE; return(0); } } sr = _dl_find_symbol(name, flags|SYM_NOWARNNOTFOUND, NULL, object); if (sr.sym == NULL) { DL_DEB(("dlsym: failed to find symbol %s\n", name)); _dl_errno = DL_NO_SYMBOL; return NULL; } addr = sr.obj->obj_base + sr.sym->st_value; #ifdef __hppa__ if (ELF_ST_TYPE(sr.sym->st_info) == STT_FUNC) addr = _dl_md_plabel(addr, sr.obj->dyn.pltgot); #endif DL_DEB(("dlsym: %s in %s: %p\n", name, object->load_name, (void *)addr)); return (void *)addr; } int dlctl(void *handle, int command, void *data) { int retval; switch (command) { case DL_SETTHREADLCK: DL_DEB(("dlctl: _dl_thread_fnc set to %p\n", data)); _dl_thread_fnc = data; retval = 0; break; case DL_SETBINDLCK: /* made superfluous by kbind */ retval = 0; break; case DL_REFERENCE: { elf_object_t *obj; obj = obj_from_addr(data); if (obj == NULL) { _dl_errno = DL_CANT_FIND_OBJ; retval = -1; break; } if ((obj->status & STAT_NODELETE) == 0) { obj->opencount++; obj->status |= STAT_NODELETE; } retval = 0; break; } case 0x20: _dl_show_objects(); retval = 0; break; case 0x21: { struct object_vector vec; struct dep_node *n, *m; elf_object_t *obj; int i; _dl_printf("Load Groups:\n"); TAILQ_FOREACH(n, &_dlopened_child_list, next_sib) { obj = n->data; _dl_printf("%s\n", obj->load_name); _dl_printf(" children\n"); for (vec = obj->child_vec, i = 0; i < vec.len; i++) _dl_printf("\t[%s]\n", vec.vec[i]->load_name); _dl_printf(" grpref\n"); TAILQ_FOREACH(m, &obj->grpref_list, next_sib) _dl_printf("\t[%s]\n", m->data->load_name); _dl_printf("\n"); } retval = 0; break; } default: _dl_errno = DL_INVALID_CTL; retval = -1; break; } return (retval); } __strong_alias(_dlctl,dlctl); int dlclose(void *handle) { lock_cb *cb; int retval; if (handle == RTLD_DEFAULT) return 0; cb = _dl_thread_kern_stop(); if (_dl_debug_map && _dl_debug_map->r_brk) { _dl_debug_map->r_state = RT_DELETE; (*((void (*)(void))_dl_debug_map->r_brk))(); } retval = _dl_real_close(handle); if (_dl_debug_map && _dl_debug_map->r_brk) { _dl_debug_map->r_state = RT_CONSISTENT; (*((void (*)(void))_dl_debug_map->r_brk))(); } _dl_thread_kern_go(cb); return (retval); } int _dl_real_close(void *handle) { elf_object_t *object; elf_object_t *dynobj; object = (elf_object_t *)handle; dynobj = _dl_objects; while (dynobj && dynobj != object) dynobj = dynobj->next; if (!dynobj || object != dynobj) { _dl_errno = DL_INVALID_HANDLE; return (1); } if (object->opencount == 0) { _dl_errno = DL_INVALID_HANDLE; return (1); } object->opencount--; _dl_notify_unload_shlib(object); _dl_run_all_dtors(); _dl_unload_shlib(object); _dl_cleanup_objects(); return (0); } /* * Return a character string describing the last dl... error occurred. */ char * dlerror(void) { char *errmsg; switch (_dl_errno) { case 0: /* NO ERROR */ errmsg = NULL; break; case DL_NOT_FOUND: errmsg = "File not found"; break; case DL_CANT_OPEN: errmsg = "Can't open file"; break; case DL_NOT_ELF: errmsg = "File not an ELF object"; break; case DL_CANT_OPEN_REF: errmsg = "Can't open referenced object"; break; case DL_CANT_MMAP: errmsg = "Can't map ELF object"; break; case DL_INVALID_HANDLE: errmsg = "Invalid handle"; break; case DL_NO_SYMBOL: errmsg = "Unable to resolve symbol"; break; case DL_INVALID_CTL: errmsg = "Invalid dlctl() command"; break; case DL_NO_OBJECT: errmsg = "No shared object contains address"; break; case DL_CANT_FIND_OBJ: errmsg = "Cannot determine caller's shared object"; break; case DL_CANT_LOAD_OBJ: errmsg = "Cannot load specified object"; break; case DL_INVALID_MODE: errmsg = "Invalid mode"; break; default: errmsg = "Unknown error"; } _dl_errno = 0; return (errmsg); } static void _dl_tracefmt(int fd, elf_object_t *object, const char *fmt1, const char *fmt2, const char *objtypename) { const char *fmt; int i; fmt = object->sod.sod_library ? fmt1 : fmt2; for (i = 0; fmt[i]; i++) { if (fmt[i] != '%' && fmt[i] != '\\') { _dl_dprintf(fd, "%c", fmt[i]); continue; } if (fmt[i] == '%') { i++; switch (fmt[i]) { case '\0': return; case '%': _dl_dprintf(fd, "%c", '%'); break; case 'A': _dl_dprintf(fd, "%s", _dl_traceprog ? _dl_traceprog : ""); break; case 'a': _dl_dprintf(fd, "%s", __progname); break; case 'e': _dl_dprintf(fd, "%lX", (void *)(object->load_base + object->load_size)); break; case 'g': _dl_dprintf(fd, "%d", object->grprefcount); break; case 'm': _dl_dprintf(fd, "%d", object->sod.sod_major); break; case 'n': _dl_dprintf(fd, "%d", object->sod.sod_minor); break; case 'O': _dl_dprintf(fd, "%d", object->opencount); break; case 'o': _dl_dprintf(fd, "%s", object->sod.sod_name); break; case 'p': _dl_dprintf(fd, "%s", object->load_name); break; case 'r': _dl_dprintf(fd, "%d", object->refcount); break; case 't': _dl_dprintf(fd, "%s", objtypename); break; case 'x': _dl_dprintf(fd, "%lX", object->load_base); break; } } if (fmt[i] == '\\') { i++; switch (fmt[i]) { case '\0': return; case 'n': _dl_dprintf(fd, "%c", '\n'); break; case 'r': _dl_dprintf(fd, "%c", '\r'); break; case 't': _dl_dprintf(fd, "%c", '\t'); break; default: _dl_dprintf(fd, "%c", fmt[i]); break; } } } } void _dl_show_objects(void) { elf_object_t *object; char *objtypename; int outputfd; char *pad; const char *fmt1, *fmt2; object = _dl_objects; if (_dl_traceld) outputfd = STDOUT_FILENO; else outputfd = STDERR_FILENO; if (sizeof(long) == 8) pad = " "; else pad = ""; fmt1 = _dl_tracefmt1 ? _dl_tracefmt1 : "\t%x %e %t %O %r %g %p\n"; fmt2 = _dl_tracefmt2 ? _dl_tracefmt2 : "\t%x %e %t %O %r %g %p\n"; if (_dl_tracefmt1 == NULL && _dl_tracefmt2 == NULL) _dl_dprintf(outputfd, "\tStart %s End %s Type Open Ref GrpRef Name\n", pad, pad); if (_dl_tracelib) { for (; object != NULL; object = object->next) if (object->obj_type == OBJTYPE_LDR) { object = object->next; break; } } for (; object != NULL; object = object->next) { switch (object->obj_type) { case OBJTYPE_LDR: objtypename = "ld.so"; break; case OBJTYPE_EXE: objtypename = "exe "; break; case OBJTYPE_LIB: objtypename = "rlib "; break; case OBJTYPE_DLO: objtypename = "dlib "; break; default: objtypename = "?????"; break; } _dl_tracefmt(outputfd, object, fmt1, fmt2, objtypename); } } lock_cb * _dl_thread_kern_stop(void) { lock_cb *cb = _dl_thread_fnc; if (cb != NULL) (*cb)(0); return cb; } void _dl_thread_kern_go(lock_cb *cb) { if (cb != NULL) (*cb)(1); } int dl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *data), void *data) { elf_object_t *object; struct dl_phdr_info info; int retval = -1; for (object = _dl_objects; object != NULL; object = object->next) { if (object->phdrp == NULL) continue; info.dlpi_addr = object->obj_base; info.dlpi_name = object->load_name; info.dlpi_phdr = object->phdrp; info.dlpi_phnum = object->phdrc; retval = callback(&info, sizeof (struct dl_phdr_info), data); if (retval) break; } return retval; } static elf_object_t * obj_from_addr(const void *addr) { elf_object_t *dynobj; Elf_Phdr *phdrp; int phdrc; Elf_Addr start; int i; for (dynobj = _dl_objects; dynobj != NULL; dynobj = dynobj->next) { if (dynobj->phdrp == NULL) continue; phdrp = dynobj->phdrp; phdrc = dynobj->phdrc; for (i = 0; i < phdrc; i++, phdrp++) { if (phdrp->p_type == PT_LOAD) { start = dynobj->obj_base + phdrp->p_vaddr; if ((Elf_Addr)addr >= start && (Elf_Addr)addr < start + phdrp->p_memsz) return dynobj; } } } return NULL; } int dladdr(const void *addr, Dl_info *info) { const elf_object_t *object; const Elf_Sym *sym; void *symbol_addr; u_int32_t symoffset; object = obj_from_addr(addr); if (object == NULL) { _dl_errno = DL_NO_OBJECT; return 0; } info->dli_fname = (char *)object->load_name; info->dli_fbase = (void *)object->load_base; info->dli_sname = NULL; info->dli_saddr = NULL; /* * Walk the symbol list looking for the symbol whose address is * closest to the address sent in. */ for (symoffset = 0; symoffset < object->nchains; symoffset++) { sym = object->dyn.symtab + symoffset; /* * For skip the symbol if st_shndx is either SHN_UNDEF or * SHN_COMMON. */ if (sym->st_shndx == SHN_UNDEF || sym->st_shndx == SHN_COMMON) continue; /* * If the symbol is greater than the specified address, or if * it is further away from addr than the current nearest * symbol, then reject it. */ symbol_addr = (void *)(object->obj_base + sym->st_value); if (symbol_addr > addr || symbol_addr < info->dli_saddr) continue; /* Update our idea of the nearest symbol. */ info->dli_sname = object->dyn.strtab + sym->st_name; info->dli_saddr = symbol_addr; /* Exact match? */ if (info->dli_saddr == addr) break; } return 1; }