# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. # include $(SRC)/cmd/sgs/Makefile.com EXTRASUBDIRS = mdbmod include $(SRC)/cmd/sgs/Makefile.sub # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 2018, Joyent, Inc. # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # RTLD= ld.so.1 AVLOBJ= avl.o DTROBJ= dtrace_data.o SGSCOMMONOBJ= alist.o strhash.o BLTOBJ= msg.o ELFCAPOBJ= elfcap.o OBJECTS= $(BLTOBJ) \ $(AVLOBJ) \ $(DTROBJ) \ $(SGSCOMMONOBJ) \ $(ELFCAPOBJ) \ $(P_ASOBJS) $(P_COMOBJS) $(P_MACHOBJS) $(G_MACHOBJS) \ $(S_ASOBJS) $(S_COMOBJS) $(S_MACHOBJS) $(CP_MACHOBJS) COMOBJS= $(P_COMOBJS) $(S_COMOBJS) ASOBJS= $(P_ASOBJS) $(S_ASOBJS) MACHOBJS= $(P_MACHOBJS) $(S_MACHOBJS) NOCTFOBJS= $(ASOBJS) include $(SRC)/lib/Makefile.lib include $(SRC)/cmd/sgs/Makefile.com # Hammerhead: ld.so.1 MUST be linked by illumos ld (not GNU ld). # GNU ld produces ELFOSABI_NONE, missing PT_SUNWDTRACE/PT_SUNW_UNWIND headers, # and wrong segment alignment — the kernel cannot properly map the result. # The actual LD override is in the arch-specific Makefile (amd64/Makefile) # because it must come AFTER Makefile.master.64 which resets LD. SRCDIR = ../common ELFCAP = $(SRC)/common/elfcap PLAT_i386 = intel/ia32 PLAT_amd64 = intel/amd64 PLAT_sparc = sparc PLAT = $(PLAT_$(BASEPLAT)) # DTrace needs an executable data segment. MAPFILE.NED= MAPFILES += $(MAPFILE-ORDER) # For the libc/libthread unified world: # This library needs to be placed in /lib to allow # dlopen() functionality while in single-user mode. ROOTFS_DYNLIB= $(RTLD:%=$(ROOTFS_LIBDIR)/%) ROOTFS_DYNLIB64= $(RTLD:%=$(ROOTFS_LIBDIR64)/%) # For the libc/libthread separated world: # A version of this library needs to be placed in /etc/lib to allow # dlopen() functionality while in single-user mode. ETCLIBDIR= $(ROOT)/etc/lib ETCDYNLIB= $(RTLD:%=$(ETCLIBDIR)/%) ROOTDYNLIB= $(RTLD:%=$(ROOTFS_LIBDIR)/%) ROOTDYNLIB64= $(RTLD:%=$(ROOTFS_LIBDIR64)/%) COMPATLINKS= etc/lib/ld.so.1 \ usr/lib/ld.so.1 # Hammerhead: 64-bit only - no amd64 compat links needed (covered by COMPATLINKS) COMPATLINKS64= $(ROOT)/etc/lib/ld.so.1 : COMPATLINKTARGET= ../../lib/ld.so.1 $(ROOT)/usr/lib/ld.so.1 : COMPATLINKTARGET= ../../lib/ld.so.1 FILEMODE = 755 CPPFLAGS += -I$(SRC)/lib/libc/inc \ -I$(SRC)/uts/common/krtld \ -I$(SRC)/uts/$(PLAT) \ -I$(SRC)/uts/$(PLAT)/krtld \ -I$(SRC)/common/sgsrtcid \ -I$(ELFCAP) \ $(CPPFEATUREMACROS) ASFLAGS += -D_ASM $(CPPFLAGS) ASFLAGS64 += -D_ASM $(CPPFLAGS) LDLIB = -L ../../libld/$(MACH) RTLDLIB = -L ../../librtld/$(MACH) CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off # These definitions require that libc be built in the same workspace # as the run-time linker and before the run-time linker is built. # This is required for the system's self-consistency in any case. CPICLIB = -L $(SRC)/lib/libc/$(MACH) CPICLIB64 = -L $(SRC)/lib/libc/$(MACH64) CLIB = -lc_pic LDLIBS += $(CONVLIBDIR) -lconv \ $(CPICLIB) $(CLIB) \ $(LDDBGLIBDIR) -llddbg \ $(RTLDLIB) -lrtld \ $(LDLIB) -lld # -Bsymbolic: all global symbols resolve locally (no interposition). # Safe for rtld because it IS the runtime linker - never interposed. DYNFLAGS += -i -e _rt_boot $(VERSREF) $(ZNODLOPEN) \ $(ZINTERPOSE) -zdtrace=dtrace_data '-R$$ORIGIN' \ -Bsymbolic BLTDEFS= msg.h BLTDATA= msg.c BLTMESG= $(SGSMSGDIR)/rtld BLTFILES= $(BLTDEFS) $(BLTDATA) $(BLTMESG) # Hammerhead: amd64-only (sparc, 32-bit variants removed). SGSMSGCOM= ../common/rtld.msg SGSMSG64= ../common/rtld.64.msg SGSMSGINTEL= ../common/rtld.intel.msg SGSMSGINTEL64= ../common/rtld.intel64.msg SGSMSGCHK= ../common/rtld.chk.msg SGSMSGTARG= $(SGSMSGCOM) SGSMSGALL= $(SGSMSGCOM) $(SGSMSG64) \ $(SGSMSGINTEL) $(SGSMSGINTEL64) SGSMSGFLAGS1= $(SGSMSGFLAGS) -m $(BLTMESG) SGSMSGFLAGS2= $(SGSMSGFLAGS) -h $(BLTDEFS) -d $(BLTDATA) -n rtld_msg SRCS= $(AVLOBJ:%.o=$(SRC)/common/avl/%.c) \ $(DTROBJ:%.o=$(SRC)/common/dtrace/%.c) \ $(SGSCOMMONOBJ:%.o=$(SGSCOMMON)/%.c) \ $(COMOBJS:%.o=../common/%.c) $(MACHOBJS:%.o=%.c) $(BLTDATA) \ $(G_MACHOBJS:%.o=$(SRC)/uts/$(PLAT)/krtld/%.c) \ $(CP_MACHOBJS:%.o=../$(MACH)/%.c) \ $(ASOBJS:%.o=%.S) CLEANFILES += $(CRTS) $(BLTFILES) CLOBBERFILES += $(RTLD) # # We cannot currently enable the stack protector for rtld as it runs # before libc initializes, which is where we always enable the stack # protector values. Because rtld is likely on an alternate link map and # links in the relevant portions of libc through libc_pic.a, there is # probably a path to enabling an rtld specific version of the stack # protector. # # As a result, this currently disables the stack protector in two # related targets which really could use it. These are libconv and libc. # Both of these end up building position-independent archive libraries # that are directly linked into rtld. This situation can and should be # improved. # STACKPROTECT = none # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # # Copyright (c) 2019, Joyent, Inc. # # Since we use RTLD rather than DYNLIB, we must explicitly hook in ctfmerge. $(RTLD) : CTFMERGE_POST = $(CTFMERGE_LIB) pics/%.o: %.S $(COMPILE.s) -o $@ $< $(POST_PROCESS_S_O) pics/%.o: $(CRTSRCS)/%.s $(COMPILE.s) -o $@ $< $(POST_PROCESS_S_O) pics/%.o: $(CRTSRCS)/%.S $(COMPILE.s) -o $@ $< $(POST_PROCESS_S_O) pics/%.o: %.c $(COMPILE.c) -o $@ $< $(POST_PROCESS_O) pics/%.o: ../common/%.c $(COMPILE.c) -o $@ $< $(POST_PROCESS_O) pics/%.o: $(SRC)/common/avl/%.c $(COMPILE.c) -o $@ $< $(POST_PROCESS_O) pics/%.o: $(SRC)/common/dtrace/%.c $(COMPILE.c) -o $@ $< $(POST_PROCESS_O) pics/%.o: $(SGSCOMMON)/%.c $(COMPILE.c) -o $@ $< $(POST_PROCESS_O) pics/%.o: $(SRC)/uts/$(PLAT)/krtld/%.c $(COMPILE.c) -o $@ $< $(POST_PROCESS_O) pics/elfcap.o: $(ELFCAP)/elfcap.c $(COMPILE.c) -o $@ $(ELFCAP)/elfcap.c $(POST_PROCESS_O) $(RTLD): pics $(PICS) $(CRTS) $(LD) -o $@ -dy $(GSHARED) $(DYNFLAGS) $(CRTI) $(PICS) \ $(LDLIBS) $(CRTN) $(POST_PROCESS_SO) $(ROOTFS_LIBDIR)/%: % $(INS.file) $(ETCLIBDIR)/%: % $(INS.file) include $(SRC)/lib/Makefile.targ # Derived source and header files (messaging). catalog: $(BLTMESG) chkmsg : SGSMSGCHK=../common/rtld.chk.msg chkmsg: $(SRCS) $(CHKSRCS) sh $(CHKMSG) $(CHKMSGFLAGS) $(SRCS) $(CHKSRCS) # Hammerhead: GNU Make grouped target syntax (&:) - was dmake + syntax $(BLTDEFS) $(BLTDATA) &: $(SGSMSGTARG) $(SGSMSG) $(SGSMSGFLAGS2) $(SGSMSGTARG) $(BLTMESG) : $(SGSMSGALL) $(SGSMSG) $(SGSMSGFLAGS1) $(SGSMSGALL) # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # BASEPLAT = amd64 # Object lists are organized into primary (most frequently used code) and # secondary lists (less frequently used code). P_COMOBJS= debugdata.o \ analyze.o elf.o external.o globals.o \ malloc.o paths.o setup.o util.o \ dlfcns.o config_elf.o locale.o tsort.o \ remove.o move.o tls.o cap.o S_COMOBJS= debug.o audit.o object.o G_MACHOBJS= doreloc.o P_MACHOBJS= amd64_elf.o _setup.o dlamd64getunwind.o CP_MACHOBJS= S_MACHOBJS= P_ASOBJS= boot.o boot_elf.o caller.o S_ASOBJS= CRTSRCS= ../../../../lib/crt/amd64 CRTI= pics/crti.o CRTN= pics/crtn.o CRTS= $(CRTI) $(CRTN) include $(SRC)/cmd/sgs/rtld/Makefile.com include $(SRC)/lib/Makefile.lib.64 MAPFILE-ORDER = ../common/mapfile-order-gcc # Add any machine specific flags. ASFLAGS += -D__amd64 -D_ELF64 $(amd64_ASFLAGS) ADBGENFLAGS += -mlp64 ADBSUB= $(ADBSUB64) CPPFLAGS += -D_ELF64 # Hammerhead: 64-bit only - runtime linker at flattened path SONAME= /lib/ld.so.1 SGSMSGTARG += $(SGSMSGINTEL) $(SGSMSGINTEL64) $(SGSMSG64) LDLIB = -L ../../libld/$(MACH64) RTLDLIB = -L ../../librtld/$(MACH64) CPICLIB = $(CPICLIB64) LDDBGLIBDIR = $(LDDBGLIBDIR64) CONVLIBDIR = $(CONVLIBDIR64) .KEEP_STATE: all: $(RTLD) install: all $(ROOTDYNLIB64) $(ROOTCOMPATLINKS) $(ROOTCOMPATLINKS64) adbmacros: adb .WAIT $(ADBSCRIPTS) adbinstall: adbmacros .WAIT $(ROOTADB64) include $(SRC)/cmd/sgs/rtld/Makefile.targ include $(SRC)/Makefile.master.64 # Hammerhead: ld.so.1 MUST be linked by illumos ld (sunld), not GNU ld. # GNU ld produces ELFOSABI_NONE, missing PT_SUNWDTRACE/PT_SUNW_UNWIND program # headers, and wrong segment alignment — the kernel cannot properly map the # result. Override LD after Makefile.master.64 (which resets LD=$(LD64)). # # `unexport LD_ALTEXEC` does NOT reliably prevent sunld from seeing the # variable in recipe subshells (gmake may still pass it through). When # LD_ALTEXEC is set to gnu-ld-wrapper, sunld re-execs that wrapper, # which silently ignores `-z dtrace=dtrace_data` — the resulting # ld.so.1 has PT_SUNWDTRACE with p_memsz=0, and the kernel's # dtrace_safe_phdr() rejects it with silent ENOEXEC when exec'ing any # binary that uses ld.so.1 as its interpreter (including /sbin/init). # # Use the same wrapper script kernel linking uses: a single-token path # that unsets LD_ALTEXEC before exec'ing /usr/bin/sunld. See # `usr/src/tools/scripts/sunld-kernel.sh`. unexport LD_ALTEXEC LD = $(SRC)/tools/scripts/sunld-kernel.sh GSHARED = -G # Redefine DYNFLAGS in illumos ld native syntax (no -Wl, prefix). # The Makefile.targ link rule calls $(LD) directly, not $(CC). # Note: illumos ld requires space between -z and the keyword. DYNFLAGS = -h $(SONAME) -z textoff -z defs \ $(MAPFILES:%=-M %) $(MAPFILE.PGA:%=-M %) \ -i -e _rt_boot $(VERSREF) $(ZNODLOPEN) \ $(ZINTERPOSE) -z dtrace=dtrace_data '-R$$ORIGIN' \ -Bsymbolic ZNODLOPEN = -z nodlopen ZINTERPOSE = -z interpose # Hammerhead: GNU strip corrupts ld.so.1 in two places. # # 1. POST_PROCESS_SO runs STRIP_STABS ($(STRIP) -x $@) after the link # rule in Makefile.targ. On Hammerhead $(STRIP) is GNU strip. # 2. INS.file in Makefile.master passes `-s` to install(1), which # invokes GNU strip again at install time. # # GNU strip handles the Solaris PT_SUNWDTRACE program header # incorrectly: # # - sunld with `-z dtrace=dtrace_data` creates a PT_SUNWDTRACE # program header with p_filesz=0 (NOBITS — dtrace_data is in # .bss), p_memsz=0x40 (= FASTTRAP_SUNWDTRACE_SIZE), p_vaddr set to # the symbol address. # - GNU strip sees p_filesz=0 and 'optimizes' by zeroing p_memsz as # well, and drops the `dtrace_data` symbol entirely. # - The kernel's dtrace_safe_phdr() at elf.c:123 rejects any # interpreter whose PT_SUNWDTRACE p_memsz < PT_SUNWDTRACE_SIZE (64). # - Result: every exec() of a dynamic binary that uses ld.so.1 as # its interpreter fails with ENOEXEC (including /sbin/init). # # Disable both strip calls. The rest of POST_PROCESS_SO (mcs -d, # elfsign) still runs. ld.so.1 ends up larger (~1.4 MB unstripped vs # ~474 KB stripped) but exec works. STRIP_STABS = : INS.file = $(RM) $@; $(INS) -m $(FILEMODE) -f $(@D) $< /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2018, Joyent, Inc. * Copyright 2022 Oxide Computer Company */ /* * amd64 specific setup routine - relocate ld.so's symbols, setup its * environment, map in loadable sections of the executable. * * Takes base address ld.so was loaded at, address of ld.so's dynamic * structure, address of process environment pointers, address of auxiliary * vector and * argv[0] (process name). * If errors occur, send process signal - otherwise * return executable's entry point to the bootstrap routine. */ #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "msg.h" /* * Number of bytes to save for register usage. */ uint_t _plt_save_size; void (*_plt_fp_save)(void *); void (*_plt_fp_restore)(void *); extern void _elf_rtbndr_fp_save_orig(void *); extern void _elf_rtbndr_fp_restore_orig(void *); extern void _elf_rtbndr_fp_fxsave(void *); extern void _elf_rtbndr_fp_fxrestore(void *); extern void _elf_rtbndr_fp_xsave(void *); extern void _elf_rtbndr_fp_xrestore(void *); /* * Based on what the kernel has told us, go through and set up the various * pointers that we'll need for elf_rtbndr for the FPU. */ static void _setup_plt_fpu(int kind, size_t len) { /* * If we didn't get a length for some reason, fall back to the old * implementation. */ if (len == 0) kind = -1; switch (kind) { case AT_386_FPINFO_FXSAVE: _plt_fp_save = _elf_rtbndr_fp_fxsave; _plt_fp_restore = _elf_rtbndr_fp_fxrestore; _plt_save_size = len; break; /* * We can treat processors that don't correctly handle the exception * information in xsave the same way we do others. The information * that may or may not be properly saved and restored should not be * relevant to us because of the ABI. */ case AT_386_FPINFO_XSAVE: case AT_386_FPINFO_XSAVE_AMD: _plt_fp_save = _elf_rtbndr_fp_xsave; _plt_fp_restore = _elf_rtbndr_fp_xrestore; _plt_save_size = len; break; default: _plt_fp_save = _elf_rtbndr_fp_save_orig; _plt_fp_restore = _elf_rtbndr_fp_restore_orig; /* * The ABI says that 8 floating point registers are used for * passing arguments (%xmm0 through %xmm7). Because these * registers on some platforms may shadow the %ymm and %zmm * registers, we end up needing to size this for the maximally * sized register we care about, a 512-bit (64-byte) zmm * register. */ _plt_save_size = 64 * 8; break; } } /* VARARGS */ unsigned long _setup(Boot *ebp, Dyn *ld_dyn) { ulong_t reladdr, relacount, ld_base = 0; ulong_t relaent = 0, pltrelsz = 0; ulong_t strtab, soname, interp_base = 0; char *_rt_name, **_envp, **_argv; int _syspagsz = 0, fd = -1; uint_t _flags = 0; uint_t hwcap[3] = { 0, 0, 0 }; Dyn *dyn_ptr; Phdr *phdr = NULL; Rt_map *lmp; auxv_t *auxv, *_auxv; uid_t uid = (uid_t)-1, euid = (uid_t)-1; gid_t gid = (gid_t)-1, egid = (gid_t)-1; char *_platform = NULL, *_execname = NULL, *_emulator = NULL; int auxflags = -1, fpkind = -1; size_t fpsize = 0; /* * Scan the bootstrap structure to pick up the basics. */ for (; ebp->eb_tag != EB_NULL; ebp++) switch (ebp->eb_tag) { case EB_LDSO_BASE: ld_base = (unsigned long)ebp->eb_un.eb_val; break; case EB_ARGV: _argv = (char **)ebp->eb_un.eb_ptr; break; case EB_ENVP: _envp = (char **)ebp->eb_un.eb_ptr; break; case EB_AUXV: _auxv = (auxv_t *)ebp->eb_un.eb_ptr; break; case EB_PAGESIZE: _syspagsz = (int)ebp->eb_un.eb_val; break; } /* * Search the aux. vector for the information passed by exec. */ for (auxv = _auxv; auxv->a_type != AT_NULL; auxv++) { switch (auxv->a_type) { case AT_EXECFD: /* this is the old exec that passes a file descriptor */ fd = (int)auxv->a_un.a_val; break; case AT_FLAGS: /* processor flags (MAU available, etc) */ _flags = auxv->a_un.a_val; break; case AT_PAGESZ: /* system page size */ _syspagsz = (int)auxv->a_un.a_val; break; case AT_PHDR: /* address of the segment table */ phdr = (Phdr *)auxv->a_un.a_ptr; break; case AT_BASE: /* interpreter base address */ if (ld_base == 0) ld_base = auxv->a_un.a_val; interp_base = auxv->a_un.a_val; break; case AT_SUN_UID: /* effective user id for the executable */ euid = (uid_t)auxv->a_un.a_val; break; case AT_SUN_RUID: /* real user id for the executable */ uid = (uid_t)auxv->a_un.a_val; break; case AT_SUN_GID: /* effective group id for the executable */ egid = (gid_t)auxv->a_un.a_val; break; case AT_SUN_RGID: /* real group id for the executable */ gid = (gid_t)auxv->a_un.a_val; break; case AT_SUN_PLATFORM: /* platform name */ _platform = auxv->a_un.a_ptr; break; case AT_SUN_EXECNAME: /* full pathname of execed object */ _execname = auxv->a_un.a_ptr; break; case AT_SUN_AUXFLAGS: /* auxiliary flags */ auxflags = (int)auxv->a_un.a_val; break; case AT_SUN_HWCAP: /* hardware capabilities */ hwcap[0] = (uint_t)auxv->a_un.a_val; break; case AT_SUN_HWCAP2: /* hardware capabilities */ hwcap[1] = (uint_t)auxv->a_un.a_val; break; case AT_SUN_HWCAP3: /* hardware capabilities */ hwcap[2] = (uint_t)auxv->a_un.a_val; break; case AT_SUN_EMULATOR: /* name of emulation library, if any */ _emulator = auxv->a_un.a_ptr; break; case AT_SUN_FPTYPE: fpkind = (int)auxv->a_un.a_val; break; case AT_SUN_FPSIZE: fpsize = (size_t)auxv->a_un.a_val; break; } } /* * Get needed info from ld.so's dynamic structure. */ /* LINTED */ dyn_ptr = (Dyn *)((char *)ld_dyn + ld_base); for (ld_dyn = dyn_ptr; ld_dyn->d_tag != DT_NULL; ld_dyn++) { switch (ld_dyn->d_tag) { case DT_RELA: reladdr = ld_dyn->d_un.d_ptr + ld_base; break; case DT_RELACOUNT: relacount = ld_dyn->d_un.d_val; break; case DT_RELAENT: relaent = ld_dyn->d_un.d_val; break; case DT_PLTRELSZ: pltrelsz = ld_dyn->d_un.d_val; break; case DT_STRTAB: strtab = ld_dyn->d_un.d_ptr + ld_base; break; case DT_SONAME: soname = ld_dyn->d_un.d_val; break; } } _rt_name = (char *)strtab + soname; /* * If we don't have a RELAENT, just assume the size. */ if (relaent == 0) relaent = sizeof (Rela); /* * As all global symbol references within ld.so.1 are protected * (symbolic), only RELATIVE and JMPSLOT relocations should be left * to process at runtime. Process all relocations now. */ relacount += (pltrelsz / relaent); for (; relacount; relacount--) { ulong_t roffset; roffset = ((Rela *)reladdr)->r_offset + ld_base; *((ulong_t *)roffset) += ld_base + ((Rela *)reladdr)->r_addend; reladdr += relaent; } /* * If an emulation library is being used, use that as the linker's * effective executable name. The real executable is not linked by this * linker. */ if (_emulator != NULL) { _execname = _emulator; rtld_flags2 |= RT_FL2_BRANDED; } /* * Initialize the dyn_plt_ent_size field. It currently contains the * size of the dyn_plt_template. It still needs to be aligned and have * space for the 'dyn_data' area added. */ dyn_plt_ent_size = ROUND(dyn_plt_ent_size, M_WORD_ALIGN) + sizeof (uintptr_t) + sizeof (uintptr_t) + sizeof (ulong_t) + sizeof (ulong_t) + sizeof (Sym); /* * Initialize the amd64 specific PLT relocation constants based on the * FP information that we have. */ _setup_plt_fpu(fpkind, fpsize); /* * Continue with generic startup processing. */ if ((lmp = setup((char **)_envp, (auxv_t *)_auxv, _flags, _platform, _syspagsz, _rt_name, ld_base, interp_base, fd, phdr, _execname, _argv, uid, euid, gid, egid, auxflags, hwcap)) == NULL) { rtldexit(&lml_main, 1); } return (LM_ENTRY_PT(lmp)()); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, Joyent, Inc. All rights reserved. */ /* * amd64 machine dependent and ELF file class dependent functions. * Contains routines for performing function binding and symbol relocations. */ #include #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" #include "_inline_gen.h" #include "_inline_reloc.h" #include "msg.h" extern void elf_rtbndr(Rt_map *, ulong_t, caddr_t); int elf_mach_flags_check(Rej_desc *rej, Ehdr *ehdr) { /* * Check machine type and flags. */ if (ehdr->e_flags != 0) { rej->rej_type = SGS_REJ_BADFLAG; rej->rej_info = (uint_t)ehdr->e_flags; return (0); } return (1); } void ldso_plt_init(Rt_map *lmp) { /* * There is no need to analyze ld.so because we don't map in any of * its dependencies. However we may map these dependencies in later * (as if ld.so had dlopened them), so initialize the plt and the * permission information. */ if (PLTGOT(lmp)) elf_plt_init((PLTGOT(lmp)), (caddr_t)lmp); } static const uchar_t dyn_plt_template[] = { /* 0x00 */ 0x55, /* pushq %rbp */ /* 0x01 */ 0x48, 0x89, 0xe5, /* movq %rsp, %rbp */ /* 0x04 */ 0x48, 0x83, 0xec, 0x10, /* subq $0x10, %rsp */ /* 0x08 */ 0x4c, 0x8d, 0x1d, 0x00, /* leaq trace_fields(%rip), %r11 */ 0x00, 0x00, 0x00, /* 0x0f */ 0x4c, 0x89, 0x5d, 0xf8, /* movq %r11, -0x8(%rbp) */ /* 0x13 */ 0x49, 0xbb, 0x00, 0x00, /* movq $elf_plt_trace, %r11 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x1d */ 0x41, 0xff, 0xe3 /* jmp *%r11 */ /* 0x20 */ }; /* * And the virutal outstanding relocations against the * above block are: * * reloc offset Addend symbol * R_AMD64_PC32 0x0b -4 trace_fields * R_AMD64_64 0x15 0 elf_plt_trace */ #define TRCREL1OFF 0x0b #define TRCREL2OFF 0x15 int dyn_plt_ent_size = sizeof (dyn_plt_template); /* * the dynamic plt entry is: * * pushq %rbp * movq %rsp, %rbp * subq $0x10, %rsp * leaq trace_fields(%rip), %r11 * movq %r11, -0x8(%rbp) * movq $elf_plt_trace, %r11 * jmp *%r11 * dyn_data: * .align 8 * uintptr_t reflmp * uintptr_t deflmp * uint_t symndx * uint_t sb_flags * Sym symdef */ static caddr_t elf_plt_trace_write(ulong_t roffset, Rt_map *rlmp, Rt_map *dlmp, Sym *sym, uint_t symndx, uint_t pltndx, caddr_t to, uint_t sb_flags, int *fail) { extern int elf_plt_trace(); ulong_t got_entry; uchar_t *dyn_plt; uintptr_t *dyndata; /* * We only need to add the glue code if there is an auditing * library that is interested in this binding. */ dyn_plt = (uchar_t *)((uintptr_t)AUDINFO(rlmp)->ai_dynplts + (pltndx * dyn_plt_ent_size)); /* * Have we initialized this dynamic plt entry yet? If we haven't do it * now. Otherwise this function has been called before, but from a * different plt (ie. from another shared object). In that case * we just set the plt to point to the new dyn_plt. */ if (*dyn_plt == 0) { Sym *symp; Xword symvalue; Lm_list *lml = LIST(rlmp); (void) memcpy((void *)dyn_plt, dyn_plt_template, sizeof (dyn_plt_template)); dyndata = (uintptr_t *)((uintptr_t)dyn_plt + ROUND(sizeof (dyn_plt_template), M_WORD_ALIGN)); /* * relocate: * leaq trace_fields(%rip), %r11 * R_AMD64_PC32 0x0b -4 trace_fields */ symvalue = (Xword)((uintptr_t)dyndata - (uintptr_t)(&dyn_plt[TRCREL1OFF]) - 4); if (do_reloc_rtld(R_AMD64_PC32, &dyn_plt[TRCREL1OFF], &symvalue, MSG_ORIG(MSG_SYM_LADYNDATA), MSG_ORIG(MSG_SPECFIL_DYNPLT), lml) == 0) { *fail = 1; return (0); } /* * relocating: * movq $elf_plt_trace, %r11 * R_AMD64_64 0x15 0 elf_plt_trace */ symvalue = (Xword)elf_plt_trace; if (do_reloc_rtld(R_AMD64_64, &dyn_plt[TRCREL2OFF], &symvalue, MSG_ORIG(MSG_SYM_ELFPLTTRACE), MSG_ORIG(MSG_SPECFIL_DYNPLT), lml) == 0) { *fail = 1; return (0); } *dyndata++ = (uintptr_t)rlmp; *dyndata++ = (uintptr_t)dlmp; *dyndata = (uintptr_t)(((uint64_t)sb_flags << 32) | symndx); dyndata++; symp = (Sym *)dyndata; *symp = *sym; symp->st_value = (Addr)to; } got_entry = (ulong_t)roffset; *(ulong_t *)got_entry = (ulong_t)dyn_plt; return ((caddr_t)dyn_plt); } /* * Function binding routine - invoked on the first call to a function through * the procedure linkage table; * passes first through an assembly language interface. * * Takes the offset into the relocation table of the associated * relocation entry and the address of the link map (rt_private_map struct) * for the entry. * * Returns the address of the function referenced after re-writing the PLT * entry to invoke the function directly. * * On error, causes process to terminate with a signal. */ ulong_t elf_bndr(Rt_map *lmp, ulong_t pltndx, caddr_t from) { Rt_map *nlmp, *llmp; ulong_t addr, reloff, symval, rsymndx; char *name; Rela *rptr; Sym *rsym, *nsym; uint_t binfo, sb_flags = 0, dbg_class; Slookup sl; Sresult sr; int entry, lmflags; Lm_list *lml; /* * For compatibility with libthread (TI_VERSION 1) we track the entry * value. A zero value indicates we have recursed into ld.so.1 to * further process a locking request. Under this recursion we disable * tsort and cleanup activities. */ entry = enter(0); lml = LIST(lmp); if ((lmflags = lml->lm_flags) & LML_FLG_RTLDLM) { dbg_class = dbg_desc->d_class; dbg_desc->d_class = 0; } /* * Perform some basic sanity checks. If we didn't get a load map or * the relocation offset is invalid then its possible someone has walked * over the .got entries or jumped to plt0 out of the blue. */ if ((!lmp) && (pltndx <= (ulong_t)PLTRELSZ(lmp) / (ulong_t)RELENT(lmp))) { Conv_inv_buf_t inv_buf; eprintf(lml, ERR_FATAL, MSG_INTL(MSG_REL_PLTREF), conv_reloc_amd64_type(R_AMD64_JUMP_SLOT, 0, &inv_buf), EC_NATPTR(lmp), EC_XWORD(pltndx), EC_NATPTR(from)); rtldexit(lml, 1); } reloff = pltndx * (ulong_t)RELENT(lmp); /* * Use relocation entry to get symbol table entry and symbol name. */ addr = (ulong_t)JMPREL(lmp); rptr = (Rela *)(addr + reloff); rsymndx = ELF_R_SYM(rptr->r_info); rsym = (Sym *)((ulong_t)SYMTAB(lmp) + (rsymndx * SYMENT(lmp))); name = (char *)(STRTAB(lmp) + rsym->st_name); /* * Determine the last link-map of this list, this'll be the starting * point for any tsort() processing. */ llmp = lml->lm_tail; /* * Find definition for symbol. Initialize the symbol lookup, and * symbol result, data structures. */ SLOOKUP_INIT(sl, name, lmp, lml->lm_head, ld_entry_cnt, 0, rsymndx, rsym, 0, LKUP_DEFT); SRESULT_INIT(sr, name); if (lookup_sym(&sl, &sr, &binfo, NULL) == 0) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_REL_NOSYM), NAME(lmp), demangle(name)); rtldexit(lml, 1); } name = (char *)sr.sr_name; nlmp = sr.sr_dmap; nsym = sr.sr_sym; symval = nsym->st_value; if (!(FLAGS(nlmp) & FLG_RT_FIXED) && (nsym->st_shndx != SHN_ABS)) symval += ADDR(nlmp); if ((lmp != nlmp) && ((FLAGS1(nlmp) & FL1_RT_NOINIFIN) == 0)) { /* * Record that this new link map is now bound to the caller. */ if (bind_one(lmp, nlmp, BND_REFER) == 0) rtldexit(lml, 1); } if ((lml->lm_tflags | AFLAGS(lmp) | AFLAGS(nlmp)) & LML_TFLG_AUD_SYMBIND) { uint_t symndx = (((uintptr_t)nsym - (uintptr_t)SYMTAB(nlmp)) / SYMENT(nlmp)); symval = audit_symbind(lmp, nlmp, nsym, symndx, symval, &sb_flags); } if (!(rtld_flags & RT_FL_NOBIND)) { addr = rptr->r_offset; if (!(FLAGS(lmp) & FLG_RT_FIXED)) addr += ADDR(lmp); if (((lml->lm_tflags | AFLAGS(lmp)) & (LML_TFLG_AUD_PLTENTER | LML_TFLG_AUD_PLTEXIT)) && AUDINFO(lmp)->ai_dynplts) { int fail = 0; uint_t pltndx = reloff / sizeof (Rela); uint_t symndx = (((uintptr_t)nsym - (uintptr_t)SYMTAB(nlmp)) / SYMENT(nlmp)); symval = (ulong_t)elf_plt_trace_write(addr, lmp, nlmp, nsym, symndx, pltndx, (caddr_t)symval, sb_flags, &fail); if (fail) rtldexit(lml, 1); } else { /* * Write standard PLT entry to jump directly * to newly bound function. */ *(ulong_t *)addr = symval; } } /* * Print binding information and rebuild PLT entry. */ DBG_CALL(Dbg_bind_global(lmp, (Addr)from, (Off)(from - ADDR(lmp)), (Xword)(reloff / sizeof (Rela)), PLT_T_FULL, nlmp, (Addr)symval, nsym->st_value, name, binfo)); /* * Complete any processing for newly loaded objects. Note we don't * know exactly where any new objects are loaded (we know the object * that supplied the symbol, but others may have been loaded lazily as * we searched for the symbol), so sorting starts from the last * link-map know on entry to this routine. */ if (entry) load_completion(llmp); /* * Some operations like dldump() or dlopen()'ing a relocatable object * result in objects being loaded on rtld's link-map, make sure these * objects are initialized also. */ if ((LIST(nlmp)->lm_flags & LML_FLG_RTLDLM) && LIST(nlmp)->lm_init) load_completion(nlmp); /* * Make sure the object to which we've bound has had it's .init fired. * Cleanup before return to user code. */ if (entry) { is_dep_init(nlmp, lmp); leave(lml, 0); } if (lmflags & LML_FLG_RTLDLM) dbg_desc->d_class = dbg_class; return (symval); } /* * Read and process the relocations for one link object, we assume all * relocation sections for loadable segments are stored contiguously in * the file. */ int elf_reloc(Rt_map *lmp, uint_t plt, int *in_nfavl, APlist **textrel) { ulong_t relbgn, relend, relsiz, basebgn; ulong_t pltbgn, pltend, _pltbgn, _pltend; ulong_t roffset, rsymndx, psymndx = 0; ulong_t dsymndx; uchar_t rtype; long reladd, value, pvalue; Sym *symref, *psymref, *symdef, *psymdef; Syminfo *sip; char *name, *pname; Rt_map *_lmp, *plmp; int ret = 1, noplt = 0; int relacount = RELACOUNT(lmp), plthint = 0; Rela *rel; uint_t binfo, pbinfo; APlist *bound = NULL; /* * Although only necessary for lazy binding, initialize the first * global offset entry to go to elf_rtbndr(). dbx(1) seems * to find this useful. */ if ((plt == 0) && PLTGOT(lmp)) { mmapobj_result_t *mpp; /* * Make sure the segment is writable. */ if ((((mpp = find_segment((caddr_t)PLTGOT(lmp), lmp)) != NULL) && ((mpp->mr_prot & PROT_WRITE) == 0)) && ((set_prot(lmp, mpp, 1) == 0) || (aplist_append(textrel, mpp, AL_CNT_TEXTREL) == NULL))) return (0); elf_plt_init(PLTGOT(lmp), (caddr_t)lmp); } /* * Initialize the plt start and end addresses. */ if ((pltbgn = (ulong_t)JMPREL(lmp)) != 0) pltend = pltbgn + (ulong_t)(PLTRELSZ(lmp)); relsiz = (ulong_t)(RELENT(lmp)); basebgn = ADDR(lmp); if (PLTRELSZ(lmp)) plthint = PLTRELSZ(lmp) / relsiz; /* * If we've been called upon to promote an RTLD_LAZY object to an * RTLD_NOW then we're only interested in scaning the .plt table. * An uninitialized .plt is the case where the associated got entry * points back to the plt itself. Determine the range of the real .plt * entries using the _PROCEDURE_LINKAGE_TABLE_ symbol. */ if (plt) { Slookup sl; Sresult sr; relbgn = pltbgn; relend = pltend; if (!relbgn || (relbgn == relend)) return (1); /* * Initialize the symbol lookup, and symbol result, data * structures. */ SLOOKUP_INIT(sl, MSG_ORIG(MSG_SYM_PLT), lmp, lmp, ld_entry_cnt, elf_hash(MSG_ORIG(MSG_SYM_PLT)), 0, 0, 0, LKUP_DEFT); SRESULT_INIT(sr, MSG_ORIG(MSG_SYM_PLT)); if (elf_find_sym(&sl, &sr, &binfo, NULL) == 0) return (1); symdef = sr.sr_sym; _pltbgn = symdef->st_value; if (!(FLAGS(lmp) & FLG_RT_FIXED) && (symdef->st_shndx != SHN_ABS)) _pltbgn += basebgn; _pltend = _pltbgn + (((PLTRELSZ(lmp) / relsiz)) * M_PLT_ENTSIZE) + M_PLT_RESERVSZ; } else { /* * The relocation sections appear to the run-time linker as a * single table. Determine the address of the beginning and end * of this table. There are two different interpretations of * the ABI at this point: * * o The REL table and its associated RELSZ indicate the * concatenation of *all* relocation sections (this is the * model our link-editor constructs). * * o The REL table and its associated RELSZ indicate the * concatenation of all *but* the .plt relocations. These * relocations are specified individually by the JMPREL and * PLTRELSZ entries. * * Determine from our knowledege of the relocation range and * .plt range, the range of the total relocation table. Note * that one other ABI assumption seems to be that the .plt * relocations always follow any other relocations, the * following range checking drops that assumption. */ relbgn = (ulong_t)(REL(lmp)); relend = relbgn + (ulong_t)(RELSZ(lmp)); if (pltbgn) { if (!relbgn || (relbgn > pltbgn)) relbgn = pltbgn; if (!relbgn || (relend < pltend)) relend = pltend; } } if (!relbgn || (relbgn == relend)) { DBG_CALL(Dbg_reloc_run(lmp, 0, plt, DBG_REL_NONE)); return (1); } DBG_CALL(Dbg_reloc_run(lmp, M_REL_SHT_TYPE, plt, DBG_REL_START)); /* * If we're processing a dynamic executable in lazy mode there is no * need to scan the .rel.plt table, however if we're processing a shared * object in lazy mode the .got addresses associated to each .plt must * be relocated to reflect the location of the shared object. */ if (pltbgn && ((MODE(lmp) & RTLD_NOW) == 0) && (FLAGS(lmp) & FLG_RT_FIXED)) noplt = 1; sip = SYMINFO(lmp); /* * Loop through relocations. */ while (relbgn < relend) { mmapobj_result_t *mpp; uint_t sb_flags = 0; rtype = ELF_R_TYPE(((Rela *)relbgn)->r_info, M_MACH); /* * If this is a RELATIVE relocation in a shared object (the * common case), and if we are not debugging, then jump into a * tighter relocation loop (elf_reloc_relative). */ if ((rtype == R_AMD64_RELATIVE) && ((FLAGS(lmp) & FLG_RT_FIXED) == 0) && (DBG_ENABLED == 0)) { if (relacount) { relbgn = elf_reloc_relative_count(relbgn, relacount, relsiz, basebgn, lmp, textrel, 0); relacount = 0; } else { relbgn = elf_reloc_relative(relbgn, relend, relsiz, basebgn, lmp, textrel, 0); } if (relbgn >= relend) break; rtype = ELF_R_TYPE(((Rela *)relbgn)->r_info, M_MACH); } roffset = ((Rela *)relbgn)->r_offset; /* * If this is a shared object, add the base address to offset. */ if (!(FLAGS(lmp) & FLG_RT_FIXED)) { /* * If we're processing lazy bindings, we have to step * through the plt entries and add the base address * to the corresponding got entry. */ if (plthint && (plt == 0) && (rtype == R_AMD64_JUMP_SLOT) && ((MODE(lmp) & RTLD_NOW) == 0)) { relbgn = elf_reloc_relative_count(relbgn, plthint, relsiz, basebgn, lmp, textrel, 1); plthint = 0; continue; } roffset += basebgn; } reladd = (long)(((Rela *)relbgn)->r_addend); rsymndx = ELF_R_SYM(((Rela *)relbgn)->r_info); rel = (Rela *)relbgn; relbgn += relsiz; /* * Optimizations. */ if (rtype == R_AMD64_NONE) continue; if (noplt && ((ulong_t)rel >= pltbgn) && ((ulong_t)rel < pltend)) { relbgn = pltend; continue; } /* * If we're promoting plts, determine if this one has already * been written. */ if (plt && ((*(ulong_t *)roffset < _pltbgn) || (*(ulong_t *)roffset > _pltend))) continue; /* * If this relocation is not against part of the image * mapped into memory we skip it. */ if ((mpp = find_segment((caddr_t)roffset, lmp)) == NULL) { elf_reloc_bad(lmp, (void *)rel, rtype, roffset, rsymndx); continue; } binfo = 0; /* * If a symbol index is specified then get the symbol table * entry, locate the symbol definition, and determine its * address. */ if (rsymndx) { /* * If a Syminfo section is provided, determine if this * symbol is deferred, and if so, skip this relocation. */ if (sip && is_sym_deferred((ulong_t)rel, basebgn, lmp, textrel, sip, rsymndx)) continue; /* * Get the local symbol table entry. */ symref = (Sym *)((ulong_t)SYMTAB(lmp) + (rsymndx * SYMENT(lmp))); /* * If this is a local symbol, just use the base address. * (we should have no local relocations in the * executable). */ if (ELF_ST_BIND(symref->st_info) == STB_LOCAL) { value = basebgn; name = NULL; /* * Special case TLS relocations. */ if (rtype == R_AMD64_DTPMOD64) { /* * Use the TLS modid. */ value = TLSMODID(lmp); } else if ((rtype == R_AMD64_TPOFF64) || (rtype == R_AMD64_TPOFF32)) { if ((value = elf_static_tls(lmp, symref, rel, rtype, 0, roffset, 0)) == 0) { ret = 0; break; } } } else { /* * If the symbol index is equal to the previous * symbol index relocation we processed then * reuse the previous values. (Note that there * have been cases where a relocation exists * against a copy relocation symbol, our ld(1) * should optimize this away, but make sure we * don't use the same symbol information should * this case exist). */ if ((rsymndx == psymndx) && (rtype != R_AMD64_COPY)) { /* LINTED */ if (psymdef == 0) { DBG_CALL(Dbg_bind_weak(lmp, (Addr)roffset, (Addr) (roffset - basebgn), name)); continue; } /* LINTED */ value = pvalue; /* LINTED */ name = pname; /* LINTED */ symdef = psymdef; /* LINTED */ symref = psymref; /* LINTED */ _lmp = plmp; /* LINTED */ binfo = pbinfo; if ((LIST(_lmp)->lm_tflags | AFLAGS(_lmp)) & LML_TFLG_AUD_SYMBIND) { value = audit_symbind(lmp, _lmp, /* LINTED */ symdef, dsymndx, value, &sb_flags); } } else { Slookup sl; Sresult sr; /* * Lookup the symbol definition. * Initialize the symbol lookup, and * symbol result, data structure. */ name = (char *)(STRTAB(lmp) + symref->st_name); SLOOKUP_INIT(sl, name, lmp, 0, ld_entry_cnt, 0, rsymndx, symref, rtype, LKUP_STDRELOC); SRESULT_INIT(sr, name); symdef = NULL; if (lookup_sym(&sl, &sr, &binfo, in_nfavl)) { name = (char *)sr.sr_name; _lmp = sr.sr_dmap; symdef = sr.sr_sym; } /* * If the symbol is not found and the * reference was not to a weak symbol, * report an error. Weak references * may be unresolved. */ /* BEGIN CSTYLED */ if (symdef == 0) { if (sl.sl_bind != STB_WEAK) { if (elf_reloc_error(lmp, name, rel, binfo)) continue; ret = 0; break; } else { psymndx = rsymndx; psymdef = 0; DBG_CALL(Dbg_bind_weak(lmp, (Addr)roffset, (Addr) (roffset - basebgn), name)); continue; } } /* END CSTYLED */ /* * If symbol was found in an object * other than the referencing object * then record the binding. */ if ((lmp != _lmp) && ((FLAGS1(_lmp) & FL1_RT_NOINIFIN) == 0)) { if (aplist_test(&bound, _lmp, AL_CNT_RELBIND) == 0) { ret = 0; break; } } /* * Calculate the location of definition; * symbol value plus base address of * containing shared object. */ if (IS_SIZE(rtype)) value = symdef->st_size; else value = symdef->st_value; if (!(FLAGS(_lmp) & FLG_RT_FIXED) && !(IS_SIZE(rtype)) && (symdef->st_shndx != SHN_ABS) && (ELF_ST_TYPE(symdef->st_info) != STT_TLS)) value += ADDR(_lmp); /* * Retain this symbol index and the * value in case it can be used for the * subsequent relocations. */ if (rtype != R_AMD64_COPY) { psymndx = rsymndx; pvalue = value; pname = name; psymdef = symdef; psymref = symref; plmp = _lmp; pbinfo = binfo; } if ((LIST(_lmp)->lm_tflags | AFLAGS(_lmp)) & LML_TFLG_AUD_SYMBIND) { dsymndx = (((uintptr_t)symdef - (uintptr_t)SYMTAB(_lmp)) / SYMENT(_lmp)); value = audit_symbind(lmp, _lmp, symdef, dsymndx, value, &sb_flags); } } /* * If relocation is PC-relative, subtract * offset address. */ if (IS_PC_RELATIVE(rtype)) value -= roffset; /* * Special case TLS relocations. */ if (rtype == R_AMD64_DTPMOD64) { /* * Relocation value is the TLS modid. */ value = TLSMODID(_lmp); } else if ((rtype == R_AMD64_TPOFF64) || (rtype == R_AMD64_TPOFF32)) { if ((value = elf_static_tls(_lmp, symdef, rel, rtype, name, roffset, value)) == 0) { ret = 0; break; } } } } else { /* * Special cases. */ if (rtype == R_AMD64_DTPMOD64) { /* * TLS relocation value is the TLS modid. */ value = TLSMODID(lmp); } else value = basebgn; name = NULL; } DBG_CALL(Dbg_reloc_in(LIST(lmp), ELF_DBG_RTLD, M_MACH, M_REL_SHT_TYPE, rel, NULL, 0, name)); /* * Make sure the segment is writable. */ if (((mpp->mr_prot & PROT_WRITE) == 0) && ((set_prot(lmp, mpp, 1) == 0) || (aplist_append(textrel, mpp, AL_CNT_TEXTREL) == NULL))) { ret = 0; break; } /* * Call relocation routine to perform required relocation. */ switch (rtype) { case R_AMD64_COPY: if (elf_copy_reloc(name, symref, lmp, (void *)roffset, symdef, _lmp, (const void *)value) == 0) ret = 0; break; case R_AMD64_JUMP_SLOT: if (((LIST(lmp)->lm_tflags | AFLAGS(lmp)) & (LML_TFLG_AUD_PLTENTER | LML_TFLG_AUD_PLTEXIT)) && AUDINFO(lmp)->ai_dynplts) { int fail = 0; int pltndx = (((ulong_t)rel - (uintptr_t)JMPREL(lmp)) / relsiz); int symndx = (((uintptr_t)symdef - (uintptr_t)SYMTAB(_lmp)) / SYMENT(_lmp)); (void) elf_plt_trace_write(roffset, lmp, _lmp, symdef, symndx, pltndx, (caddr_t)value, sb_flags, &fail); if (fail) ret = 0; } else { /* * Write standard PLT entry to jump directly * to newly bound function. */ DBG_CALL(Dbg_reloc_apply_val(LIST(lmp), ELF_DBG_RTLD, (Xword)roffset, (Xword)value)); *(ulong_t *)roffset = value; } break; default: value += reladd; /* * Write the relocation out. */ if (do_reloc_rtld(rtype, (uchar_t *)roffset, (Xword *)&value, name, NAME(lmp), LIST(lmp)) == 0) ret = 0; DBG_CALL(Dbg_reloc_apply_val(LIST(lmp), ELF_DBG_RTLD, (Xword)roffset, (Xword)value)); } if ((ret == 0) && ((LIST(lmp)->lm_flags & LML_FLG_TRC_WARN) == 0)) break; if (binfo) { DBG_CALL(Dbg_bind_global(lmp, (Addr)roffset, (Off)(roffset - basebgn), (Xword)(-1), PLT_T_FULL, _lmp, (Addr)value, symdef->st_value, name, binfo)); } } return (relocate_finish(lmp, bound, ret)); } /* * Initialize the first few got entries so that function calls go to * elf_rtbndr: * * GOT[GOT_XLINKMAP] = the address of the link map * GOT[GOT_XRTLD] = the address of rtbinder */ void elf_plt_init(void *got, caddr_t l) { uint64_t *_got; /* LINTED */ Rt_map *lmp = (Rt_map *)l; _got = (uint64_t *)got + M_GOT_XLINKMAP; *_got = (uint64_t)lmp; _got = (uint64_t *)got + M_GOT_XRTLD; *_got = (uint64_t)elf_rtbndr; } /* * Plt writing interface to allow debugging initialization to be generic. */ Pltbindtype /* ARGSUSED1 */ elf_plt_write(uintptr_t addr, uintptr_t vaddr, void *rptr, uintptr_t symval, Xword pltndx) { Rela *rel = (Rela*)rptr; uintptr_t pltaddr; pltaddr = addr + rel->r_offset; *(ulong_t *)pltaddr = (ulong_t)symval + rel->r_addend; DBG_CALL(pltcntfull++); return (PLT_T_FULL); } /* * Provide a machine specific interface to the conversion routine. By calling * the machine specific version, rather than the generic version, we insure that * the data tables/strings for all known machine versions aren't dragged into * ld.so.1. */ const char * _conv_reloc_type(uint_t rel) { static Conv_inv_buf_t inv_buf; return (conv_reloc_amd64_type(rel, 0, &inv_buf)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1988 AT&T * All Rights Reserved * * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Bootstrap routine for run-time linker. * We get control from exec which has loaded our text and * data into the process' address space and created the process * stack. * * On entry, the process stack looks like this: * * # # <- %rsp * #_______________________# high addresses * # strings # * #_______________________# * # 0 word # * #_______________________# * # Auxiliary # * # entries # * # ... # * # (size varies) # * #_______________________# * # 0 word # * #_______________________# * # Environment # * # pointers # * # ... # * # (one word each) # * #_______________________# * # 0 word # * #_______________________# * # Argument # low addresses * # pointers # * # Argc words # * #_______________________# * # argc # * #_______________________# <- %rbp * * * We must calculate the address at which ld.so was loaded, * find the addr of the dynamic section of ld.so, of argv[0], and of * the process' environment pointers - and pass the thing to _setup * to handle. We then call _rtld - on return we jump to the entry * point for the executable. */ #if defined(lint) extern unsigned long _setup(); extern void atexit_fini(); void main() { (void) _setup(); atexit_fini(); } #else #include .file "boot.s" .text .globl _rt_boot .globl _setup .globl _GLOBAL_OFFSET_TABLE_ .type _rt_boot,@function .align 4 _rt_alias: / in case we were invoked from libc.so jmp .get_got _rt_boot: / save for referencing args movq %rsp,%rbp / make room for a max sized boot vector subq $EB_MAX_SIZE64,%rsp / use esi as a pointer to &eb[0] movq %rsp,%rsi / set up tag for argv movq $EB_ARGV,0(%rsi) / get address of argv leaq 8(%rbp),%rax / put after tag movq %rax,8(%rsi) / set up tag for envp movq $EB_ENVP,16(%rsi) / get # of args movq (%rbp),%rax / one for the zero & one for argc addq $2,%rax / now points past args & @ envp leaq (%rbp,%rax,8),%rdi / set envp movq %rdi,24(%rsi) / next .L0: addq $8,%rdi / search for 0 at end of env cmpq $0,-8(%rdi) jne .L0 / set up tag for auxv movq $EB_AUXV,32(%rsi) / point to auxv movq %rdi,40(%rsi) / set up NULL tag movq $EB_NULL,48(%rsi) / arg1 - address of &eb[0] movq %rsi, %rdi .get_got: leaq _GLOBAL_OFFSET_TABLE_(%rip), %rbx / addq $_GLOBAL_OFFSET_TABLE_, %rbx movq %rbx,%r9 // addq $[.L2-.L1], %rbx movq %rbx,%r10 movq (%rbx),%rsi / _setup(&eb[0], _DYNAMIC) call _setup@PLT / release stack frame movq %rbp,%rsp movq atexit_fini@GOTPCREL(%rip), %rdx / transfer control to the executable jmp *%rax .size _rt_boot,.-_rt_boot #endif /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2018 Joyent, Inc. All rights reserved. */ /* * Welcome to the magic behind the PLT (procedure linkage table). When rtld * fills out the PLT entries, it will refer initially to the functions in this * file. As such our goal is simple: * * The lie of the function call must be preserved at all costs. * * This means that we need to prepare the system for an arbitrary series of * instructions to be called. For example, as a side effect of resolving a * symbol we may need to open a shared object which will cause any _init * functions to be called. Those functions can use any and all of the ABI state * that they desire (for example, the FPU registers). Therefore we must save and * restore all the ABI mandated registers here. * * For the full information about what we need to save and restore and why, * please see the System V amd64 PS ABI '3.2.3 Parameter Passing'. For general * purpose registers, we need to take care of the following: * * %rax - Used for information about the number of vector arguments * %rdi - arg0 * %rsi - arg1 * %rdx - arg2 * %rcx - arg3 * %r8 - arg4 * %r9 - arg5 * %r10 - static chain pointer * * Unfortunately, the world of the FPU is more complicated. * * The ABI mandates that we must save %xmm0-%xmm7. On newer Intel processors, * %xmm0-%xmm7 shadow %ymm0-%ymm7 and %zmm0-%zmm7. Historically, when saving the * FPU, we only saved and restored these eight registers. Unfortunately, this * process itself ended up having side effects. Because the registers shadow one * another, if we saved a full %zmm register when only a %xmm register was * valid, we would end up causing the processor to think that the full %zmm * register was valid. Once it believed that this was the case, it would then * degrade performance of code that only used the %xmm registers. * * One way to tackle this problem would have been to use xgetbv with ecx=1 to * get information about what was actually in use and only save and restore * that. You can imagine that this logic roughly ends up as something like: * * if (zmm_inuse) * save_zmm() * if (ymm_inuse) * save_ymm() * save_xmm() * * However, this logic leaves us at the mercy of the branch predictor. This * means that all of our efforts can end up still causing the CPU to execute * things to make it think that some of these other FPU registers are in use and * thus defeat the optimizations that it has. * * To deal with this problem, Intel has suggested using the xsave family of * instructions. The kernel provides information about the size required for the * floating point registers as well as which of several methods we need to * employ through the aux vector. This gets us out of trying to look at the * hardware capabilities and make decisions every time. As part of the * amd64-specific portion of rtld, it will process those values and determine * the functions on an as-needed basis. * * There are two different functions that we export. The first is elf_rtbndr(). * This is basically the glue that gets us into the PLT and to perform * relocations. elf_rtbndr() determines the address of the function that we must * call and arranges its stack such that when we return from elf_rtbndr() we * will instead jump to the actual relocated function which will return to the * original caller. Because of this, we must preserve all of the registers that * are used for arguments and restore them before returning. * * The second function we export is elf_plt_trace(). This is used to add support * for audit libraries among other things. elf_plt_trace() may or may not call * the underlying function as a side effect or merely set up its return to it. * This changes how we handle %rax. If we call the function ourself, then we end * up making sure that %rax is the return value versus the initial value. In * addition, because we get %r11 from the surrounding PLT code, we opt to * preserve it in case some of the relocation logic ever ends up calling back * into us again. */ #if defined(lint) #include #include <_rtld.h> #include <_audit.h> #include <_elf.h> #include #include #else #include #include <_audit.h> #include #include #include /* * This macro is used to zero the xsave header. The contents of scratchreg will * be destroyed. locreg should contain the starting address of the xsave header. */ #define XSAVE_HEADER_ZERO(scratch, loc) \ xorq scratch, scratch; \ movq scratch, 0x200(loc); \ movq scratch, 0x208(loc); \ movq scratch, 0x210(loc); \ movq scratch, 0x218(loc); \ movq scratch, 0x220(loc); \ movq scratch, 0x228(loc); \ movq scratch, 0x230(loc); \ movq scratch, 0x238(loc) .file "boot_elf.s" .text /* * This section of the code contains glue functions that are used to take care * of saving and restoring the FPU. We deal with this in a few different ways * based on the hardware support and what exists. Historically we've only saved * and restored the first 8 floating point registers rather than the entire FPU. * That implementation still exists here and is kept around mostly as an * insurance policy. */ ENTRY(_elf_rtbndr_fp_save_orig) movq org_scapset@GOTPCREL(%rip),%r11 movq (%r11),%r11 /* Syscapset_t pointer */ movl 8(%r11),%edx /* sc_hw_2 */ testl $AV_386_2_AVX512F,%edx jne .save_zmm movl (%r11),%edx /* sc_hw_1 */ testl $AV_386_AVX,%edx jne .save_ymm movdqa %xmm0, (%rdi) movdqa %xmm1, 64(%rdi) movdqa %xmm2, 128(%rdi) movdqa %xmm3, 192(%rdi) movdqa %xmm4, 256(%rdi) movdqa %xmm5, 320(%rdi) movdqa %xmm6, 384(%rdi) movdqa %xmm7, 448(%rdi) jmp .save_finish .save_ymm: vmovdqa %ymm0, (%rdi) vmovdqa %ymm1, 64(%rdi) vmovdqa %ymm2, 128(%rdi) vmovdqa %ymm3, 192(%rdi) vmovdqa %ymm4, 256(%rdi) vmovdqa %ymm5, 320(%rdi) vmovdqa %ymm6, 384(%rdi) vmovdqa %ymm7, 448(%rdi) jmp .save_finish .save_zmm: vmovdqa64 %zmm0, (%rdi) vmovdqa64 %zmm1, 64(%rdi) vmovdqa64 %zmm2, 128(%rdi) vmovdqa64 %zmm3, 192(%rdi) vmovdqa64 %zmm4, 256(%rdi) vmovdqa64 %zmm5, 320(%rdi) vmovdqa64 %zmm6, 384(%rdi) vmovdqa64 %zmm7, 448(%rdi) .save_finish: ret SET_SIZE(_elf_rtbndr_fp_save_orig) ENTRY(_elf_rtbndr_fp_restore_orig) movq org_scapset@GOTPCREL(%rip),%r11 movq (%r11),%r11 /* Syscapset_t pointer */ movl 8(%r11),%edx /* sc_hw_2 */ testl $AV_386_2_AVX512F,%edx jne .restore_zmm movl (%r11),%edx /* sc_hw_1 */ testl $AV_386_AVX,%edx jne .restore_ymm movdqa (%rdi), %xmm0 movdqa 64(%rdi), %xmm1 movdqa 128(%rdi), %xmm2 movdqa 192(%rdi), %xmm3 movdqa 256(%rdi), %xmm4 movdqa 320(%rdi), %xmm5 movdqa 384(%rdi), %xmm6 movdqa 448(%rdi), %xmm7 jmp .restore_finish .restore_ymm: vmovdqa (%rdi), %ymm0 vmovdqa 64(%rdi), %ymm1 vmovdqa 128(%rdi), %ymm2 vmovdqa 192(%rdi), %ymm3 vmovdqa 256(%rdi), %ymm4 vmovdqa 320(%rdi), %ymm5 vmovdqa 384(%rdi), %ymm6 vmovdqa 448(%rdi), %ymm7 jmp .restore_finish .restore_zmm: vmovdqa64 (%rdi), %zmm0 vmovdqa64 64(%rdi), %zmm1 vmovdqa64 128(%rdi), %zmm2 vmovdqa64 192(%rdi), %zmm3 vmovdqa64 256(%rdi), %zmm4 vmovdqa64 320(%rdi), %zmm5 vmovdqa64 384(%rdi), %zmm6 vmovdqa64 448(%rdi), %zmm7 .restore_finish: ret SET_SIZE(_elf_rtbndr_fp_restore_orig) ENTRY(_elf_rtbndr_fp_fxsave) fxsaveq (%rdi) ret SET_SIZE(_elf_rtbndr_fp_fxsave) ENTRY(_elf_rtbndr_fp_fxrestore) fxrstor (%rdi) ret SET_SIZE(_elf_rtbndr_fp_fxrestore) ENTRY(_elf_rtbndr_fp_xsave) XSAVE_HEADER_ZERO(%rdx, %rdi) movq $_CONST(XFEATURE_FP_ALL), %rdx movl %edx, %eax shrq $32, %rdx xsave (%rdi) /* save data */ ret SET_SIZE(_elf_rtbndr_fp_xsave) ENTRY(_elf_rtbndr_fp_xrestore) movq $_CONST(XFEATURE_FP_ALL), %rdx movl %edx, %eax shrq $32, %rdx xrstor (%rdi) /* save data */ ret SET_SIZE(_elf_rtbndr_fp_xrestore) #endif #if defined(lint) /* ARGSUSED0 */ int elf_plt_trace() { return (0); } #else /* * On entry the 'glue code' has already done the following: * * pushq %rbp * movq %rsp, %rbp * subq $0x10, %rsp * leaq trace_fields(%rip), %r11 * movq %r11, -0x8(%rbp) * movq $elf_plt_trace, %r11 * jmp *%r11 * * so - -8(%rbp) contains the dyndata ptr * * 0x0 Addr *reflmp * 0x8 Addr *deflmp * 0x10 Word symndx * 0x14 Word sb_flags * 0x18 Sym symdef.st_name * 0x1c symdef.st_info * 0x1d symdef.st_other * 0x1e symdef.st_shndx * 0x20 symdef.st_value * 0x28 symdef.st_size * * Also note - on entry 16 bytes have already been subtracted * from the %rsp. The first 8 bytes is for the dyn_data_ptr, * the second 8 bytes are to align the stack and are available * for use. */ #define REFLMP_OFF 0x0 #define DEFLMP_OFF 0x8 #define SYMNDX_OFF 0x10 #define SBFLAGS_OFF 0x14 #define SYMDEF_OFF 0x18 #define SYMDEF_VALUE_OFF 0x20 /* * Next, we need to create a bunch of local storage. First, we have to preserve * the standard registers per the amd64 ABI. This means we need to deal with: * %rax - Used for information about the number of vector arguments * %rdi - arg0 * %rsi - arg1 * %rdx - arg2 * %rcx - arg3 * %r8 - arg4 * %r9 - arg5 * %r10 - static chain pointer * %r11 - PLT Interwork register, our caller is using this, so it's not * a temporary for us. * * In addition, we need to save the amd64 ABI floating point arguments. Finally, * we need to deal with our local storage. We need a La_amd64_regs and a * uint64_t for the previous stack size. * * To deal with this and the potentially variable size of the FPU regs, we have * to play a few different games. We refer to all of the standard registers, the * previous stack size, and La_amd64_regs structure off of %rbp. These are all * values that are below %rbp. */ #define SPDYNOFF -8 #define SPDESTOFF -16 #define SPPRVSTKOFF -24 #define SPLAREGOFF -88 #define ORIG_RDI -96 #define ORIG_RSI -104 #define ORIG_RDX -112 #define ORIG_RCX -120 #define ORIG_R8 -128 #define ORIG_R9 -136 #define ORIG_R10 -144 #define ORIG_R11 -152 #define ORIG_RAX -160 #define PLT_SAVE_OFF 168 ENTRY(elf_plt_trace) /* * Save our static registers. After that 64-byte align us and subtract * the appropriate amount for the FPU. The frame pointer has already * been pushed for us by the glue code. */ movq %rdi, ORIG_RDI(%rbp) movq %rsi, ORIG_RSI(%rbp) movq %rdx, ORIG_RDX(%rbp) movq %rcx, ORIG_RCX(%rbp) movq %r8, ORIG_R8(%rbp) movq %r9, ORIG_R9(%rbp) movq %r10, ORIG_R10(%rbp) movq %r11, ORIG_R11(%rbp) movq %rax, ORIG_RAX(%rbp) subq $PLT_SAVE_OFF, %rsp movq _plt_save_size@GOTPCREL(%rip),%r9 movq _plt_fp_save@GOTPCREL(%rip),%r10 subq (%r9), %rsp andq $-64, %rsp movq %rsp, %rdi call *(%r10) /* * Now that we've saved all of our registers, figure out what we need to * do next. */ movq SPDYNOFF(%rbp), %rax / %rax = dyndata testb $LA_SYMB_NOPLTENTER, SBFLAGS_OFF(%rax) / je .start_pltenter movq SYMDEF_VALUE_OFF(%rax), %rdi movq %rdi, SPDESTOFF(%rbp) / save destination address jmp .end_pltenter .start_pltenter: /* * save all registers into La_amd64_regs */ leaq SPLAREGOFF(%rbp), %rsi / %rsi = &La_amd64_regs leaq 8(%rbp), %rdi movq %rdi, 0(%rsi) / la_rsp movq 0(%rbp), %rdi movq %rdi, 8(%rsi) / la_rbp movq ORIG_RDI(%rbp), %rdi movq %rdi, 16(%rsi) / la_rdi movq ORIG_RSI(%rbp), %rdi movq %rdi, 24(%rsi) / la_rsi movq ORIG_RDX(%rbp), %rdi movq %rdi, 32(%rsi) / la_rdx movq ORIG_RCX(%rbp), %rdi movq %rdi, 40(%rsi) / la_rcx movq ORIG_R8(%rbp), %rdi movq %rdi, 48(%rsi) / la_r8 movq ORIG_R9(%rbp), %rdi movq %rdi, 56(%rsi) / la_r9 /* * prepare for call to la_pltenter */ movq SPDYNOFF(%rbp), %r11 / %r11 = &dyndata leaq SBFLAGS_OFF(%r11), %r9 / arg6 (&sb_flags) leaq SPLAREGOFF(%rbp), %r8 / arg5 (&La_amd64_regs) movl SYMNDX_OFF(%r11), %ecx / arg4 (symndx) leaq SYMDEF_OFF(%r11), %rdx / arg3 (&Sym) movq DEFLMP_OFF(%r11), %rsi / arg2 (dlmp) movq REFLMP_OFF(%r11), %rdi / arg1 (rlmp) call audit_pltenter@PLT movq %rax, SPDESTOFF(%rbp) / save calling address .end_pltenter: /* * If *no* la_pltexit() routines exist * we do not need to keep the stack frame * before we call the actual routine. Instead we * jump to it and remove our stack from the stack * at the same time. */ movl audit_flags(%rip), %eax andl $AF_PLTEXIT, %eax / value of audit.h:AF_PLTEXIT cmpl $0, %eax je .bypass_pltexit /* * Has the *nopltexit* flag been set for this entry point */ movq SPDYNOFF(%rbp), %r11 / %r11 = &dyndata testb $LA_SYMB_NOPLTEXIT, SBFLAGS_OFF(%r11) je .start_pltexit .bypass_pltexit: /* * No PLTEXIT processing required. */ movq 0(%rbp), %r11 movq %r11, -8(%rbp) / move prev %rbp movq SPDESTOFF(%rbp), %r11 / r11 == calling destination movq %r11, 0(%rbp) / store destination at top /* Restore FPU */ movq _plt_fp_restore@GOTPCREL(%rip),%r10 movq %rsp, %rdi call *(%r10) movq ORIG_RDI(%rbp), %rdi movq ORIG_RSI(%rbp), %rsi movq ORIG_RDX(%rbp), %rdx movq ORIG_RCX(%rbp), %rcx movq ORIG_R8(%rbp), %r8 movq ORIG_R9(%rbp), %r9 movq ORIG_R10(%rbp), %r10 movq ORIG_R11(%rbp), %r11 movq ORIG_RAX(%rbp), %rax subq $8, %rbp / adjust %rbp for 'ret' movq %rbp, %rsp / /* * At this point, after a little doctoring, we should * have the following on the stack: * * 16(%rsp): ret addr * 8(%rsp): dest_addr * 0(%rsp): Previous %rbp * * So - we pop the previous %rbp, and then * ret to our final destination. */ popq %rbp / ret / jmp to final destination / and clean up stack :) .start_pltexit: /* * In order to call the destination procedure and then return * to audit_pltexit() for post analysis we must first grow * our stack frame and then duplicate the original callers * stack state. This duplicates all of the arguements * that were to be passed to the destination procedure. */ movq %rbp, %rdi / addq $16, %rdi / %rdi = src movq (%rbp), %rdx / subq %rdi, %rdx / %rdx == prev frame sz /* * If audit_argcnt > 0 then we limit the number of * arguements that will be duplicated to audit_argcnt. * * If (prev_stack_size > (audit_argcnt * 8)) * prev_stack_size = audit_argcnt * 8; */ movl audit_argcnt(%rip),%eax / %eax = audit_argcnt cmpl $0, %eax jle .grow_stack leaq (,%rax,8), %rax / %eax = %eax * 4 cmpq %rax,%rdx jle .grow_stack movq %rax, %rdx /* * Grow the stack and duplicate the arguements of the * original caller. */ .grow_stack: movq %rsp, %r11 subq %rdx, %rsp / grow the stack movq %rdx, SPPRVSTKOFF(%rbp) / -88(%rbp) == prev frame sz movq %rsp, %rcx / %rcx = dest addq %rcx, %rdx / %rdx == tail of dest .while_base: cmpq %rdx, %rcx / while (base+size >= src++) { jge .end_while / movq (%rdi), %rsi movq %rsi,(%rcx) / *dest = *src addq $8, %rdi / src++ addq $8, %rcx / dest++ jmp .while_base / } /* * The above stack is now an exact duplicate of * the stack of the original calling procedure. */ .end_while: / / Restore registers using %r11 which contains our old %rsp value / before growing the stack. / movq _plt_fp_restore@GOTPCREL(%rip),%r10 movq %r11, %rdi call *(%r10) .trace_r2_finish: movq ORIG_RDI(%rbp), %rdi movq ORIG_RSI(%rbp), %rsi movq ORIG_RDX(%rbp), %rdx movq ORIG_RCX(%rbp), %rcx movq ORIG_R8(%rbp), %r8 movq ORIG_R9(%rbp), %r9 movq ORIG_R10(%rbp), %r10 movq ORIG_RAX(%rbp), %rax movq ORIG_R11(%rbp), %r11 /* * Call to desitnation function - we'll return here * for pltexit monitoring. */ call *SPDESTOFF(%rbp) addq SPPRVSTKOFF(%rbp), %rsp / cleanup dupped stack / / prepare for call to audit_pltenter() / movq SPDYNOFF(%rbp), %r11 / %r11 = &dyndata movq SYMNDX_OFF(%r11), %r8 / arg5 (symndx) leaq SYMDEF_OFF(%r11), %rcx / arg4 (&Sym) movq DEFLMP_OFF(%r11), %rdx / arg3 (dlmp) movq REFLMP_OFF(%r11), %rsi / arg2 (rlmp) movq %rax, %rdi / arg1 (returnval) call audit_pltexit@PLT /* * Clean up after ourselves and return to the * original calling procedure. Make sure to restore * registers. */ movq _plt_fp_restore@GOTPCREL(%rip),%r10 movq %rsp, %rdi movq %rax, SPPRVSTKOFF(%rbp) call *(%r10) movq ORIG_RDI(%rbp), %rdi movq ORIG_RSI(%rbp), %rsi movq ORIG_RDX(%rbp), %rdx movq ORIG_RCX(%rbp), %rcx movq ORIG_R8(%rbp), %r8 movq ORIG_R9(%rbp), %r9 movq ORIG_R10(%rbp), %r10 movq ORIG_R11(%rbp), %r11 movq SPPRVSTKOFF(%rbp), %rax movq %rbp, %rsp / popq %rbp / ret / return to caller SET_SIZE(elf_plt_trace) #endif /* * We got here because a call to a function resolved to a procedure * linkage table entry. That entry did a JMPL to the first PLT entry, which * in turn did a call to elf_rtbndr. * * the code sequence that got us here was: * * .PLT0: * pushq GOT+8(%rip) #GOT[1] * jmp *GOT+16(%rip) #GOT[2] * nop * nop * nop * nop * ... * PLT entry for foo: * jmp *name1@GOTPCREL(%rip) * pushl $rel.plt.foo * jmp PLT0 * * At entry, the stack looks like this: * * return address 16(%rsp) * $rel.plt.foo (plt index) 8(%rsp) * lmp 0(%rsp) * */ #if defined(lint) extern unsigned long elf_bndr(Rt_map *, unsigned long, caddr_t); void elf_rtbndr(Rt_map * lmp, unsigned long reloc, caddr_t pc) { (void) elf_bndr(lmp, reloc, pc); } #else /* * The PLT code that landed us here placed 2 arguments on the stack as * arguments to elf_rtbndr. * Additionally the pc of caller is below these 2 args. * Our stack will look like this after we establish a stack frame with * push %rbp; movq %rsp, %rbp sequence: * * 8(%rbp) arg1 - *lmp * 16(%rbp), %rsi arg2 - reloc index * 24(%rbp), %rdx arg3 - pc of caller */ #define LBPLMPOFF 8 /* arg1 - *lmp */ #define LBPRELOCOFF 16 /* arg2 - reloc index */ #define LBRPCOFF 24 /* arg3 - pc of caller */ /* * With the above in place, we must now proceed to preserve all temporary * registers that are also used for passing arguments. Specifically this * means: * * %rax - Used for information about the number of vector arguments * %rdi - arg0 * %rsi - arg1 * %rdx - arg2 * %rcx - arg3 * %r8 - arg4 * %r9 - arg5 * %r10 - static chain pointer * * While we don't have to preserve %r11, we do have to preserve the FPU * registers. The FPU logic is delegated to a specific function that we'll call. * However, it requires that its stack is 64-byte aligned. We defer the * alignment to that point. This will also take care of the fact that a caller * may not call us with a correctly aligned stack pointer per the amd64 ABI. */ .extern _plt_save_size .extern _plt_fp_save .extern plt_fp_restore .weak _elf_rtbndr _elf_rtbndr = elf_rtbndr ENTRY(elf_rtbndr) pushq %rbp /* Establish stack frame */ movq %rsp, %rbp /* * Save basic regs. */ pushq %rax pushq %rdi pushq %rsi pushq %rdx pushq %rcx pushq %r8 pushq %r9 pushq %r10 pushq %r12 /* * Save the amount of space we need for the FPU registers and call that * function. Save %rsp before we manipulate it to make restore easier. */ movq %rsp, %r12 movq _plt_save_size@GOTPCREL(%rip),%r9 movq _plt_fp_save@GOTPCREL(%rip),%r10 subq (%r9), %rsp andq $-64, %rsp movq %rsp, %rdi call *(%r10) /* * Perform actual PLT logic. Note that the plt related arguments are * located at an offset relative to %rbp. */ movq LBPLMPOFF(%rbp), %rdi /* arg1 - *lmp */ movq LBPRELOCOFF(%rbp), %rsi /* arg2 - reloc index */ movq LBRPCOFF(%rbp), %rdx /* arg3 - pc of caller */ call elf_bndr@PLT /* call elf_rtbndr(lmp, relndx, pc) */ movq %rax, LBPRELOCOFF(%rbp) /* store final destination */ /* Restore FPU */ movq _plt_fp_restore@GOTPCREL(%rip),%r10 movq %rsp, %rdi call *(%r10) movq %r12, %rsp popq %r12 popq %r10 popq %r9 popq %r8 popq %rcx popq %rdx popq %rsi popq %rdi popq %rax movq %rbp, %rsp /* Restore our stack frame */ popq %rbp addq $8, %rsp /* pop 1st plt-pushed args */ /* the second arguement is used */ /* for the 'return' address to our */ /* final destination */ ret /* invoke resolved function */ SET_SIZE(elf_rtbndr) #endif /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Return the pc of the calling routine. */ #if defined(lint) #include caddr_t caller() { return (0); } #else #include .file "caller.s" ENTRY(caller) movq 8(%rbp),%rax ret SET_SIZE(caller) #endif /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include "_rtld.h" #include "_elf.h" #include "_inline_gen.h" #include "msg.h" static Dl_amd64_unwindinfo * getunwind_core(Lm_list *lml, void *pc, Dl_amd64_unwindinfo *unwindinfo) { Rt_map *lmp; /* * Validate the version information. */ if (unwindinfo == NULL) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLVAL)); return (0); } if ((unwindinfo->dlui_version < DLUI_VERS_1) || (unwindinfo->dlui_version > DLUI_VERS_CURRENT)) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_UNW_BADVERS), unwindinfo->dlui_version, DLUI_VERS_CURRENT); return (0); } /* * Clean out the structure. */ unwindinfo->dlui_flags = 0; unwindinfo->dlui_objname = 0; unwindinfo->dlui_unwindstart = 0; unwindinfo->dlui_unwindend = 0; unwindinfo->dlui_segstart = 0; unwindinfo->dlui_segend = 0; /* * Identify the link-map associated with the exception "pc". Note, * the "pc" might not correspond to a link-map (as can happen with a * "pc" fabricated by a debugger such as dbx). In this case, the * unwind data buffer will be filled with flags set to indicate an * unknown caller. */ lmp = _caller(pc, CL_NONE); if (lmp) { mmapobj_result_t *mpp; /* * Determine the associated segment. */ if ((mpp = find_segment(pc, lmp)) == NULL) return (0); unwindinfo->dlui_objname = (char *)PATHNAME(lmp); unwindinfo->dlui_segstart = mpp->mr_addr; unwindinfo->dlui_segend = mpp->mr_addr + mpp->mr_msize; if (PTUNWIND(lmp) && (mpp->mr_addr)) { uintptr_t base; if (FLAGS(lmp) & FLG_RT_FIXED) base = 0; else base = ADDR(lmp); unwindinfo->dlui_unwindstart = (void *)(PTUNWIND(lmp)->p_vaddr + base); unwindinfo->dlui_unwindend = (void *)(PTUNWIND(lmp)->p_vaddr + PTUNWIND(lmp)->p_memsz + base); } else if (mpp->mr_addr) unwindinfo->dlui_flags |= DLUI_FLG_NOUNWIND; else unwindinfo->dlui_flags |= DLUI_FLG_NOUNWIND | DLUI_FLG_NOOBJ; } else { /* * No object found. */ unwindinfo->dlui_flags = DLUI_FLG_NOOBJ | DLUI_FLG_NOUNWIND; } return (unwindinfo); } #pragma weak _dlamd64getunwind = dlamd64getunwind Dl_amd64_unwindinfo * dlamd64getunwind(void *pc, Dl_amd64_unwindinfo *unwindinfo) { Rt_map *lmp; Lm_list *lml; int entry = enter(0); lmp = _caller(caller(), CL_EXECDEF); lml = LIST(lmp); unwindinfo = getunwind_core(lml, pc, unwindinfo); if (entry) leave(lml, 0); return (unwindinfo); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef __AUDIT_DOT_H #define __AUDIT_DOT_H #ifndef _ASM #include #include #ifdef __cplusplus extern "C" { #endif /* * Define all auditing structures. * * A shared object may be a client of one or more audit libraries. Calls to an * auditor are accompanied with cookies that identify an object being audited. * These cookies are initialized to the link-map pointers of the object being * audited, however the auditor is free to re-allocate these cookies, and thus * associate their own data with each object being audited. * * With auditing version LAV_VERSION5, local auditors can provide la_preinit() * and la_activity() routines, each of which should be passed a cookie that * represents the link-map of the head of the associated link-map list. These * cookies are maintained on the associated link-map list, using the lm_cookies * alist. These cookies are created by _audit_add_head(), and dynamically * retrieved by the _audit_preinit() and _audit_activity() routines. * * Having these cookies kept on the link-map list decouples these cookies from * the Audit_client structure of the object being locally audited. In addition, * this model ensures that multiple objects, undergoing local auditing from the * same auditor, receive the same head link-map cookie. */ typedef struct { Rt_map *ac_lmp; /* audit library identifier */ uintptr_t ac_cookie; /* cookie assigned to audit library */ Word ac_flags; /* and its associated flags */ } Audit_client; #define FLG_AC_BINDTO 0x00001 #define FLG_AC_BINDFROM 0x00002 /* * Each shared object being audited may provide a list of client structures * and dynamic plts (one per auditor). */ struct audit_info { uint_t ai_cnt; /* no. of clients */ Audit_client *ai_clients; /* array of client structures */ void *ai_dynplts; /* array of dynamic plts */ }; /* * Define an Audit Descriptor - each audit object is added to this descriptor * as an Audit Interface. There is one global audit descriptor - auditors, * and a specific object my require its own - AUDITORS(lmp). */ struct audit_desc { char *ad_name; /* originating audit names */ APlist *ad_list; /* audit objs Audit Interface list */ uint_t ad_cnt; /* no. of audit objs in this desc. */ uint_t ad_flags; /* audit capabilities found. See */ /* LML_TFLG_AUD_* flags */ }; /* * Define an Audit List descriptor for each audit object. */ struct audit_list { const char *al_libname; /* object name for diagnostics */ Rt_map *al_lmp; /* object link-map */ Grp_hdl *al_ghp; /* object handle */ uint_t al_flags; /* audit capabilities found */ uint_t (*al_version)(uint_t); void (*al_preinit)(uintptr_t *); char *(*al_objsearch)(const char *, uintptr_t *, uint_t); uint_t (*al_objopen)(Link_map *, Lmid_t, uintptr_t *); int (*al_objfilter)(uintptr_t *, const char *, uintptr_t *, uint_t); uint_t (*al_objclose)(uintptr_t *); void (*al_activity)(uintptr_t *, uint_t); #if defined(_ELF64) uintptr_t (*al_pltenter)(Sym *, uint_t, uintptr_t *, uintptr_t *, void *, uint_t *, const char *); uintptr_t (*al_pltexit)(Sym *, uint_t, uintptr_t *, uintptr_t *, uintptr_t, const char *); uintptr_t (*al_symbind)(Sym *, uint_t, uintptr_t *, uintptr_t *, uint_t *, const char *); #else uintptr_t (*al_pltenter)(Sym *, uint_t, uintptr_t *, uintptr_t *, void *, uint_t *); uintptr_t (*al_pltexit)(Sym *, uint_t, uintptr_t *, uintptr_t *, uintptr_t); uintptr_t (*al_symbind)(Sym *, uint_t, uintptr_t *, uintptr_t *, uint_t *); #endif /* _ELF64 */ uint_t al_vernum; /* object version */ }; /* * Link-Edit audit functions */ extern int audit_setup(Rt_map *, Audit_desc *, uint_t, int *); extern void audit_desc_cleanup(Rt_map *); extern void audit_info_cleanup(Rt_map *); extern int audit_objopen(Rt_map *, Rt_map *); extern int audit_objfilter(Rt_map *, const char *, Rt_map *, uint_t flags); extern void audit_activity(Rt_map *, uint_t); extern void audit_preinit(Rt_map *); extern char *audit_objsearch(Rt_map *, const char *, uint_t); extern void audit_objclose(Rt_map *, Rt_map *); extern void _audit_objclose(APlist *, Rt_map *); extern Addr audit_symbind(Rt_map *, Rt_map *, Sym *, uint_t, Addr value, uint_t *); extern Addr audit_pltenter(Rt_map *, Rt_map *, Sym *, uint_t, void *, uint_t *); extern Addr audit_pltexit(uintptr_t, Rt_map *, Rt_map *, Sym *, uint_t); extern uint_t audit_flags; #endif /* _ASM */ /* * Values for audit_flags. Intended to be the same as the LML equivalents * but kept in a separate variable to simplify boot_elf.s coding. */ #define AF_PLTENTER 0x01 /* same as LML_AUD_PLTENTER */ #define AF_PLTEXIT 0x02 /* Same as LML_AUD_PLTEXIT */ #ifdef __cplusplus } #endif #endif /* __AUDIT_DOT_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1988 AT&T * All Rights Reserved * * Copyright (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef __ELF_DOT_H #define __ELF_DOT_H #include #include #include #include #include <_rtld.h> #ifdef __cplusplus extern "C" { #endif /* * Common extern functions for ELF file class. */ extern int elf_config(Rt_map *); extern Rtc_obj *elf_config_ent(const char *, Word, int, const char **); extern void elf_config_flt(Lm_list *, const char *, const char *, Alist **, Aliste); #if defined(__i386) extern int elf_copy_gen(Rt_map *); #endif extern int elf_copy_reloc(char *, Sym *, Rt_map *, void *, Sym *, Rt_map *, const void *); extern int elf_find_sym(Slookup *, Sresult *, uint_t *, int *); extern int elf_lazy_find_sym(Slookup *, Sresult *, uint_t *, int *); extern Rt_map *elf_lazy_load(Rt_map *, Slookup *, uint_t, const char *, uint_t, Grp_hdl **, int *); extern int elf_lookup_filtee(Slookup *, Sresult *, uint_t *, uint_t, int *); extern int elf_mach_flags_check(Rej_desc *, Ehdr *); extern Rt_map *elf_new_lmp(Lm_list *, Aliste, Fdesc *, Addr, size_t, void *, Rt_map *, int *); extern Rt_map *elf_obj_file(Lm_list *, Aliste, Rt_map *, const char *, mmapobj_result_t *, mmapobj_result_t *, uint_t); extern Rt_map *elf_obj_fini(Lm_list *, Rt_map *, Rt_map *, int *); extern void elf_plt_init(void *, caddr_t); #if defined(__sparcv9) extern void elf_plt2_init(uint_t *, Rt_map *); #endif extern int elf_reloc(Rt_map *, uint_t, int *, APlist **); extern void elf_reloc_bad(Rt_map *, void *, uchar_t, ulong_t, ulong_t); extern int elf_reloc_error(Rt_map *, const char *, void *, uint_t); extern int elf_rtld_load(); extern long elf_static_tls(Rt_map *, Sym *, void *, uchar_t, char *, ulong_t, long); extern Fct *elf_verify(caddr_t, size_t, Fdesc *, const char *, Rej_desc *); extern int elf_verify_vers(const char *, Rt_map *, Rt_map *); /* * Padinfo * * Used to track the which PLTpadd entries have been used and * to where they are bound. * * NOTE: these are only currently used for SparcV9 */ typedef struct pltpadinfo { Addr pp_addr; void *pp_plt; } Pltpadinfo; /* * Private data for an ELF file class. */ typedef struct _rt_elf_private { void *e_symtab; /* symbol table */ void *e_sunwsymtab; /* symtab augmented with local fcns */ uint_t *e_hash; /* hash table */ char *e_strtab; /* string table */ void *e_reloc; /* relocation table */ uint_t *e_pltgot; /* addrs for procedure linkage table */ void *e_jmprel; /* plt relocations */ ulong_t e_sunwsortent; /* size of sunw[sym|tls]sort entry */ uint_t *e_sunwsymsort; /* sunwsymtab indices sorted by addr */ ulong_t e_sunwsymsortsz; /* size of sunwsymtab */ ulong_t e_sunwsymsz; /* size of e_sunwsymtab */ ulong_t e_pltrelsize; /* size of PLT relocation entries */ ulong_t e_relsz; /* size of relocs */ ulong_t e_relent; /* size of base reloc entry */ ulong_t e_movesz; /* size of movetabs */ ulong_t e_moveent; /* size of base movetab entry */ ulong_t e_tlsstatoff; /* TLS offset into static block */ void *e_movetab; /* movetable address */ Phdr *e_pttls; /* PT_TLS */ Phdr *e_ptunwind; /* PT_SUNW_UNWIND (amd64 specific) */ ulong_t e_syment; /* size of symtab entry */ Verneed *e_verneed; /* versions needed by this image and */ int e_verneednum; /* their associated count */ Verdef *e_verdef; /* versions defined by this image and */ int e_verdefnum; /* their associated count */ Versym *e_versym; /* Per-symbol versions */ ulong_t e_syminent; /* syminfo entry size */ void *e_pltpad; /* PLTpad table */ void *e_pltpadend; /* end of PLTpad table */ Syscapset e_capset; /* capabilities set */ Capinfo *e_capinfo; /* symbol capabilities information */ uint_t e_capchainent; /* size of capabilities chain entry */ uint_t e_capchainsz; /* size of capabilities chain data */ } Rt_elfp; /* * Macros for getting to linker ELF private data. */ #define ELFPRV(X) ((X)->rt_priv) #define SYMTAB(X) (((Rt_elfp *)(X)->rt_priv)->e_symtab) #define SUNWSYMTAB(X) (((Rt_elfp *)(X)->rt_priv)->e_sunwsymtab) #define HASH(X) (((Rt_elfp *)(X)->rt_priv)->e_hash) #define STRTAB(X) (((Rt_elfp *)(X)->rt_priv)->e_strtab) #define REL(X) (((Rt_elfp *)(X)->rt_priv)->e_reloc) #define PLTGOT(X) (((Rt_elfp *)(X)->rt_priv)->e_pltgot) #define MOVESZ(X) (((Rt_elfp *)(X)->rt_priv)->e_movesz) #define MOVEENT(X) (((Rt_elfp *)(X)->rt_priv)->e_moveent) #define MOVETAB(X) (((Rt_elfp *)(X)->rt_priv)->e_movetab) #define JMPREL(X) (((Rt_elfp *)(X)->rt_priv)->e_jmprel) #define SUNWSYMSZ(X) (((Rt_elfp *)(X)->rt_priv)->e_sunwsymsz) #define PTTLS(X) (((Rt_elfp *)(X)->rt_priv)->e_pttls) #define PTUNWIND(X) (((Rt_elfp *)(X)->rt_priv)->e_ptunwind) #define TLSSTATOFF(X) (((Rt_elfp *)(X)->rt_priv)->e_tlsstatoff) #define PLTRELSZ(X) (((Rt_elfp *)(X)->rt_priv)->e_pltrelsize) #define RELSZ(X) (((Rt_elfp *)(X)->rt_priv)->e_relsz) #define RELENT(X) (((Rt_elfp *)(X)->rt_priv)->e_relent) #define SYMENT(X) (((Rt_elfp *)(X)->rt_priv)->e_syment) #define VERNEED(X) (((Rt_elfp *)(X)->rt_priv)->e_verneed) #define VERNEEDNUM(X) (((Rt_elfp *)(X)->rt_priv)->e_verneednum) #define VERDEF(X) (((Rt_elfp *)(X)->rt_priv)->e_verdef) #define VERDEFNUM(X) (((Rt_elfp *)(X)->rt_priv)->e_verdefnum) #define VERSYM(X) (((Rt_elfp *)(X)->rt_priv)->e_versym) #define SYMINENT(X) (((Rt_elfp *)(X)->rt_priv)->e_syminent) #define PLTPAD(X) (((Rt_elfp *)(X)->rt_priv)->e_pltpad) #define PLTPADEND(X) (((Rt_elfp *)(X)->rt_priv)->e_pltpadend) #define SUNWSORTENT(X) (((Rt_elfp *)(X)->rt_priv)->e_sunwsortent) #define SUNWSYMSORT(X) (((Rt_elfp *)(X)->rt_priv)->e_sunwsymsort) #define SUNWSYMSORTSZ(X) (((Rt_elfp *)(X)->rt_priv)->e_sunwsymsortsz) #define CAPSET(X) (((Rt_elfp *)(X)->rt_priv)->e_capset) #define CAPINFO(X) (((Rt_elfp *)(X)->rt_priv)->e_capinfo) #define CAPCHAINENT(X) (((Rt_elfp *)(X)->rt_priv)->e_capchainent) #define CAPCHAINSZ(X) (((Rt_elfp *)(X)->rt_priv)->e_capchainsz) /* * Most of the above macros are used from ELF specific routines, however there * are a couple of instances where we need to ensure the file being processed * is ELF before dereferencing the macro. */ #define THIS_IS_ELF(X) (FCT(X) == &elf_fct) #define THIS_IS_NOT_ELF(X) (FCT(X) != &elf_fct) #ifdef __cplusplus } #endif #endif /* __ELF_DOT_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _INLINE_GEN_H #define _INLINE_GEN_H #include #include #include inline static mmapobj_result_t * find_segment(caddr_t roffset, Rt_map *lmp) { mmapobj_result_t *mpp = MMAPS(lmp); uint_t mnum = MMAPCNT(lmp); /* * Scan segments backwards. The heaviest use of this function stems * from relocation processing. And typically, relocations are against * the data segment. By scanning segments in reverse order, the data * segment is processed first. */ for (mpp += (mnum - 1); mnum; mnum--, mpp--) { if ((roffset >= (mpp->mr_addr + mpp->mr_offset)) && (roffset < (mpp->mr_addr + mpp->mr_msize))) return (mpp); } return (NULL); } #endif /* _INLINE_GEN_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _INLINE_RELOC_H #define _INLINE_RELOC_H #include #include #include /* * Generic relative relocation function. */ inline static ulong_t /* LINTED */ /* ARGSUSED4 */ _elf_reloc_relative(ulong_t rbgn, ulong_t base, Rt_map *lmp, APlist **textrel, int add) { mmapobj_result_t *mpp; ulong_t roffset; roffset = ((M_RELOC *)rbgn)->r_offset; roffset += base; /* * If this relocation is against an address that is not associated with * a mapped segment, fall back to the generic relocation loop to * collect the associated error. */ if ((mpp = find_segment((caddr_t)roffset, lmp)) == NULL) return (0); /* * If this relocation is against a segment that does not provide write * access, set the write permission for all non-writable mappings. */ if (((mpp->mr_prot & PROT_WRITE) == 0) && textrel && ((set_prot(lmp, mpp, 1) == 0) || (aplist_append(textrel, mpp, AL_CNT_TEXTREL) == NULL))) return (0); /* * Perform a base address update. This simple operation is required * for updating .plt relocations in preparation for lazy binding. */ #if defined(__x86) if (add) { *((ulong_t *)roffset) += base; return (1); } #endif /* * Perform the actual relocation. Note, for backward compatibility, * SPARC relocations are added to the offset contents (there was a time * when the offset was used to contain the addend, rather than using * the addend itself). */ #if defined(__sparc) *((ulong_t *)roffset) += base + ((M_RELOC *)rbgn)->r_addend; #elif defined(__amd64) *((ulong_t *)roffset) = base + ((M_RELOC *)rbgn)->r_addend; #else *((ulong_t *)roffset) += base; #endif return (1); } /* * When a generic relocation loop realizes that it's dealing with relative * relocations, but no DT_RELCOUNT .dynamic tag is present, this tighter loop * is entered as an optimization. */ inline static ulong_t /* LINTED */ elf_reloc_relative(ulong_t rbgn, ulong_t rend, ulong_t rsize, ulong_t base, Rt_map *lmp, APlist **textrel, int add) { uchar_t rtype; do { if (_elf_reloc_relative(rbgn, base, lmp, textrel, add) == 0) break; rbgn += rsize; if (rbgn >= rend) break; /* * Make sure the next type is a relative relocation. */ rtype = ELF_R_TYPE(((M_RELOC *)rbgn)->r_info, M_MACH); } while (rtype == M_R_RELATIVE); return (rbgn); } /* * This is the tightest loop for RELATIVE relocations for those objects built * with the DT_RELACOUNT .dynamic entry. */ inline static ulong_t /* LINTED */ elf_reloc_relative_count(ulong_t rbgn, ulong_t rcount, ulong_t rsize, ulong_t base, Rt_map *lmp, APlist **textrel, int add) { for (; rcount; rcount--) { if (_elf_reloc_relative(rbgn, base, lmp, textrel, add) == 0) break; rbgn += rsize; } return (rbgn); } /* * Determine, from a symbols Syminfo information, whether a symbol reference * is deferred. This routine is called from elf_reloc() as part of processing * an objects relocations. */ inline static int /* LINTED */ is_sym_deferred(ulong_t rbgn, ulong_t base, Rt_map *lmp, APlist **textrel, Syminfo *sip, ulong_t sndx) { Syminfo *sipe; /* * ldd(1) by default, sets LD_DEFERRED to force deferred dependency * processing. ldd -D disables LD_DEFERRED, which allows ld.so.1's * default action of skipping deferred dependencies. */ if (rtld_flags & RT_FL_DEFERRED) return (0); /* LINTED */ sipe = (Syminfo *)((char *)sip + (sndx * SYMINENT(lmp))); if (sipe->si_flags & SYMINFO_FLG_DEFERRED) { /* * This .plt relocation should be skipped at this time, as * deferred references are only processed when the associated * function is explicitly called. * * On i386 and amd64 platforms the relocation offset needs * adjusting to add this objects base address. If the object * has already been relocated without RTLD_NOW, then this * update will have already been carried out. However, if this * is an initial RTLD_NOW relocation pass, this relocation * offset needs updating now. */ #if defined(__x86) if ((FLAGS(lmp) & FLG_RT_RELOCED) == 0) (void) _elf_reloc_relative(rbgn, base, lmp, textrel, 1); #endif return (1); } return (0); } #endif /* _INLINE_RELOC_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1988 AT&T * All Rights Reserved * * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright (c) 2012, Joyent, Inc. All rights reserved. */ #ifndef __RTLD_H #define __RTLD_H /* * Common header for run-time linker. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /* * Dependency search rule order. */ #define RPLENV 1 /* replaceable LD_LIBRARY_PATH */ #define PRMENV 2 /* permanent LD_LIBRARY_PATH */ #define RUNPATH 3 /* callers runpath */ #define DEFAULT 4 /* default library path */ typedef struct fdesc Fdesc; typedef struct fct Fct; typedef struct pdesc Pdesc; /* * Data structure for file class specific functions and data. */ struct fct { /* Verify that the object is of this class. */ Fct *(*fct_verify_file)(caddr_t, size_t, Fdesc *, const char *, Rej_desc *); /* Generate a link-map to describe the loaded object. */ Rt_map *(*fct_new_lmp)(Lm_list *, Aliste, Fdesc *, Addr, size_t, void *, Rt_map *, int *); /* Retrieve the entry point of the object. */ Addr (*fct_entry_pt)(void); /* Determine the objects dependencies (needed entries). */ int (*fct_needed)(Lm_list *, Aliste, Rt_map *, int *); /* Look up a symbol for the object. */ int (*fct_lookup_sym)(Slookup *, Sresult *, uint_t *, int *); /* Relocate the object. */ int (*fct_reloc)(Rt_map *, uint_t, int *, APlist **); /* List of default directories to search for dependencies. */ Alist **(*fct_get_def_dirs)(void); /* List of secure directories to search for dependencies. */ Alist **(*fct_get_sec_dirs)(void); /* Transpose the name of the object. */ int (*fct_fix_name)(const char *, Rt_map *, Alist **, Aliste, uint_t); /* Get a shared object name */ char *(*fct_get_so)(const char *, const char *, size_t, size_t); /* Retrieve a symbolic address from the object. */ void (*fct_dladdr)(ulong_t, Rt_map *, Dl_info *, void **, int); /* Process a dlsym(3c) request within the object. */ int (*fct_dlsym)(Grp_hdl *, Slookup *, Sresult *, uint_t *, int *); }; /* * Macros for getting to the file class table. */ #define LM_ENTRY_PT(X) ((X)->rt_fct->fct_entry_pt) #define LM_NEEDED(X) ((X)->rt_fct->fct_needed) #define LM_LOOKUP_SYM(X) ((X)->rt_fct->fct_lookup_sym) #define LM_RELOC(X) ((X)->rt_fct->fct_reloc) #define LM_DEFAULT_DIRS(X) ((X)->rt_fct->fct_get_def_dirs) #define LM_SECURE_DIRS(X) ((X)->rt_fct->fct_get_sec_dirs) #define LM_FIX_NAME(X) ((X)->rt_fct->fct_fix_name) #define LM_GET_SO(X) ((X)->rt_fct->fct_get_so) #define LM_DLADDR(X) ((X)->rt_fct->fct_dladdr) #define LM_DLSYM(X) ((X)->rt_fct->fct_dlsym) /* * Initial memory map allocation. Typical ELF objects contain a text and data * segment, which can be augmented with a bss mapping. Add a bunch more for * luck. */ #define MMAPFD_NUM 10 /* * Define Alist initialization counts. */ #define AL_CNT_ALIAS 2 /* ALIAS() */ #define AL_CNT_DEPENDS 20 /* DEPENDS() */ #define AL_CNT_CALLERS 20 /* CALLERS() */ #define AL_CNT_GROUPS 20 /* GROUPS() */ #define AL_CNT_COPYREL 10 /* COPY() */ #define AL_CNT_LAZYFIND 10 /* elf_lazy_find_sym() */ #define AL_CNT_GRPCLCT 10 /* gdp_collect() */ #define AL_CNT_DEPCLCT 10 /* load_finish() */ #define AL_CNT_RTLDINFO 1 /* RTLDINFO() */ #define AL_CNT_FPNODE 4 /* FPNODE() */ #define AL_CNT_LMLISTS 20 /* lm_lists */ #define AL_CNT_LMNOW 8 /* lm_now */ #define AL_CNT_RELBIND 20 /* relocation binding */ #define AL_CNT_ACTAUDIT 2 /* lm_actaudit */ #define AL_CNT_MOVES 10 /* move_data */ #define AL_CNT_MPOBJS 4 /* elf_obj_file() */ #define AL_CNT_TEXTREL 2 /* text relocation segment */ #define AL_CNT_NEEDED 1 /* dependency path */ #define AL_CNT_SEARCH 4 /* search path */ #define AL_CNT_FILTEES 2 /* filtee path */ #define AL_CNT_HANDLES 1 /* hdl_list[] */ #define AL_CNT_FREELIST 80 /* free_alp */ #define AL_CNT_CAP 10 /* capabilities candidate */ #define AL_CNT_SPATH 4 /* search path */ #define AL_CNT_DYNLIST 2 /* dynlm_list */ #define AL_CNT_PENDING 2 /* pending tsort list (INITFIRST) */ #define AL_CNT_PLTPAD 10 /* plt padding */ #define AL_CNT_AUDITORS 2 /* auditing list */ #define AL_CNT_ENVIRON 20 /* environment list (enough for ldd) */ #define AL_CNT_COOKIES 2 /* head link-map list cookies */ /* * Size of buffer for building error messages. */ #define ERRSIZE 2048 /* MAXPATHLEN * 2 */ /* * Configuration file information. */ typedef struct config { const char *c_name; Addr c_bgn; Addr c_end; Word *c_hashtbl; Word *c_hashchain; const char *c_strtbl; Rtc_obj *c_objtbl; Rtc_fltr *c_fltr; Rtc_flte *c_flte; } Config; /* * Register symbol list. */ typedef struct reglist { Rt_map *rl_lmp; /* defining object */ Sym *rl_sym; /* regsym */ struct reglist *rl_next; /* next entry */ } Reglist; /* * Data structure to hold interpreter information. */ typedef struct interp { char *i_name; /* interpreter name */ caddr_t i_faddr; /* address interpreter is mapped at */ } Interp; /* * Data structure used to keep track of copy relocations. These relocations * are collected during initial relocation processing and maintained on the * COPY(lmp) list of the defining object. Each copy list is also added to the * COPY(lmp) of the head object (normally the application dynamic executable) * from which they will be processed after all relocations are done. * * The use of RTLD_GROUP will also reference individual objects COPY(lmp) lists * in case a bound symbol must be assigned to it actual copy relocation. */ typedef struct { const char *r_name; /* symbol name */ Sym *r_rsym; /* reference symbol table entry */ Rt_map *r_rlmp; /* reference link map */ Rt_map *r_dlmp; /* definition link map */ Sym *r_dsym; /* definition symbol table entry */ void *r_radd; /* copy to address */ const void *r_dadd; /* copy from address */ ulong_t r_size; /* copy size bytes */ } Rel_copy; /* * Define a file descriptor, which maintains information regarding a pathname * that has been opened and minimally inspected. */ struct fdesc { Rt_map *fd_lmp; /* existing link-map pointer */ Lm_list *fd_lml; /* callers link-map list */ Fct *fd_ftp; /* file functions pointer */ const char *fd_oname; /* original file name */ const char *fd_odir; /* original directory name */ const char *fd_nname; /* new file (expanded) name */ const char *fd_pname; /* new path (resolved) name */ dev_t fd_dev; /* file device number */ rtld_ino_t fd_ino; /* file inode number */ avl_index_t fd_avlwhere; /* avl tree insertion index */ Syscapset fd_scapset; /* capabilities */ mmapobj_result_t *fd_mapp; /* mapping pointer */ uint_t fd_mapn; /* mapping number */ uint_t fd_flags; }; #define FLG_FD_ALTER 0x0001 /* file is an alternate */ #define FLG_FD_SLASH 0x0002 /* file contains a "/" */ #define FLG_FD_RESOLVED 0x0004 /* fd_nname has been resolved */ #define FLG_FD_ALTCHECK 0x0008 /* alternative system capabilities */ /* checked */ #define FLG_FD_ALTCAP 0x0010 /* alternative system capabilities */ /* should be used */ #define FLG_FD_IGNORE 0x0020 /* descriptor should be ignored */ /* * File descriptor availability flag. */ #define FD_UNAVAIL -1 /* * Disabled filter flag. Filter objects are referenced using their .dynamic * index (DT_FILTER or DT_AUXILIARY). This index is saved and used to lookup * the required filter. Note that 0 is a valid .dynamic index. The caller's * OBJFLTRNDX() element is initialized using the following flag, and should * the filter's initialization fail, is reset to this value to indicate the * filter is disabled. UINT_MAX provides a convenient invalid .dynamic index. */ #define FLTR_DISABLED UINT_MAX /* * Status flags for rtld_flags */ #define RT_FL_THREADS 0x00000001 /* threads are enabled */ #define RT_FL_WARNFLTR 0x00000002 /* warn of missing filtees (ldd) */ #define RT_FL_DBNOTIF 0x00000004 /* binding activity going on */ #define RT_FL_DEFERRED 0x00000008 /* load deferred dependencies (ldd) */ #define RT_FL_NOBIND 0x00000010 /* don't carry out plt binding */ #define RT_FL_NOVERSION 0x00000020 /* disable version checking */ #define RT_FL_SECURE 0x00000040 /* setuid/segid flag */ #define RT_FL_APPLIC 0x00000080 /* executing application code */ #define RT_FL_NOENVIRON 0x00000100 /* don't process environment */ /* variables (ld.so.1 -e) */ #define RT_FL_CONFGEN 0x00000200 /* don't relocate initiating object */ /* set by crle(1) */ #define RT_FL_CONFAPP 0x00000400 /* application specific configuration */ /* cache required */ #define RT_FL_DEBUGGER 0x00000800 /* a debugger is monitoring us */ #define RT_FL_OPERATION 0x00001000 /* start recording operations */ #define RT_FL_NEWLOCALE 0x00002000 /* message locale has changed */ #define RT_FL_NOBAPLT 0x00004000 /* sparc: don't use ba plt's */ #define RT_FL_NOAUXFLTR 0x00008000 /* disable auxiliary filters */ #define RT_FL_NOAUDIT 0x00020000 /* disable auditing */ #define RT_FL_ATEXIT 0x00040000 /* we're shutting down */ #define RT_FL_SILENCERR 0x00080000 /* silence error messages */ #define RT_FL_INITFIRST 0x00200000 /* processing a DT_INITFIRST object */ #define RT_FL_DEMANGLE 0x01000000 /* demangle C++ symbol names */ #define RT_FL_NOCFG 0x02000000 /* disable config file use */ #define RT_FL_NODIRCFG 0x04000000 /* disable directory config use */ #define RT_FL_NOOBJALT 0x08000000 /* disable object alternative use */ #define RT_FL_NOENVCFG 0x10000000 /* disable config envars use */ #define RT_FL_DIRCFG 0x20000000 /* directory config info available */ #define RT_FL_OBJALT 0x40000000 /* object alternatives are available */ #define RT_FL_MEMRESV 0x80000000 /* memory reservation established */ /* * Status flags for rtld_flags2 */ #define RT_FL2_HASAUDIT 0x00000001 /* auditing lm_list is present */ #define RT_FL2_RTLDSEEN 0x00000002 /* rtldinfo has been set */ #define RT_FL2_UNIFPROC 0x00000004 /* libc/libthread unified environment */ #define RT_FL2_NOFLTCFG 0x00000010 /* disable config filter use */ #define RT_FL2_FLTCFG 0x00000020 /* filter config info available */ #define RT_FL2_HWCAP 0x00000040 /* hardware capabilities available */ #define RT_FL2_FTL2WARN 0x00000080 /* convert fatal to warning messages */ #define RT_FL2_BINDNOW 0x00000100 /* LD_BIND_NOW in effect */ #define RT_FL2_BINDLAZY 0x00000200 /* disable RTLD_NOW (and LD_BIND_NOW) */ #define RT_FL2_PLMSETUP 0x00000400 /* primary link-map set up complete */ #define RT_FL2_BRANDED 0x00000800 /* process is branded */ #define RT_FL2_NOPLM 0x00001000 /* process has no primary link map */ #define RT_FL2_SETUID 0x00002000 /* ld.so.1 is setuid root */ #define RT_FL2_ADDR32 0x00004000 /* 32-bit address space requirement */ /* * Information flags for env_info. */ #define ENV_INF_PATHCFG 0x00000001 /* replaceable LD_LIBRARY_PATH */ /* originates from configuration */ /* file */ #define ENV_INF_FLAGCFG 0x00000002 /* replaceable LD_FLAGS originates */ /* from configuration file */ /* * RTLDINFO descriptor. */ typedef struct { Rt_map *rti_lmp; /* RTLDINFO provider */ Lc_interface *rti_info; /* RTLDINFO data */ } Rti_desc; /* * Binding flags for the rt_bind_guard()/rt_bind_clear() routines. * These are defined in usr/src/lib/libc/inc/libc_int.h in the * latest version of the libc/rtld runtime interface (CI_V_FIVE). */ #if !defined(CI_V_FIVE) #define THR_FLG_RTLD 0x00000001 /* rtldlock bind guard flag */ #define THR_FLG_NOLOCK 0x00000000 /* no-op before CI_V_FIVE */ #define THR_FLG_REENTER 0x00000000 /* no-op before CI_V_FIVE */ #endif #define ROUND(x, a) (((int)(x) + ((int)(a) - 1)) & ~((int)(a) - 1)) /* * Print buffer. */ typedef struct { char *pr_buf; /* pointer to beginning of buffer */ char *pr_cur; /* pointer to next free char in buffer */ size_t pr_len; /* buffer size */ int pr_fd; /* output fd */ } Prfbuf; /* * Path name descriptor. Used to construct various path names such as search * paths, dependency paths, filter paths etc. The pd_info element can be used * to hold various pointers, like Grp_hdl, Rtc_obj, etc. */ struct pdesc { const char *pd_pname; /* path name - may be expanded */ const char *pd_oname; /* original name - unexpanded */ void *pd_info; /* possible auxiliary information */ size_t pd_plen; /* path name length */ uint_t pd_flags; /* descriptor specific flags */ }; /* * Path name descriptors are passed to expand_path() and expand(). These * routines break down possible multiple path strings (separated with ":"), * and perform any reserved token expansion. These routines are passed * information that indicates the use of the path, for example, search paths, * i.e., LD_LIBRARY_PATH, RPATHS, etc. are defined using la_objsearch() * information (see LA_SER flags in link.h). This information is recorded in * the pd_flags field for later use. * * Define expansion path tokens. These are used to prevent various expansions * from occurring, and record those expansions that do. Any expansion * information is also recorded in the pd_flags field, and thus is or'd * together with any LA_SER flags. */ #define PD_TKN_ORIGIN 0x00001000 /* $ORIGIN expansion has occurred */ #define PD_TKN_PLATFORM 0x00002000 /* $PLATFORM expansion has occurred */ #define PD_TKN_OSNAME 0x00004000 /* $OSNAME expansion has occurred */ #define PD_TKN_OSREL 0x00008000 /* $OSREL expansion has occurred */ #define PD_TKN_ISALIST 0x00010000 /* $ISALIST expansion has occurred */ #define PD_TKN_CAP 0x00020000 /* $CAPABILITY/$HWCAP expansion has */ /* occurred */ #define PD_TKN_MACHINE 0x00040000 /* $MACHINE expansion has occurred */ #define PD_TKN_RESOLVED 0x00080000 /* resolvepath() expansion has */ /* occurred */ #define PD_MSK_EXPAND 0x000ff000 /* mask for all expansions */ /* * Define additional path information. These definitions extend the path * information, and may be passed into expand_path(), or set internally, or * inherited from expand(). These definitions are or'd together with any * LA_SER_ flags and PD_TKN_ flags. */ #define PD_FLG_PNSLASH 0x00100000 /* pd_pname contains a slash */ #define PD_FLG_DUPLICAT 0x00200000 /* path is a duplicate */ #define PD_FLG_EXTLOAD 0x00400000 /* path defines extra loaded objects */ /* (preload, audit etc.) */ #define PD_FLG_UNIQUE 0x00800000 /* ensure path is unique */ #define PD_FLG_USED 0x01000000 /* indicate that path is used */ #define PD_FLG_FULLPATH 0x02000000 /* ensure path is a full path */ #define PD_MSK_INHERIT 0x0ffff000 /* mask for pd_flags incorporation */ /* * Additional token expansion information. Although these flags may be set * within a token data item return from expand(), they are masked off with * PD_MSK_INHERIT prior to any expansion information being recorded in a path * name descriptor for later diagnostics. */ #define TKN_NONE 0x00000001 /* no token expansion has occurred */ #define TKN_DOTSLASH 0x00000002 /* path contains a "./" */ /* * dlopen() handle list size. */ #define HDLIST_SZ 101 /* prime no. for hashing */ #define HDLIST_ORP 102 /* orphan handle list */ /* * Define a path name search descriptor. This "cookie" maintains state as * search paths are processed with get_next_dir(). Note, the path list is an * indirect pointer, as search paths can be reevaluated for secure applications * to provide better error diagnostics. */ typedef struct { uchar_t *sp_rule; /* present search rule */ Alist **sp_dalpp; /* present path list within rule */ Aliste sp_idx; /* present index within path list */ } Spath_desc; /* * Define a path name definition descriptor. Used to maintain initial path * name definitions. */ typedef struct { const char *sd_name; /* path name */ size_t sd_len; /* path name size */ } Spath_defn; /* * Define _caller flags. */ #define CL_NONE 0 #define CL_EXECDEF 1 /* supply the executable as a default */ /* if the caller can't be determined */ /* * Binding information flags. These flags are passed up from low level binding * routines to indicate "additional" information, such as why a binding has been * rejected. These flags use the same data element as is used to record any * DBG_BINFO flags. The DBG_BINFO flags are used to define the final bindings * information and are used to provide better binding diagnostics. */ #define BINFO_REJDIRECT 0x010000 /* reject a direct binding */ #define BINFO_REJSINGLE 0x100000 /* reject a singleton binding */ #define BINFO_REJGROUP 0x200000 /* reject a group binding */ #define BINFO_MSK_TRYAGAIN 0xf00000 /* a mask of bindings that */ /* should be retried */ #define BINFO_MSK_REJECTED 0xff0000 /* a mask of bindings that */ /* have been rejected */ /* * The 32-bit version of rtld uses special stat() wrapper functions * that preserve the non-largefile semantics of stat()/fstat() while * allowing for large inode values. The 64-bit rtld uses stat() directly. */ #ifdef _LP64 #define rtld_fstat fstat #define rtld_stat stat typedef struct stat rtld_stat_t; #else typedef struct { dev_t st_dev; rtld_ino_t st_ino; mode_t st_mode; uid_t st_uid; off_t st_size; timestruc_t st_mtim; #ifdef sparc blksize_t st_blksize; #endif } rtld_stat_t; #endif /* * Some capabilities aux vector definitions have been removed over time. * However, existing objects may define these capabilities. Establish * capability masks that provide for deleting any removed capabilities, so * that these capabilities are not used to validate the associated object. * * These masks are tightly coupled to the aux vector definitions in auxv_386.h * and auxv_SPARC.h, however they are maintained here, as only ld.so.1 needs * to remove these capabilities. These definitions also describe where the * flags are associated and allow for providing multi-architecture definitions * should they become necessary, without having to pollute global header files. */ #if defined(__x86) #define AV_HW1_IGNORE (0x8000 | 0x2000) /* withdrawn MON and PAUSE */ #else /* auxv_386.h flags */ #define AV_HW1_IGNORE 0 #endif /* * Error messages generated by ld.so.1 can be written to two different places. * During initial startup, messages are flushed to the stderr. Once ld.so.1 * has jumped to the application, messages are stored in an internal buffer for * retrieval by dlerror(). Between these two conditions, events such as libc's * callbacks, and calls to auditors, are effectively jumping to application * code. These transitions from application code to ld.so.1 code are guarded by * the following macros to ensure any error messages are directed to the * appropriate output. The single argument, "f", is a local variable that * can retain, and reinstate, the RT_FL_APPLIC flag of the global rtld_flags * variable. */ #define APPLICATION_ENTER(f) \ f = (rtld_flags & RT_FL_APPLIC) ? 0 : RT_FL_APPLIC; \ rtld_flags |= f; #define APPLICATION_RETURN(f) \ rtld_flags &= ~f; /* * Data declarations. */ extern Lc_desc glcs[]; /* global external interfaces */ extern Rt_lock rtldlock; /* rtld lock */ extern int thr_flg_nolock; extern int thr_flg_reenter; extern APlist *dynlm_list; /* dynamic list of link-maps */ extern char **environ; /* environ pointer */ extern int dyn_plt_ent_size; /* Size of dynamic plt's */ extern ulong_t at_flags; /* machine specific file flags */ extern const char *procname; /* file name of executing process */ extern Rtld_db_priv r_debug; /* debugging information */ extern char *lasterr; /* string describing last error */ extern Interp *interp; /* ELF executable interpreter info */ extern const char *rtldname; /* name of the dynamic linker */ extern APlist *hdl_alp[]; /* dlopen() handle list */ extern size_t syspagsz; /* system page size */ extern Isa_desc *isa; /* isalist descriptor */ extern Uts_desc *uts; /* utsname descriptor */ extern uint_t rtld_flags; /* status flags for RTLD */ extern uint_t rtld_flags2; /* additional status flags for RTLD */ extern uint32_t pltcnt21d; /* cnt of 21d PLTs */ extern uint32_t pltcnt24d; /* cnt of 24d PLTs */ extern uint32_t pltcntu32; /* cnt of u32 PLTs */ extern uint32_t pltcntu44; /* cnt of u44 PLTs */ extern uint32_t pltcntfull; /* cnt of full PLTs */ extern uint32_t pltcntfar; /* cnt of far PLTs */ extern uchar_t search_rules[]; /* dependency search rules */ extern Fct elf_fct; /* ELF file class dependent data */ extern Config *config; /* configuration structure */ extern const char *locale; /* locale environment setting */ extern const char *rpl_audit; /* replaceable LD_AUDIT string */ extern const char *rpl_debug; /* replaceable LD_DEBUG string */ extern const char *rpl_ldflags; /* replaceable LD_FLAGS string */ extern const char *rpl_libpath; /* replaceable LD_LIBRARY string */ extern Alist *rpl_libdirs; /* and its associated Pdesc list */ extern const char *rpl_preload; /* replaceable LD_PRELOAD string */ extern const char *prm_audit; /* permanent LD_AUDIT string */ extern const char *prm_debug; /* permanent LD_DEBUG string */ extern const char *prm_ldflags; /* permanent LD_FLAGS string */ extern const char *prm_libpath; /* permanent LD_LIBRARY string */ extern Alist *prm_libdirs; /* and its associated Pdesc list */ extern const char *prm_preload; /* permanent LD_PRELOAD string */ extern Alist *elf_def_dirs; /* ELF default directory seach paths */ extern Alist *elf_sec_dirs; /* ELF secure directory seach paths */ extern uint_t env_info; /* information regarding environment */ /* variables */ extern int killsig; /* signal sent on fatal exit */ extern APlist *free_alp; /* defragmentation list */ extern uint_t audit_argcnt; /* no. of stack args to copy */ extern Audit_desc *auditors; /* global auditors */ extern APlist *aud_preinit; /* list of objects defining local */ extern APlist *aud_activity; /* preinit and activity auditors */ extern char **_environ; /* environ reference for libc */ extern const char *dbg_file; /* debugging directed to a file */ extern Reglist *reglist; /* list of register symbols */ extern const Msg err_reject[]; /* rejection error message tables */ extern const Msg ldd_reject[]; extern const Msg ldd_warn[]; extern const char *profile_name; /* object being profiled */ extern const char *profile_out; /* profile output file */ extern const char *profile_lib; /* audit library to perform profile */ extern Dl_argsinfo argsinfo; /* process argument, environment and */ /* auxv information */ extern const char *err_strs[ERR_NUM]; /* diagnostic error string headers */ extern const char *nosym_str; /* MSG_GEN_NOSYM message cache */ extern Syscapset *org_scapset; /* original system capabilities */ extern Syscapset *alt_scapset; /* alternative system capabilities */ extern const char *rpl_hwcap; /* replaceable hwcap str */ extern const char *rpl_sfcap; /* replaceable sfcap str */ extern const char *rpl_machcap; /* replaceable machcap str */ extern const char *rpl_platcap; /* replaceable platcap str */ extern const char *rpl_cap_files; /* associated files */ extern const char *prm_hwcap; /* permanent hwcap str */ extern const char *prm_sfcap; /* permanent sfcap str */ extern const char *prm_machcap; /* permanent machcap str */ extern const char *prm_platcap; /* permanent platcap str */ extern const char *prm_cap_files; /* associated files */ extern avl_tree_t *capavl; /* capabilities files */ extern avl_tree_t *nfavl; /* not-found path names */ extern avl_tree_t *spavl; /* secure path names */ extern u_longlong_t cnt_map; /* Incr. for each object mapped */ extern u_longlong_t cnt_unmap; /* Incr. for each object unmapped */ /* * Function declarations. */ extern void addfree(void *, size_t); extern int append_alias(Rt_map *, const char *, int *); extern Rt_map *analyze_lmc(Lm_list *, Aliste, Rt_map *, Rt_map *, int *); extern void atexit_fini(void); extern int bind_one(Rt_map *, Rt_map *, uint_t); extern int bufprint(Prfbuf *, const char *, ...); extern void call_array(Addr *, uint_t, Rt_map *, Word); extern void call_fini(Lm_list *, Rt_map **, Rt_map *); extern void call_init(Rt_map **, int); extern int callable(Rt_map *, Rt_map *, Grp_hdl *, uint_t); extern Rt_map *_caller(caddr_t, int); extern caddr_t caller(void); extern void *calloc(size_t, size_t); extern int cap_alternative(void); extern int cap_check_fdesc(Fdesc *, Cap *, char *, Rej_desc *); extern int cap_check_lmp(Rt_map *, Rej_desc *); extern int cap_filtees(Alist **, Aliste, const char *, Aliste, Rt_map *, Rt_map *, const char *, int, uint_t, int *); extern int cap_match(Sresult *, uint_t, Sym *, char *); extern const char *_conv_reloc_type(uint_t rel); extern Aliste create_cntl(Lm_list *, int); extern void defrag(void); extern int dbg_setup(const char *, Dbg_desc *); extern const char *demangle(const char *); extern int dlclose_intn(Grp_hdl *, Rt_map *); extern int dlclose_core(Grp_hdl *, Rt_map *, Lm_list *); extern int dlsym_handle(Grp_hdl *, Slookup *, Sresult *, uint_t *, int *); extern void *dlsym_intn(void *, const char *, Rt_map *, Rt_map **); extern Grp_hdl *dlmopen_intn(Lm_list *, const char *, int, Rt_map *, uint_t, uint_t); extern size_t doprf(const char *, va_list, Prfbuf *); extern int dowrite(Prfbuf *); extern void *dz_map(Lm_list *, caddr_t, size_t, int, int); extern int enter(int); extern uint_t expand(char **, size_t *, char **, uint_t, uint_t, Rt_map *); extern int expand_paths(Rt_map *, const char *, Alist **, Aliste, uint_t, uint_t); extern void free_hdl(Grp_hdl *); extern void file_notfound(Lm_list *, const char *, Rt_map *, uint_t, Rej_desc *); extern int find_path(Lm_list *, Rt_map *, uint_t, Fdesc *, Rej_desc *, int *); extern int fpavl_insert(Lm_list *, Rt_map *, const char *, avl_index_t); extern Rt_map *fpavl_recorded(Lm_list *, const char *, uint_t, avl_index_t *); extern void fpavl_remove(Rt_map *); extern size_t fullpath(Rt_map *, Fdesc *); extern Lmid_t get_linkmap_id(Lm_list *); extern Pdesc *get_next_dir(Spath_desc *, Rt_map *, uint_t); extern Grp_desc *hdl_add(Grp_hdl *, Rt_map *, uint_t, int *); extern Grp_hdl *hdl_create(Lm_list *, Rt_map *, Rt_map *, uint_t, uint_t, uint_t); extern int hdl_initialize(Grp_hdl *, Rt_map *, int, int); extern int hwcap1_check(Syscapset *, Xword, Rej_desc *); extern int hwcap2_check(Syscapset *, Xword, Rej_desc *); extern void is_dep_init(Rt_map *, Rt_map *); extern int is_move_data(caddr_t); extern int is_path_secure(char *, Rt_map *, uint_t, uint_t); extern int is_rtld_setuid(); extern int is_sym_interposer(Rt_map *, Sym *); extern void ldso_plt_init(Rt_map *); extern void leave(Lm_list *, int); extern void lm_append(Lm_list *, Aliste, Rt_map *); extern void lm_delete(Lm_list *, Rt_map *, Rt_map *); extern void lm_move(Lm_list *, Aliste, Aliste, Lm_cntl *, Lm_cntl *); extern Rt_map *load_cap(Lm_list *, Aliste, const char *, Rt_map *, uint_t, uint_t, Grp_hdl **, Rej_desc *, int *); extern void load_completion(Rt_map *); extern Rt_map *load_file(Lm_list *, Aliste, Rt_map *, Fdesc *, int *); extern Rt_map *load_path(Lm_list *, Aliste, Rt_map *, int, uint_t, Grp_hdl **, Fdesc *, Rej_desc *, int *); extern Rt_map *load_one(Lm_list *, Aliste, Alist *, Rt_map *, int, uint_t, Grp_hdl **, int *); extern const char *load_trace(Lm_list *, Pdesc *, Rt_map *, Fdesc *); extern void nfavl_insert(const char *, avl_index_t); extern void *nu_map(Lm_list *, caddr_t, size_t, int, int); extern Fct *map_obj(Lm_list *, Fdesc *, size_t, const char *, int, Rej_desc *); extern void *malloc(size_t); extern int machcap_check(Syscapset *, const char *, Rej_desc *); extern void machine_name(Syscapset *); extern int move_data(Rt_map *, APlist **); extern int platcap_check(Syscapset *, const char *, Rej_desc *); extern void platform_name(Syscapset *); extern int pnavl_recorded(avl_tree_t **, const char *, uint_t, avl_index_t *); extern int procenv_user(APlist *, Word *, Word *); extern void rd_event(Lm_list *, rd_event_e, r_state_e); extern int readenv_user(const char **, APlist **); extern int readenv_config(Rtc_env *, Addr); extern void rejection_inherit(Rej_desc *, Rej_desc *); extern int relocate_lmc(Lm_list *, Aliste, Rt_map *, Rt_map *, int *); extern int relocate_finish(Rt_map *, APlist *, int); extern void remove_alist(Alist **, int); extern void remove_cntl(Lm_list *, Aliste); extern int remove_hdl(Grp_hdl *, Rt_map *, int *); extern void remove_lmc(Lm_list *, Rt_map *, Aliste, const char *); extern void remove_lml(Lm_list *); extern void remove_so(Lm_list *, Rt_map *, Rt_map *); extern int rt_critical(void); extern int rt_bind_guard(int); extern int rt_bind_clear(int); extern int rt_get_extern(Lm_list *, Rt_map *); extern int rt_mutex_lock(Rt_lock *); extern int rt_mutex_unlock(Rt_lock *); extern void rt_thr_init(Lm_list *); extern thread_t rt_thr_self(void); extern void rtld_db_dlactivity(Lm_list *); extern void rtld_db_preinit(Lm_list *); extern void rtld_db_postinit(Lm_list *); extern void rtldexit(Lm_list *, int); #ifndef _LP64 extern int rtld_fstat(int, rtld_stat_t *restrict); extern int rtld_stat(const char *restrict, rtld_stat_t *restrict); #endif extern int rtld_getopt(char **, char ***, auxv_t **, Word *, Word *); extern void security(uid_t, uid_t, gid_t, gid_t, int); extern void set_environ(Lm_list *); extern void set_dirs(Alist **, Spath_defn *, uint_t); extern int set_prot(Rt_map *, mmapobj_result_t *, int); extern Rt_map *setup(char **, auxv_t *, Word, char *, int, char *, ulong_t, ulong_t, int fd, Phdr *, char *, char **, uid_t, uid_t, gid_t, gid_t, int, uint_t *); extern const char *stravl_insert(const char *, uint_t, size_t, int); extern void spavl_insert(const char *); extern int sfcap1_check(Syscapset *, Xword, Rej_desc *); extern int tls_assign(Lm_list *, Rt_map *, Phdr *); extern void tls_modaddrem(Rt_map *, uint_t); extern int tls_statmod(Lm_list *, Rt_map *); extern Rt_map **tsort(Rt_map *, int, int); extern void unused(Lm_list *); extern void unmap_obj(mmapobj_result_t *, uint_t); extern int update_mode(Rt_map *, int, int); extern void zero(caddr_t, size_t); #if defined(__sparc) /* * SPARC Register symbol support. */ extern int elf_regsyms(Rt_map *); extern void set_sparc_g1(ulong_t); extern void set_sparc_g2(ulong_t); extern void set_sparc_g3(ulong_t); extern void set_sparc_g4(ulong_t); extern void set_sparc_g5(ulong_t); extern void set_sparc_g6(ulong_t); extern void set_sparc_g7(ulong_t); #endif extern long _sysconfig(int); #ifdef __cplusplus } #endif #endif /* __RTLD_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright (c) 1988 AT&T * All Rights Reserved */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" #include "_inline_gen.h" #include "msg.h" /* * If a load filter flag is in effect, and this object is a filter, trigger the * loading of all its filtees. The load filter flag is in effect when creating * configuration files, or when under the control of ldd(1), or the LD_LOADFLTR * environment variable is set, or this object was built with the -zloadfltr * flag. Otherwise, filtee loading is deferred until triggered by a relocation. */ static void load_filtees(Rt_map *lmp, int *in_nfavl) { if ((FLAGS1(lmp) & MSK_RT_FILTER) && ((FLAGS(lmp) & FLG_RT_LOADFLTR) || (LIST(lmp)->lm_tflags & LML_TFLG_LOADFLTR))) { Dyninfo *dip = DYNINFO(lmp); uint_t cnt, max = DYNINFOCNT(lmp); Slookup sl; /* * Initialize the symbol lookup data structure. Note, no symbol * name is supplied. This NULL name causes filters to be loaded * but no symbol to be searched for. */ SLOOKUP_INIT(sl, 0, lmp, lmp, ld_entry_cnt, 0, 0, 0, 0, 0); for (cnt = 0; cnt < max; cnt++, dip++) { uint_t binfo; Sresult sr; SRESULT_INIT(sr, NULL); if (((dip->di_flags & MSK_DI_FILTER) == 0) || ((dip->di_flags & FLG_DI_AUXFLTR) && (rtld_flags & RT_FL_NOAUXFLTR))) continue; (void) elf_lookup_filtee(&sl, &sr, &binfo, cnt, in_nfavl); } } } /* * Analyze one or more link-maps of a link map control list. This routine is * called at startup to continue the processing of the main executable. It is * also called each time a new set of objects are loaded, ie. from filters, * lazy-loaded objects, or dlopen(). * * In each instance we traverse the link-map control list starting with the * initial object. As dependencies are analyzed they are added to the link-map * control list. Thus the list grows as we traverse it - this results in the * breadth first ordering of all needed objects. * * Return the initial link-map from which analysis starts for relocate_lmc(). */ Rt_map * analyze_lmc(Lm_list *lml, Aliste nlmco, Rt_map *nlmp, Rt_map *clmp, int *in_nfavl) { Rt_map *lmp; Lm_cntl *nlmc; /* * If this link-map control list is being analyzed, return. The object * that has just been added will be picked up by the existing analysis * thread. Note, this is only really meaningful during process init- * ialization, as objects are added to the main link-map control list. * Following this initialization, each family of objects that are loaded * are added to a new link-map control list. */ /* LINTED */ nlmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, nlmco); if (nlmc->lc_flags & LMC_FLG_ANALYZING) return (nlmp); /* * If this object doesn't belong to the present link-map control list * then it must already have been analyzed, or it is in the process of * being analyzed prior to us recursing into this analysis. In either * case, ignore the object as it's already being taken care of. */ if (nlmco != CNTL(nlmp)) return (nlmp); nlmc->lc_flags |= LMC_FLG_ANALYZING; for (lmp = nlmp; lmp; lmp = NEXT_RT_MAP(lmp)) { if (FLAGS(lmp) & (FLG_RT_ANALZING | FLG_RT_ANALYZED | FLG_RT_DELETE)) continue; /* * Indicate that analyzing is under way. */ FLAGS(lmp) |= FLG_RT_ANALZING; /* * If this link map represents a relocatable object, then we * need to finish the link-editing of the object at this point. */ if (FLAGS(lmp) & FLG_RT_OBJECT) { Rt_map *olmp; if ((olmp = elf_obj_fini(lml, lmp, clmp, in_nfavl)) == NULL) { if (lml->lm_flags & LML_FLG_TRC_ENABLE) continue; nlmp = NULL; break; } /* * The original link-map that captured a relocatable * object is a temporary link-map, that basically acts * as a place holder in the link-map list. On * completion of relocatable object processing, a new * link-map is created, and switched with the place * holder. Therefore, reassign both the present * link-map pointer and the return link-map pointer. * The former resets this routines link-map processing, * while the latter provides for later functions, like * relocate_lmc(), to start processing from this new * link-map. */ if (nlmp == lmp) nlmp = olmp; lmp = olmp; } DBG_CALL(Dbg_file_analyze(lmp)); /* * Establish any dependencies this object requires. */ if (LM_NEEDED(lmp)(lml, nlmco, lmp, in_nfavl) == 0) { if (lml->lm_flags & LML_FLG_TRC_ENABLE) continue; nlmp = NULL; break; } FLAGS(lmp) &= ~FLG_RT_ANALZING; FLAGS(lmp) |= FLG_RT_ANALYZED; /* * If we're building a configuration file, determine if this * object is a filter and if so load its filtees. This * traversal is only necessary for crle(1), as typical use of * an object will load filters as part of relocation processing. */ if (MODE(nlmp) & RTLD_CONFGEN) load_filtees(lmp, in_nfavl); /* * If an interposer has been added, it will have been inserted * in the link-map before the link we're presently analyzing. * Break out of this analysis loop and return to the head of * the link-map control list to analyze the interposer. Note * that this rescan preserves the breadth first loading of * dependencies. */ /* LINTED */ nlmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, nlmco); if (nlmc->lc_flags & LMC_FLG_REANALYZE) { nlmc->lc_flags &= ~LMC_FLG_REANALYZE; lmp = nlmc->lc_head; } } /* LINTED */ nlmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, nlmco); nlmc->lc_flags &= ~LMC_FLG_ANALYZING; return (nlmp); } /* * Determine whether a symbol represents zero, .bss, bits. Most commonly this * function is used to determine whether the data for a copy relocation refers * to initialized data or .bss. If the data definition is within .bss, then the * data is zero filled, and as the copy destination within the executable is * .bss, we can skip copying zero's to zero's. * * However, if the defining object has MOVE data, it's .bss might contain * non-zero data, in which case copy the definition regardless. * * For backward compatibility copy relocation processing, this routine can be * used to determine precisely if a copy destination is a move record recipient. */ static int are_bits_zero(Rt_map *dlmp, Sym *dsym, int dest) { mmapobj_result_t *mpp; caddr_t daddr = (caddr_t)dsym->st_value; if ((FLAGS(dlmp) & FLG_RT_FIXED) == 0) daddr += ADDR(dlmp); /* * Determine the segment that contains the copy definition. Given that * the copy relocation records have already been captured and verified, * a segment must be found (but we add an escape clause never the less). */ if ((mpp = find_segment(daddr, dlmp)) == NULL) return (1); /* * If the definition is not within .bss, indicate this is not zero data. */ if (daddr < (mpp->mr_addr + mpp->mr_offset + mpp->mr_fsize)) return (0); /* * If the definition is within .bss, make sure the definition isn't the * recipient of a move record. Note, we don't precisely analyze whether * the address is a move record recipient, as the infrastructure to * prepare for, and carry out this analysis, is probably more costly * than just copying the bytes regardless. */ if ((FLAGS(dlmp) & FLG_RT_MOVE) == 0) return (1); /* * However, for backward compatibility copy relocation processing, we * can afford to work a little harder. Here, determine precisely * whether the destination in the executable is a move record recipient. * See comments in lookup_sym_interpose(), below. */ if (dest && is_move_data(daddr)) return (0); return (1); } /* * Relocate an individual object. */ static int relocate_so(Lm_list *lml, Rt_map *lmp, int *relocated, int now, int *in_nfavl) { APlist *textrel = NULL; int ret = 1; /* * If we're running under ldd(1), and haven't been asked to trace any * warnings, skip any actual relocation processing. */ if (((lml->lm_flags & LML_FLG_TRC_ENABLE) == 0) || (lml->lm_flags & LML_FLG_TRC_WARN)) { if (relocated) (*relocated)++; if ((LM_RELOC(lmp)(lmp, now, in_nfavl, &textrel) == 0) && ((lml->lm_flags & LML_FLG_TRC_ENABLE) == 0)) ret = 0; /* * Finally process any move data. Note, this is carried out * with ldd(1) under relocation processing too, as it can flush * out move errors, and enables lari(1) to provide a true * representation of the runtime bindings. */ if ((FLAGS(lmp) & FLG_RT_MOVE) && (move_data(lmp, &textrel) == 0) && ((lml->lm_flags & LML_FLG_TRC_ENABLE) == 0)) ret = 0; } /* * If a text segment was write enabled to perform any relocations or * move records, then re-protect the segment by disabling writes. */ if (textrel) { mmapobj_result_t *mpp; Aliste idx; for (APLIST_TRAVERSE(textrel, idx, mpp)) (void) set_prot(lmp, mpp, 0); free(textrel); } return (ret); } /* * Relocate the objects on a link-map control list. */ static int _relocate_lmc(Lm_list *lml, Aliste lmco, Rt_map *nlmp, int *relocated, int *in_nfavl) { Rt_map *lmp; for (lmp = nlmp; lmp; lmp = NEXT_RT_MAP(lmp)) { /* * If this object has already been relocated, we're done. If * this object is being deleted, skip it, there's probably a * relocation error somewhere that's causing this deletion. */ if (FLAGS(lmp) & (FLG_RT_RELOCING | FLG_RT_RELOCED | FLG_RT_DELETE)) continue; /* * Indicate that relocation processing is under way. */ FLAGS(lmp) |= FLG_RT_RELOCING; /* * Relocate the object. */ if (relocate_so(lml, lmp, relocated, 0, in_nfavl) == 0) return (0); /* * Indicate that the objects relocation is complete. */ FLAGS(lmp) &= ~FLG_RT_RELOCING; FLAGS(lmp) |= FLG_RT_RELOCED; /* * If this object is being relocated on the main link-map list * indicate that this object's init is available for harvesting. * Objects that are being collected on other link-map lists * will have there init availability tagged when the objects * are move to the main link-map list (ie, after we know they, * and their dependencies, are fully relocated and ready for * use). * * Note, even under ldd(1) this init identification is necessary * for -i (tsort) gathering. */ if (lmco == ALIST_OFF_DATA) { lml->lm_init++; lml->lm_flags |= LML_FLG_OBJADDED; } /* * Determine if this object is a filter, and if a load filter * flag is in effect, trigger the loading of all its filtees. */ load_filtees(lmp, in_nfavl); } /* * Perform special copy relocations. These are only meaningful for * dynamic executables (fixed and head of their link-map list). If * this ever has to change then the infrastructure of COPY() has to * change. Presently, a given link map can only have a receiver or * supplier of copy data, so a union is used to overlap the storage * for the COPY_R() and COPY_S() lists. These lists would need to * be separated. */ if ((FLAGS(nlmp) & FLG_RT_FIXED) && (nlmp == LIST(nlmp)->lm_head) && (((lml->lm_flags & LML_FLG_TRC_ENABLE) == 0) || (lml->lm_flags & LML_FLG_TRC_WARN))) { Rt_map *lmp; Aliste idx1; Word tracing; #if defined(__i386) if (elf_copy_gen(nlmp) == 0) return (0); #endif if (COPY_S(nlmp) == NULL) return (1); if ((LIST(nlmp)->lm_flags & LML_FLG_TRC_ENABLE) && (((rtld_flags & RT_FL_SILENCERR) == 0) || (LIST(nlmp)->lm_flags & LML_FLG_TRC_VERBOSE))) tracing = 1; else tracing = 0; DBG_CALL(Dbg_util_nl(lml, DBG_NL_STD)); for (APLIST_TRAVERSE(COPY_S(nlmp), idx1, lmp)) { Rel_copy *rcp; Aliste idx2; for (ALIST_TRAVERSE(COPY_R(lmp), idx2, rcp)) { int zero; /* * Only copy the data if the data is from * a non-zero definition (ie. not .bss). */ zero = are_bits_zero(rcp->r_dlmp, rcp->r_dsym, 0); DBG_CALL(Dbg_reloc_copy(rcp->r_dlmp, nlmp, rcp->r_name, zero)); if (zero) continue; (void) memcpy(rcp->r_radd, rcp->r_dadd, rcp->r_size); if ((tracing == 0) || ((FLAGS1(rcp->r_dlmp) & FL1_RT_DISPREL) == 0)) continue; (void) printf(MSG_INTL(MSG_LDD_REL_CPYDISP), demangle(rcp->r_name), NAME(rcp->r_dlmp)); } } DBG_CALL(Dbg_util_nl(lml, DBG_NL_STD)); free(COPY_S(nlmp)); COPY_S(nlmp) = NULL; } return (1); } int relocate_lmc(Lm_list *lml, Aliste nlmco, Rt_map *clmp, Rt_map *nlmp, int *in_nfavl) { int lret = 1, pret = 1; APlist *alp; Aliste plmco; Lm_cntl *plmc, *nlmc; /* * If this link-map control list is being relocated, return. The object * that has just been added will be picked up by the existing relocation * thread. Note, this is only really meaningful during process init- * ialization, as objects are added to the main link-map control list. * Following this initialization, each family of objects that are loaded * are added to a new link-map control list. */ /* LINTED */ nlmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, nlmco); if (nlmc->lc_flags & LMC_FLG_RELOCATING) return (1); nlmc->lc_flags |= LMC_FLG_RELOCATING; /* * Relocate one or more link-maps of a link map control list. If this * object doesn't belong to the present link-map control list then it * must already have been relocated, or it is in the process of being * relocated prior to us recursing into this relocation. In either * case, ignore the object as it's already being taken care of, however, * fall through and capture any relocation promotions that might have * been established from the reference mode of this object. * * If we're generating a configuration file using crle(1), two passes * may be involved. Under the first pass, RTLD_CONFGEN is set. Under * this pass, crle() loads objects into the process address space. No * relocation is necessary at this point, we simply need to analyze the * objects to ensure any directly bound dependencies, filtees, etc. * get loaded. Although we skip the relocation, fall through to ensure * any control lists are maintained appropriately. * * If objects are to be dldump(3c)'ed, crle(1) makes a second pass, * using RTLD_NOW and RTLD_CONFGEN. The RTLD_NOW effectively carries * out the relocations of all loaded objects. */ if ((nlmco == CNTL(nlmp)) && ((MODE(nlmp) & (RTLD_NOW | RTLD_CONFGEN)) != RTLD_CONFGEN)) { int relocated = 0; /* * Determine whether the initial link-map control list has * started relocation. From this point, should any interposing * objects be added to this link-map control list, the objects * are demoted to standard objects. Their interposition can't * be guaranteed once relocations have been carried out. */ if (nlmco == ALIST_OFF_DATA) lml->lm_flags |= LML_FLG_STARTREL; /* * Relocate the link-map control list. Should this relocation * fail, clean up this link-map list. Relocations within this * list may have required relocation promotions on other lists, * so before acting upon these, and possibly adding more objects * to the present link-map control list, try and clean up any * failed objects now. */ lret = _relocate_lmc(lml, nlmco, nlmp, &relocated, in_nfavl); if ((lret == 0) && (nlmco != ALIST_OFF_DATA)) remove_lmc(lml, clmp, nlmco, NAME(nlmp)); } /* * Determine the new, and previous link-map control lists. */ /* LINTED */ nlmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, nlmco); if (nlmco == ALIST_OFF_DATA) { plmco = nlmco; plmc = nlmc; } else { plmco = nlmco - lml->lm_lists->al_size; /* LINTED */ plmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, plmco); } /* * Having completed this control list of objects, they can now be bound * to from other objects. Move this control list to the control list * that precedes it. Although this control list may have only bound to * controls lists much higher up the control list stack, it must only * be moved up one control list so as to preserve the link-map order * that may have already been traversed in search of symbols. */ if (lret && (nlmco != ALIST_OFF_DATA) && nlmc->lc_head) lm_move(lml, nlmco, plmco, nlmc, plmc); /* * Determine whether existing objects that have already been relocated, * need any additional relocations performed. This can occur when new * objects are loaded with RTLD_NOW, and these new objects have * dependencies on objects that are already loaded. Note, that we peel * any relocation promotions off of one control list at a time. This * prevents relocations from being bound to objects that might yet fail * to relocate themselves. */ while ((alp = plmc->lc_now) != NULL) { Aliste idx; Rt_map *lmp; /* * Remove the relocation promotion list, as performing more * relocations may result in discovering more objects that need * promotion. */ plmc->lc_now = NULL; for (APLIST_TRAVERSE(alp, idx, lmp)) { /* * If the original relocation of the link-map control * list failed, or one of the relocation promotions of * this loop has failed, demote any pending objects * relocation mode. */ if ((lret == 0) || (pret == 0)) { MODE(lmp) &= ~RTLD_NOW; MODE(lmp) |= RTLD_LAZY; continue; } /* * If a relocation fails, save the error condition. * It's possible that all new objects on the original * link-map control list have been relocated * successfully, but if the user request requires * promoting objects that have already been loaded, we * have to indicate that this operation couldn't be * performed. The unrelocated objects are in use on * another control list, and may continue to be used. * If the .plt that resulted in the error is called, * then the process will receive a fatal error at that * time. But, the .plt may never be called. */ if (relocate_so(lml, lmp, 0, 1, in_nfavl) == 0) pret = 0; } /* * Having promoted any objects, determine whether additional * dependencies were added, and if so move them to the previous * link-map control list. */ /* LINTED */ nlmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, nlmco); /* LINTED */ plmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, plmco); if ((nlmco != ALIST_OFF_DATA) && nlmc->lc_head) lm_move(lml, nlmco, plmco, nlmc, plmc); free(alp); } /* * If relocations have been successful, indicate that relocations are * no longer active for this control list. Otherwise, leave the * relocation flag, as this flag is used to determine the style of * cleanup (see remove_lmc()). */ if (lret && pret) { /* LINTED */ nlmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, nlmco); nlmc->lc_flags &= ~LMC_FLG_RELOCATING; return (1); } return (0); } /* * Inherit the first rejection message for possible later diagnostics. * * Any attempt to process a file that is unsuccessful, should be accompanied * with an error diagnostic. However, some operations like searching for a * simple filename, involve trying numerous paths, and an error message for each * lookup is not required. Although a multiple search can fail, it's possible * that a file was found, but was rejected because it was the wrong type. * To satisfy these possibilities, the first failure is recorded as a rejection * message, and this message is used later for a more specific diagnostic. * * File searches are focused at load_one(), and from here a rejection descriptor * is passed down to various child routines. If these child routines can * process multiple files, then they will maintain their own rejection desc- * riptor. This is filled in for any failures, and a diagnostic produced to * reflect the failure. The child routines then employ rejection_inherit() to * pass the first rejection message back to load_one(). * * Note that the name, and rejection string must be duplicated, as the name * buffer and error string buffer (see conv_ routines) may be reused for * additional processing or rejection messages. */ void rejection_inherit(Rej_desc *rej1, Rej_desc *rej2) { if (rej2->rej_type && (rej1->rej_type == 0)) { rej1->rej_type = rej2->rej_type; rej1->rej_info = rej2->rej_info; rej1->rej_flags = rej2->rej_flags; if (rej2->rej_name) rej1->rej_name = stravl_insert(rej2->rej_name, 0, 0, 0); if ((rej2->rej_str) && ((rej1->rej_str = stravl_insert(rej2->rej_str, 0, 0, 0)) == NULL)) rej1->rej_str = MSG_ORIG(MSG_EMG_ENOMEM); } } /* * Helper routine for is_so_matched() that consolidates matching a path name, * or file name component of a link-map name. */ inline static int _is_so_matched(const char *name, const char *str, int path) { const char *_str; if ((path == 0) && ((_str = strrchr(str, '/')) != NULL)) _str++; else _str = str; return (strcmp(name, _str)); } /* * Determine whether a search name matches one of the names associated with a * link-map. A link-map contains several names: * * - a NAME() - this is the basename of the dynamic executable that started * the process, and the path name of any dependencies used by the process. * Most executables are received as full path names, as exec() prepends a * search $PATH to locate the executable. However, simple file names can * be received from exec() if the file is executed from the present working * directory. Regardless, ld.so.1 maintains NAME() as the basename, as * this has always been the name used in diagnostics and error messages. * Most dependencies are full path names, as the typical search for a * dependency, say "libx.so.1", results in search paths being prepended to * the name, which eventually open "/lib/libx.so.1". However, relative * path names can be supplied as dependencies, e.g. dlopen("../libx.so.1"). * * - a PATHNAME() - this is the fully resolved path name of the object. This * name will differ from NAME() for all dynamic executables, and may differ * from the NAME() of dependencies, if the dependency is not a full path * name, or the dependency resolves to a symbolic link. * * - an ALIAS() name - these are alternative names by which the object has * been found, ie. when dependencies are loaded through a variety of * different symbolic links. * * The name pattern matching can differ depending on whether we are looking * for a full path name (path != 0), or a simple file name (path == 0). Full * path names typically match NAME() or PATHNAME() entries. * * For all full path name searches, the link-map names are taken as is. For * simple file name searches, only the file name component of any link-map * names are used for comparison. */ inline static Rt_map * is_so_matched(Rt_map *lmp, const char *name, int path) { Aliste idx; const char *cp; if (_is_so_matched(name, NAME(lmp), path) == 0) return (lmp); if (PATHNAME(lmp) != NAME(lmp)) { if (_is_so_matched(name, PATHNAME(lmp), path) == 0) return (lmp); } for (APLIST_TRAVERSE(ALIAS(lmp), idx, cp)) { if (_is_so_matched(name, cp, path) == 0) return (lmp); } return (NULL); } /* * Files are opened by ld.so.1 to satisfy dependencies, filtees and dlopen() * requests. Each request investigates the file based upon the callers * environment. Once a full path name has been established, the following * checks are made: * * - does the path exist in the link-map lists FullPathNode AVL tree? if * so, the file is already loaded, and its associated link-map pointer * is returned. * - does the path exist in the not-found AVL tree? if so, this path has * already been determined to not exist, and a failure is returned. * - a device/inode check, to ensure the same file isn't mapped multiple * times through different paths. See file_open(). * * However, there are cases where a test for an existing file name needs to be * carried out, such as dlopen(NOLOAD) requests, dldump() requests, and as a * final fallback to dependency loading. These requests are handled by * is_so_loaded(). * * A traversal through the callers link-map list is carried out, and from each * link-map, a comparison is made against all of the various names by which the * object has been referenced. is_so_matched() is used to compare the link-map * names against the name being searched for. Whether the search name is a full * path name or a simple file name, governs what comparisons are made. * * A full path name, which is a fully resolved path name that starts with a "/" * character, or a relative path name that includes a "/" character, must match * the link-map names exactly. A simple file name, which is any name *not* * containing a "/" character, are matched against the file name component of * any link-map names. */ Rt_map * is_so_loaded(Lm_list *lml, const char *name, int *in_nfavl) { Rt_map *lmp; avl_index_t where; Lm_cntl *lmc; Aliste idx; int path = 0; /* * If the name is a full path name, first determine if the path name is * registered on the FullPathNode AVL, or not-found AVL trees. */ if (name[0] == '/') { uint_t hash = sgs_str_hash(name); if (((lmp = fpavl_recorded(lml, name, hash, &where)) != NULL) && ((FLAGS(lmp) & (FLG_RT_OBJECT | FLG_RT_DELETE)) == 0)) return (lmp); if (pnavl_recorded(&nfavl, name, hash, NULL)) { /* * For dlopen() and dlsym() fall backs, indicate that * a registered not-found path has indicated that this * object does not exist. */ if (in_nfavl) (*in_nfavl)++; return (NULL); } } /* * Determine whether the name is a simple file name, or a path name. */ if (strchr(name, '/')) path++; /* * Loop through the callers link-map lists. */ for (ALIST_TRAVERSE(lml->lm_lists, idx, lmc)) { for (lmp = lmc->lc_head; lmp; lmp = NEXT_RT_MAP(lmp)) { if (FLAGS(lmp) & (FLG_RT_OBJECT | FLG_RT_DELETE)) continue; if (is_so_matched(lmp, name, path)) return (lmp); } } return (NULL); } /* * Tracing is enabled by the LD_TRACE_LOADED_OPTIONS environment variable which * is normally set from ldd(1). For each link map we load, print the load name * and the full pathname of the associated object. */ /* ARGSUSED4 */ static void trace_so(Rt_map *clmp, Rej_desc *rej, const char *name, const char *path, int alter, const char *nfound) { const char *str = MSG_ORIG(MSG_STR_EMPTY); const char *reject = MSG_ORIG(MSG_STR_EMPTY); char _reject[PATH_MAX]; /* * The first time through trace_so() will only have lddstub on the * link-map list and the preloaded shared object is supplied as "path". * As we don't want to print this shared object as a dependency, but * instead inspect *its* dependencies, return. */ if (FLAGS1(clmp) & FL1_RT_LDDSTUB) return; /* * Without any rejection info, this is a supplied not-found condition. */ if (rej && (rej->rej_type == 0)) { (void) printf(nfound, name); return; } /* * If rejection information exists then establish what object was * found and the reason for its rejection. */ if (rej) { Conv_reject_desc_buf_t rej_buf; /* LINTED */ (void) snprintf(_reject, PATH_MAX, MSG_INTL(ldd_reject[rej->rej_type]), conv_reject_desc(rej, &rej_buf, M_MACH)); if (rej->rej_name) path = rej->rej_name; reject = (char *)_reject; /* * Was an alternative pathname defined (from a configuration * file). */ if (rej->rej_flags & FLG_REJ_ALTER) str = MSG_INTL(MSG_LDD_FIL_ALTER); } else { if (alter) str = MSG_INTL(MSG_LDD_FIL_ALTER); } /* * If the load name isn't a full pathname print its associated pathname * together with all the other information we've gathered. */ if (*name == '/') (void) printf(MSG_ORIG(MSG_LDD_FIL_PATH), path, str, reject); else (void) printf(MSG_ORIG(MSG_LDD_FIL_EQUIV), name, path, str, reject); } /* * Establish a link-map mode, initializing it if it has just been loaded, or * potentially updating it if it already exists. */ int update_mode(Rt_map *lmp, int omode, int nmode) { Lm_list *lml = LIST(lmp); int pmode = 0; /* * A newly loaded object hasn't had its mode set yet. Modes are used to * load dependencies, so don't propagate any parent or no-load flags, as * these would adversely affect this objects ability to load any of its * dependencies that aren't already loaded. RTLD_FIRST is applicable to * this objects handle creation only, and should not be propagated. */ if ((FLAGS(lmp) & FLG_RT_MODESET) == 0) { MODE(lmp) |= nmode & ~(RTLD_PARENT | RTLD_NOLOAD | RTLD_FIRST); FLAGS(lmp) |= FLG_RT_MODESET; return (1); } /* * Establish any new overriding modes. RTLD_LAZY and RTLD_NOW should be * represented individually (this is historic, as these two flags were * the only flags originally available to dlopen()). Other flags are * accumulative, but have a hierarchy of preference. */ if ((omode & RTLD_LAZY) && (nmode & RTLD_NOW)) { MODE(lmp) &= ~RTLD_LAZY; pmode |= RTLD_NOW; } pmode |= ((~omode & nmode) & (RTLD_GLOBAL | RTLD_WORLD | RTLD_NODELETE)); if (pmode) { DBG_CALL(Dbg_file_mode_promote(lmp, pmode)); MODE(lmp) |= pmode; } /* * If this load is an RTLD_NOW request and the object has already been * loaded non-RTLD_NOW, append this object to the relocation-now list * of the objects associated control list. Note, if the object hasn't * yet been relocated, setting its MODE() to RTLD_NOW will establish * full relocation processing when it eventually gets relocated. */ if ((pmode & RTLD_NOW) && (FLAGS(lmp) & (FLG_RT_RELOCED | FLG_RT_RELOCING))) { Lm_cntl *lmc; /* LINTED */ lmc = (Lm_cntl *)alist_item_by_offset(LIST(lmp)->lm_lists, CNTL(lmp)); (void) aplist_append(&lmc->lc_now, lmp, AL_CNT_LMNOW); } /* * If this objects .init has been collected but has not yet been called, * it may be necessary to reevaluate the object using tsort(). For * example, a new dlopen() hierarchy may bind to uninitialized objects * that are already loaded, or a dlopen(RTLD_NOW) can establish new * bindings between already loaded objects that require the tsort() * information be recomputed. If however, no new objects have been * added to the process, and this object hasn't been promoted, don't * bother reevaluating the .init. The present tsort() information is * probably as accurate as necessary, and by not establishing a parallel * tsort() we can help reduce the amount of recursion possible between * .inits. */ if (((FLAGS(lmp) & (FLG_RT_INITCLCT | FLG_RT_INITCALL)) == FLG_RT_INITCLCT) && ((lml->lm_flags & LML_FLG_OBJADDED) || ((pmode & RTLD_NOW) && (FLAGS(lmp) & (FLG_RT_RELOCED | FLG_RT_RELOCING))))) { FLAGS(lmp) &= ~FLG_RT_INITCLCT; LIST(lmp)->lm_init++; LIST(lmp)->lm_flags |= LML_FLG_OBJREEVAL; } return (pmode); } /* * Determine whether an alias name already exists, and if not create one. This * is typically used to retain dependency names, such as "libc.so.1", which * would have been expanded to full path names when they were loaded. The * full path names (NAME() and possibly PATHNAME()) are maintained on the * FullPathNode AVL tree, and thus would have been matched by fpavl_loaded() * during file_open(). */ int append_alias(Rt_map *lmp, const char *str, int *added) { const char *cp; Aliste idx; /* * Determine if this filename is already on the alias list. */ for (APLIST_TRAVERSE(ALIAS(lmp), idx, cp)) { if (strcmp(cp, str) == 0) return (1); } /* * This is a new alias, append it to the alias list. */ if (((cp = stravl_insert(str, 0, 0, 0)) == NULL) || (aplist_append(&ALIAS(lmp), cp, AL_CNT_ALIAS) == NULL)) return (0); if (added) *added = 1; return (1); } /* * Determine whether a file is already loaded by comparing device and inode * values. */ static Rt_map * is_devinode_loaded(rtld_stat_t *status, Lm_list *lml, const char *name, uint_t flags) { Lm_cntl *lmc; Aliste idx; /* * If this is an auditor, it will have been opened on a new link-map. * To prevent multiple occurrences of the same auditor on multiple * link-maps, search the head of each link-map list and see if this * object is already loaded as an auditor. */ if (flags & FLG_RT_AUDIT) { Lm_list *lml; for (APLIST_TRAVERSE(dynlm_list, idx, lml)) { Rt_map *nlmp = lml->lm_head; if (nlmp && ((FLAGS(nlmp) & (FLG_RT_AUDIT | FLG_RT_DELETE)) == FLG_RT_AUDIT) && (STDEV(nlmp) == status->st_dev) && (STINO(nlmp) == status->st_ino)) return (nlmp); } return (NULL); } /* * If the file has been found determine from the new files status * information if this file is actually linked to one we already have * mapped. This catches symlink names not caught by is_so_loaded(). */ for (ALIST_TRAVERSE(lml->lm_lists, idx, lmc)) { Rt_map *nlmp; for (nlmp = lmc->lc_head; nlmp; nlmp = NEXT_RT_MAP(nlmp)) { if ((FLAGS(nlmp) & FLG_RT_DELETE) || (FLAGS1(nlmp) & FL1_RT_LDDSTUB)) continue; if ((STDEV(nlmp) != status->st_dev) || (STINO(nlmp) != status->st_ino)) continue; if (lml->lm_flags & LML_FLG_TRC_VERBOSE) { /* BEGIN CSTYLED */ if (*name == '/') (void) printf(MSG_ORIG(MSG_LDD_FIL_PATH), name, MSG_ORIG(MSG_STR_EMPTY), MSG_ORIG(MSG_STR_EMPTY)); else (void) printf(MSG_ORIG(MSG_LDD_FIL_EQUIV), name, NAME(nlmp), MSG_ORIG(MSG_STR_EMPTY), MSG_ORIG(MSG_STR_EMPTY)); /* END CSTYLED */ } return (nlmp); } } return (NULL); } /* * Generate any error messages indicating a file could not be found. When * preloading or auditing a secure application, it can be a little more helpful * to indicate that a search of secure directories has failed, so adjust the * messages accordingly. */ void file_notfound(Lm_list *lml, const char *name, Rt_map *clmp, uint_t flags, Rej_desc *rej) { int secure = 0; if ((rtld_flags & RT_FL_SECURE) && (flags & (FLG_RT_PRELOAD | FLG_RT_AUDIT))) secure++; if (lml->lm_flags & LML_FLG_TRC_ENABLE) { /* * Under ldd(1), auxiliary filtees that can't be loaded are * ignored, unless verbose errors are requested. */ if ((rtld_flags & RT_FL_SILENCERR) && ((lml->lm_flags & LML_FLG_TRC_VERBOSE) == 0)) return; if (secure) trace_so(clmp, rej, name, 0, 0, MSG_INTL(MSG_LDD_SEC_NFOUND)); else trace_so(clmp, rej, name, 0, 0, MSG_INTL(MSG_LDD_FIL_NFOUND)); return; } if (rej->rej_type) { Conv_reject_desc_buf_t rej_buf; eprintf(lml, ERR_FATAL, MSG_INTL(err_reject[rej->rej_type]), rej->rej_name ? rej->rej_name : MSG_INTL(MSG_STR_UNKNOWN), conv_reject_desc(rej, &rej_buf, M_MACH)); return; } if (secure) eprintf(lml, ERR_FATAL, MSG_INTL(MSG_SEC_OPEN), name); else eprintf(lml, ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), name, strerror(ENOENT)); } static int file_open(int err, Lm_list *lml, Rt_map *clmp, uint_t flags, Fdesc *fdp, Rej_desc *rej, int *in_nfavl) { rtld_stat_t status; Rt_map *nlmp; avl_index_t nfavlwhere = 0; const char *oname = fdp->fd_oname, *nname = fdp->fd_nname; uint_t hash = sgs_str_hash(nname); if ((nname = stravl_insert(fdp->fd_nname, hash, 0, 0)) == NULL) return (0); fdp->fd_nname = nname; if ((err == 0) && (fdp->fd_flags & FLG_FD_ALTER)) DBG_CALL(Dbg_file_config_obj(lml, oname, 0, nname)); /* * If we're dealing with a full pathname, determine whether this * pathname is already known. Other pathnames fall through to the * dev/inode check, as even though the pathname may look the same as * one previously used, the process may have changed directory. */ if ((err == 0) && (nname[0] == '/')) { if ((nlmp = fpavl_recorded(lml, nname, hash, &(fdp->fd_avlwhere))) != NULL) { fdp->fd_lmp = nlmp; return (1); } if (pnavl_recorded(&nfavl, nname, hash, &nfavlwhere)) { /* * For dlopen() and dlsym() fall backs, indicate that * a registered not-found path has indicated that this * object does not exist. If this path has been * constructed as part of expanding a CAPABILITY * directory, this is a silent failure, where no * rejection message is created. */ if (in_nfavl) (*in_nfavl)++; return (0); } } if ((err == 0) && ((rtld_stat(nname, &status)) != -1)) { char path[PATH_MAX]; int fd, size, added; /* * If this path has been constructed as part of expanding a * CAPABILITY directory, ignore any subdirectories. As this * is a silent failure, no rejection message is created. For * any other reference that expands to a directory, fall * through to construct a meaningful rejection message. */ if ((flags & FLG_RT_CAP) && ((status.st_mode & S_IFMT) == S_IFDIR)) return (0); /* * If this is a directory (which can't be mmap()'ed) generate a * precise error message. */ if ((status.st_mode & S_IFMT) == S_IFDIR) { rej->rej_name = nname; if (fdp->fd_flags & FLG_FD_ALTER) rej->rej_flags = FLG_REJ_ALTER; rej->rej_type = SGS_REJ_STR; rej->rej_str = strerror(EISDIR); DBG_CALL(Dbg_file_rejected(lml, rej, M_MACH)); return (0); } /* * Resolve the filename and determine whether the resolved name * is already known. Typically, the previous fpavl_loaded() * will have caught this, as both NAME() and PATHNAME() for a * link-map are recorded in the FullPathNode AVL tree. However, * instances exist where a file can be replaced (loop-back * mounts, bfu, etc.), and reference is made to the original * file through a symbolic link. By checking the pathname here, * we don't fall through to the dev/inode check and conclude * that a new file should be loaded. */ if ((nname[0] == '/') && ((size = resolvepath(nname, path, (PATH_MAX - 1))) > 0)) { path[size] = '\0'; fdp->fd_flags |= FLG_FD_RESOLVED; if (strcmp(nname, path)) { if ((nlmp = fpavl_recorded(lml, path, 0, 0)) != NULL) { added = 0; if (append_alias(nlmp, nname, &added) == 0) return (0); /* BEGIN CSTYLED */ if (added) DBG_CALL(Dbg_file_skip(LIST(clmp), NAME(nlmp), nname)); /* END CSTYLED */ fdp->fd_lmp = nlmp; return (1); } /* * If this pathname hasn't been loaded, save * the resolved pathname so that it doesn't * have to be recomputed as part of fullpath() * processing. */ if ((fdp->fd_pname = stravl_insert(path, 0, (size + 1), 0)) == NULL) return (0); } } nlmp = is_devinode_loaded(&status, lml, nname, flags); if (nlmp != NULL) { if (flags & FLG_RT_AUDIT) { /* * If we've been requested to load an auditor, * and an auditor of the same name already * exists, then the original auditor is used. */ DBG_CALL(Dbg_audit_skip(LIST(clmp), NAME(nlmp), LIST(nlmp)->lm_lmidstr)); } else { /* * Otherwise, if an alternatively named file * has been found for the same dev/inode, add * a new name alias. Insert any alias full path * name in the FullPathNode AVL tree. */ added = 0; if (append_alias(nlmp, nname, &added) == 0) return (0); if (added) { if ((nname[0] == '/') && (fpavl_insert(lml, nlmp, nname, 0) == 0)) return (0); DBG_CALL(Dbg_file_skip(LIST(clmp), NAME(nlmp), nname)); } } /* * Record in the file descriptor the existing object * that satisfies this open request. */ fdp->fd_lmp = nlmp; return (1); } if ((fd = open(nname, O_RDONLY, 0)) == -1) { /* * As the file must exist for the previous stat() to * have succeeded, record the error condition. */ rej->rej_type = SGS_REJ_STR; rej->rej_str = strerror(errno); } else { /* * Map the object. A successful return indicates that * the object is appropriate for ld.so.1 processing. */ fdp->fd_ftp = map_obj(lml, fdp, status.st_size, nname, fd, rej); (void) close(fd); if (fdp->fd_ftp != NULL) { fdp->fd_dev = status.st_dev; fdp->fd_ino = status.st_ino; return (1); } } } else if (errno != ENOENT) { /* * If the open() failed for anything other than the file not * existing, record the error condition. */ rej->rej_type = SGS_REJ_STR; rej->rej_str = strerror(errno); } /* * Regardless of error, duplicate and record any full path names that * can't be used on the "not-found" AVL tree. */ if (nname[0] == '/') nfavl_insert(nname, nfavlwhere); /* * Indicate any rejection. */ if (rej->rej_type) { rej->rej_name = nname; if (fdp->fd_flags & FLG_FD_ALTER) rej->rej_flags = FLG_REJ_ALTER; DBG_CALL(Dbg_file_rejected(lml, rej, M_MACH)); } return (0); } /* * Find a full pathname (it contains a "/"). */ int find_path(Lm_list *lml, Rt_map *clmp, uint_t flags, Fdesc *fdp, Rej_desc *rej, int *in_nfavl) { const char *oname = fdp->fd_oname; int err = 0; /* * If directory configuration exists determine if this path is known. */ if (rtld_flags & RT_FL_DIRCFG) { Rtc_obj *obj; const char *aname; if ((obj = elf_config_ent(oname, (Word)elf_hash(oname), 0, &aname)) != 0) { /* * If the configuration file states that this path is a * directory, or the path is explicitly defined as * non-existent (ie. a unused platform specific * library), then go no further. */ if (obj->co_flags & RTC_OBJ_DIRENT) { err = EISDIR; } else if ((obj->co_flags & (RTC_OBJ_NOEXIST | RTC_OBJ_ALTER)) == RTC_OBJ_NOEXIST) { err = ENOENT; } else if ((obj->co_flags & RTC_OBJ_ALTER) && (rtld_flags & RT_FL_OBJALT) && (lml == &lml_main)) { int ret; fdp->fd_flags |= FLG_FD_ALTER; fdp->fd_nname = aname; /* * Attempt to open the alternative path. If * this fails, and the alternative is flagged * as optional, fall through to open the * original path. */ DBG_CALL(Dbg_libs_found(lml, aname, FLG_FD_ALTER)); ret = file_open(0, lml, clmp, flags, fdp, rej, in_nfavl); if (ret || ((obj->co_flags & RTC_OBJ_OPTINAL) == 0)) return (ret); fdp->fd_flags &= ~FLG_FD_ALTER; } } } DBG_CALL(Dbg_libs_found(lml, oname, 0)); fdp->fd_nname = oname; return (file_open(err, lml, clmp, flags, fdp, rej, in_nfavl)); } /* * Find a simple filename (it doesn't contain a "/"). */ static int _find_file(Lm_list *lml, Rt_map *clmp, uint_t flags, Fdesc *fdp, Rej_desc *rej, Pdesc *pdp, int aflag, int *in_nfavl) { const char *nname = fdp->fd_nname; DBG_CALL(Dbg_libs_found(lml, nname, aflag)); if ((lml->lm_flags & LML_FLG_TRC_SEARCH) && ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0)) { (void) printf(MSG_INTL(MSG_LDD_PTH_TRYING), nname, aflag ? MSG_INTL(MSG_LDD_FIL_ALTER) : MSG_ORIG(MSG_STR_EMPTY)); } /* * If we're being audited tell the audit library of the file we're about * to go search for. The audit library may offer an alternative * dependency, or indicate that this dependency should be ignored. */ if ((lml->lm_tflags | AFLAGS(clmp)) & LML_TFLG_AUD_OBJSEARCH) { char *aname; if ((aname = audit_objsearch(clmp, nname, (pdp->pd_flags & LA_SER_MASK))) == NULL) { DBG_CALL(Dbg_audit_terminate(lml, nname)); return (0); } if (aname != nname) { fdp->fd_flags &= ~FLG_FD_SLASH; fdp->fd_nname = aname; } } return (file_open(0, lml, clmp, flags, fdp, rej, in_nfavl)); } static int find_file(Lm_list *lml, Rt_map *clmp, uint_t flags, Fdesc *fdp, Rej_desc *rej, Pdesc *pdp, Word *strhash, int *in_nfavl) { static Rtc_obj Obj = { 0 }; Rtc_obj *dobj; const char *oname = fdp->fd_oname; size_t olen = strlen(oname); if (pdp->pd_pname == NULL) return (0); if (pdp->pd_info) { dobj = (Rtc_obj *)pdp->pd_info; if ((dobj->co_flags & (RTC_OBJ_NOEXIST | RTC_OBJ_ALTER)) == RTC_OBJ_NOEXIST) return (0); } else dobj = NULL; /* * If configuration information exists see if this directory/file * combination exists. */ if ((rtld_flags & RT_FL_DIRCFG) && ((dobj == NULL) || (dobj->co_id != 0))) { Rtc_obj *fobj; const char *aname = NULL; /* * If this object descriptor has not yet been searched for in * the configuration file go find it. */ if (dobj == NULL) { dobj = elf_config_ent(pdp->pd_pname, (Word)elf_hash(pdp->pd_pname), 0, 0); if (dobj == NULL) dobj = &Obj; pdp->pd_info = (void *)dobj; if ((dobj->co_flags & (RTC_OBJ_NOEXIST | RTC_OBJ_ALTER)) == RTC_OBJ_NOEXIST) return (0); } /* * If we found a directory search for the file. */ if (dobj->co_id != 0) { if (*strhash == 0) *strhash = (Word)elf_hash(oname); fobj = elf_config_ent(oname, *strhash, dobj->co_id, &aname); /* * If this object specifically does not exist, or the * object can't be found in a know-all-entries * directory, continue looking. If the object does * exist determine if an alternative object exists. */ if (fobj == NULL) { if (dobj->co_flags & RTC_OBJ_ALLENTS) return (0); } else { if ((fobj->co_flags & (RTC_OBJ_NOEXIST | RTC_OBJ_ALTER)) == RTC_OBJ_NOEXIST) return (0); if ((fobj->co_flags & RTC_OBJ_ALTER) && (rtld_flags & RT_FL_OBJALT) && (lml == &lml_main)) { int ret; fdp->fd_flags |= FLG_FD_ALTER; fdp->fd_nname = aname; /* * Attempt to open the alternative path. * If this fails, and the alternative is * flagged as optional, fall through to * open the original path. */ ret = _find_file(lml, clmp, flags, fdp, rej, pdp, 1, in_nfavl); if (ret || ((fobj->co_flags & RTC_OBJ_OPTINAL) == 0)) return (ret); fdp->fd_flags &= ~FLG_FD_ALTER; } } } } /* * Protect ourselves from building an invalid pathname. */ if ((olen + pdp->pd_plen + 1) >= PATH_MAX) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), oname, strerror(ENAMETOOLONG)); return (0); } if ((fdp->fd_nname = (LM_GET_SO(clmp)(pdp->pd_pname, oname, pdp->pd_plen, olen))) == NULL) return (0); return (_find_file(lml, clmp, flags, fdp, rej, pdp, 0, in_nfavl)); } static Fct *Vector[] = { &elf_fct, 0 }; /* * Remap the first page of a file to provide a better diagnostic as to why * an mmapobj(2) operation on this file failed. Sadly, mmapobj(), and all * system calls for that matter, only pass back a generic failure in errno. * Hopefully one day this will be improved, but in the mean time we repeat * the kernels ELF verification to try and provide more detailed information. */ static int map_fail(Fdesc *fdp, size_t fsize, const char *name, int fd, Rej_desc *rej) { caddr_t addr; int vnum; size_t size; /* * Use the original file size to determine what to map, and catch the * obvious error of a zero sized file. */ if (fsize == 0) { rej->rej_type = SGS_REJ_UNKFILE; return (1); } else if (fsize < syspagsz) size = fsize; else size = syspagsz; if ((addr = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) return (0); rej->rej_type = 0; /* * Validate the file against each supported file type. Should a * characteristic of the file be found invalid for this platform, a * rejection message will have been recorded. */ for (vnum = 0; Vector[vnum]; vnum++) { if (((Vector[vnum]->fct_verify_file)(addr, size, fdp, name, rej) == 0) && rej->rej_type) break; } /* * If no rejection message has been recorded, then this is simply an * unknown file type. */ if (rej->rej_type == 0) rej->rej_type = SGS_REJ_UNKFILE; (void) munmap(addr, size); return (1); } /* * Unmap a file. */ void unmap_obj(mmapobj_result_t *mpp, uint_t mapnum) { uint_t num; for (num = 0; num < mapnum; num++) { /* LINTED */ (void) munmap((void *)(uintptr_t)mpp[num].mr_addr, mpp[num].mr_msize); } cnt_unmap++; } /* * Map a file. */ Fct * map_obj(Lm_list *lml, Fdesc *fdp, size_t fsize, const char *name, int fd, Rej_desc *rej) { static mmapobj_result_t *smpp = NULL; static uint_t smapnum; mmapobj_result_t *mpp; uint_t mnum, mapnum, mflags; void *padding; /* * Allocate an initial mapping array. The initial size should be large * enough to handle the normal ELF objects we come across. */ if (smpp == NULL) { smpp = malloc(sizeof (mmapobj_result_t) * MMAPFD_NUM); if (smpp == NULL) return (NULL); smapnum = MMAPFD_NUM; } /* * If object padding is required, set the necessary flags. */ if (r_debug.rtd_objpad) { mflags = MMOBJ_INTERPRET | MMOBJ_PADDING; padding = &r_debug.rtd_objpad; } else { mflags = MMOBJ_INTERPRET; padding = NULL; } /* * Map the file. If the number of mappings required by this file * exceeds the present mapping structure, an error indicating the * return data is too big is returned. Bail on any other error. */ mapnum = smapnum; if (mmapobj(fd, mflags, smpp, &mapnum, padding) == -1) { if (errno != E2BIG) { int err = errno; /* * An unsupported error indicates that there's something * incompatible with this ELF file, and the process that * is already running. Map the first page of the file * and see if we can generate a better error message. */ if ((errno == ENOTSUP) && map_fail(fdp, fsize, name, fd, rej)) return (NULL); rej->rej_type = SGS_REJ_STR; rej->rej_str = strerror(err); return (NULL); } /* * The mapping requirement exceeds the present mapping * structure, however the number of mapping required is * available in the mapping number. */ free((void *)smpp); if ((smpp = malloc(sizeof (mmapobj_result_t) * mapnum)) == NULL) return (NULL); smapnum = mapnum; /* * With the appropriate mapping structure, try the mapping * request again. */ if (mmapobj(fd, mflags, smpp, &mapnum, padding) == -1) { rej->rej_type = SGS_REJ_STR; rej->rej_str = strerror(errno); return (NULL); } } ASSERT(mapnum != 0); /* * Traverse the mappings in search of a file type ld.so.1 can process. * If the file type is verified as one ld.so.1 can process, retain the * mapping information, and the number of mappings this object uses, * and clear the static mapping pointer for the next map_obj() use of * mmapobj(). */ DBG_CALL(Dbg_file_mmapobj(lml, name, smpp, mapnum)); cnt_map++; for (mnum = 0, mpp = smpp; mnum < mapnum; mnum++, mpp++) { uint_t flags = (mpp->mr_flags & MR_TYPE_MASK); Fct *fptr = NULL; if (flags == MR_HDR_ELF) { fptr = elf_verify((mpp->mr_addr + mpp->mr_offset), mpp->mr_fsize, fdp, name, rej); } if (fptr) { fdp->fd_mapn = mapnum; fdp->fd_mapp = smpp; smpp = NULL; return (fptr); } } /* * If the mapped file is inappropriate, indicate that the file type is * unknown, and free the mapping. */ if (rej->rej_type == 0) rej->rej_type = SGS_REJ_UNKFILE; unmap_obj(smpp, mapnum); return (NULL); } /* * A unique file has been opened. Create a link-map to represent it, and * process the various names by which it can be referenced. */ Rt_map * load_file(Lm_list *lml, Aliste lmco, Rt_map *clmp, Fdesc *fdp, int *in_nfavl) { mmapobj_result_t *fpmpp = NULL, *fmpp = NULL, *lpmpp, *lmpp; mmapobj_result_t *hmpp, *mpp, *ompp = fdp->fd_mapp; uint_t mnum, omapnum = fdp->fd_mapn; const char *nname = fdp->fd_nname; Rt_map *nlmp; Ehdr *ehdr = NULL; /* * Traverse the mappings for the input file to capture generic mapping * information, and create a link-map to represent the file. */ for (mnum = 0, mpp = ompp; mnum < omapnum; mnum++, mpp++) { uint_t flags = (mpp->mr_flags & MR_TYPE_MASK); /* * Keep track of the first and last mappings that may include * padding. */ if (fpmpp == NULL) fpmpp = mpp; lpmpp = mpp; /* * Keep track of the first and last mappings that do not include * padding. */ if (flags != MR_PADDING) { if (fmpp == NULL) fmpp = mpp; lmpp = mpp; } if (flags == MR_HDR_ELF) { /* LINTED */ ehdr = (Ehdr *)(mpp->mr_addr + mpp->mr_offset); hmpp = mpp; } } /* * The only ELF files we can handle are ET_EXEC, ET_DYN, and ET_REL. * * ET_REL must be processed by ld(1) to create an in-memory ET_DYN. * The initial processing carried out by elf_obj_file() creates a * temporary link-map, that acts as a place holder, until the objects * processing is finished with elf_obj_fini(). */ if (ehdr && (ehdr->e_type == ET_REL)) { if ((nlmp = elf_obj_file(lml, lmco, clmp, nname, hmpp, ompp, omapnum)) == NULL) return (nlmp); } else { Addr addr; size_t msize; /* * The size of the total reservation, and the padding range, * are a historic artifact required by debuggers. Although * these values express the range of the associated mappings, * there can be holes between segments (in which small objects * could be mapped). Anyone who needs to verify offsets * against segments should analyze all the object mappings, * rather than relying on these address ranges. */ addr = (Addr)(hmpp->mr_addr + hmpp->mr_offset); msize = lmpp->mr_addr + lmpp->mr_msize - fmpp->mr_addr; if ((nlmp = ((fdp->fd_ftp)->fct_new_lmp)(lml, lmco, fdp, addr, msize, NULL, clmp, in_nfavl)) == NULL) return (NULL); /* * Save generic mapping information. */ MMAPS(nlmp) = ompp; MMAPCNT(nlmp) = omapnum; PADSTART(nlmp) = (ulong_t)fpmpp->mr_addr; PADIMLEN(nlmp) = lpmpp->mr_addr + lpmpp->mr_msize - fpmpp->mr_addr; } /* * Save the dev/inode information for later comparisons, and identify * this as a new object. */ STDEV(nlmp) = fdp->fd_dev; STINO(nlmp) = fdp->fd_ino; FLAGS(nlmp) |= FLG_RT_NEWLOAD; /* * If this is ELF relocatable object, we're done for now. */ if (ehdr && (ehdr->e_type == ET_REL)) return (nlmp); /* * Insert the names of this link-map into the FullPathNode AVL tree. * Save both the NAME() and PATHNAME() if the names differ. */ (void) fullpath(nlmp, fdp); if ((NAME(nlmp)[0] == '/') && (fpavl_insert(lml, nlmp, NAME(nlmp), fdp->fd_avlwhere) == 0)) { remove_so(lml, nlmp, clmp); return (NULL); } if (((NAME(nlmp)[0] != '/') || (NAME(nlmp) != PATHNAME(nlmp))) && (fpavl_insert(lml, nlmp, PATHNAME(nlmp), 0) == 0)) { remove_so(lml, nlmp, clmp); return (NULL); } /* * If this is a secure application, record any full path name directory * in which this dependency has been found. This directory can be * deemed safe (as we've already found a dependency here). This * recording provides a fall-back should another objects $ORIGIN * definition expands to this directory, an expansion that would * ordinarily be deemed insecure. */ if (rtld_flags & RT_FL_SECURE) { if (NAME(nlmp)[0] == '/') spavl_insert(NAME(nlmp)); if ((NAME(nlmp) != PATHNAME(nlmp)) && (PATHNAME(nlmp)[0] == '/')) spavl_insert(PATHNAME(nlmp)); } /* * If we're processing an alternative object reset the original name * for possible $ORIGIN processing. */ if (fdp->fd_flags & FLG_FD_ALTER) { const char *odir, *ndir; size_t olen; FLAGS(nlmp) |= FLG_RT_ALTER; /* * If we were given a pathname containing a slash then the * original name is still in oname. Otherwise the original * directory is in dir->p_name (which is all we need for * $ORIGIN). */ if (fdp->fd_flags & FLG_FD_SLASH) { char *ofil; odir = fdp->fd_oname; ofil = strrchr(fdp->fd_oname, '/'); olen = ofil - odir + 1; } else { odir = fdp->fd_odir; olen = strlen(odir) + 1; } if ((ndir = stravl_insert(odir, 0, olen, 1)) == NULL) { remove_so(lml, nlmp, clmp); return (NULL); } ORIGNAME(nlmp) = ndir; DIRSZ(nlmp) = --olen; } return (nlmp); } /* * This function loads the named file and returns a pointer to its link map. * It is assumed that the caller has already checked that the file is not * already loaded before calling this function (refer is_so_loaded()). * Find and open the file, map it into memory, add it to the end of the list * of link maps and return a pointer to the new link map. Return 0 on error. */ static Rt_map * load_so(Lm_list *lml, Aliste lmco, Rt_map *clmp, uint_t flags, Fdesc *fdp, Rej_desc *rej, int *in_nfavl) { const char *oname = fdp->fd_oname; Pdesc *pdp; /* * If this path name hasn't already been identified as containing a * slash, check the path name. Most paths have been constructed * through appending a file name to a search path, and/or have been * inspected by expand(), and thus have a slash. However, we can * receive path names via auditors or configuration files, and thus * an evaluation here catches these instances. */ if ((fdp->fd_flags & FLG_FD_SLASH) == 0) { const char *str; for (str = oname; *str; str++) { if (*str == '/') { fdp->fd_flags |= FLG_FD_SLASH; break; } } } /* * If we are passed a 'null' link-map this means that this is the first * object to be loaded on this link-map list. In that case we set the * link-map to ld.so.1's link-map. * * This link-map is referenced to determine what lookup rules to use * when searching for files. By using ld.so.1's we are defaulting to * ELF look-up rules. * * Note: This case happens when loading the first object onto * the plt_tracing link-map. */ if (clmp == 0) clmp = lml_rtld.lm_head; /* * If this path resulted from a $CAPABILITY specification, then the * best capability object has already been establish, and is available * in the calling file descriptor. Perform some minor book-keeping so * that we can fall through into common code. */ if (flags & FLG_RT_CAP) { /* * If this object is already loaded, we're done. */ if (fdp->fd_lmp) return (fdp->fd_lmp); /* * Obtain the avl index for this object. */ (void) fpavl_recorded(lml, fdp->fd_nname, 0, &(fdp->fd_avlwhere)); } else if (fdp->fd_flags & FLG_FD_SLASH) { Rej_desc _rej = { 0 }; if (find_path(lml, clmp, flags, fdp, &_rej, in_nfavl) == 0) { rejection_inherit(rej, &_rej); return (NULL); } /* * If this object is already loaded, we're done. */ if (fdp->fd_lmp) return (fdp->fd_lmp); } else { /* * No '/' - for each directory on list, make a pathname using * that directory and filename and try to open that file. */ Spath_desc sd = { search_rules, NULL, 0 }; Word strhash = 0; int found = 0; /* * Traverse the search path lists, creating full pathnames and * attempt to load each path. */ for (pdp = get_next_dir(&sd, clmp, flags); pdp; pdp = get_next_dir(&sd, clmp, flags)) { Rej_desc _rej = { 0 }; Fdesc fd = { 0 }; /* * Under debugging, duplicate path name entries are * tagged but remain part of the search path list so * that they can be diagnosed under "unused" processing. * Skip these entries, as this path would have already * been attempted. */ if (pdp->pd_flags & PD_FLG_DUPLICAT) continue; fd = *fdp; /* * Try and locate this file. Make sure to clean up * any rejection information should the file have * been found, but not appropriate. */ if (find_file(lml, clmp, flags, &fd, &_rej, pdp, &strhash, in_nfavl) == 0) { rejection_inherit(rej, &_rej); continue; } /* * Indicate that this search path has been used. If * this is an LD_LIBRARY_PATH setting, ignore any use * by ld.so.1 itself. */ if (((pdp->pd_flags & LA_SER_LIBPATH) == 0) || ((lml->lm_flags & LML_FLG_RTLDLM) == 0)) pdp->pd_flags |= PD_FLG_USED; /* * If this object is already loaded, we're done. */ *fdp = fd; if (fdp->fd_lmp) return (fdp->fd_lmp); fdp->fd_odir = pdp->pd_pname; found = 1; break; } /* * If the file couldn't be loaded, do another comparison of * loaded files using just the basename. This catches folks * who may have loaded multiple full pathname files (possibly * from setxid applications) to satisfy dependency relationships * (i.e., a file might have a dependency on foo.so.1 which has * already been opened using its full pathname). */ if (found == 0) return (is_so_loaded(lml, oname, in_nfavl)); } /* * Trace that this successfully opened file is about to be processed. * Note, as part of processing a family of hardware capabilities filtees * a number of candidates may have been opened and mapped to determine * their capability requirements. At this point we've decided which * of the candidates to use. */ if (lml->lm_flags & LML_FLG_TRC_ENABLE) { trace_so(clmp, 0, fdp->fd_oname, fdp->fd_nname, (fdp->fd_flags & FLG_FD_ALTER), 0); } /* * Finish mapping the file and return the link-map descriptor. */ return (load_file(lml, lmco, clmp, fdp, in_nfavl)); } /* * Trace an attempt to load an object, and seed the originating name. */ const char * load_trace(Lm_list *lml, Pdesc *pdp, Rt_map *clmp, Fdesc *fdp) { const char *name = pdp->pd_pname; DBG_CALL(Dbg_libs_find(lml, name)); /* * First generate any ldd(1) diagnostics. */ if ((lml->lm_flags & (LML_FLG_TRC_VERBOSE | LML_FLG_TRC_SEARCH)) && ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0)) (void) printf(MSG_INTL(MSG_LDD_FIL_FIND), name, NAME(clmp)); /* * Propagate any knowledge of a slash within the path name. */ if (pdp->pd_flags & PD_FLG_PNSLASH) fdp->fd_flags |= FLG_FD_SLASH; /* * If we're being audited tell any audit libraries of the file we're * about to go search for. */ if (aud_activity || ((lml->lm_tflags | AFLAGS(clmp)) & LML_TFLG_AUD_ACTIVITY)) audit_activity(clmp, LA_ACT_ADD); if ((lml->lm_tflags | AFLAGS(clmp)) & LML_TFLG_AUD_OBJSEARCH) { char *aname; /* * The auditor can indicate that this object should be ignored. */ if ((aname = audit_objsearch(clmp, name, LA_SER_ORIG)) == NULL) { DBG_CALL(Dbg_audit_terminate(lml, name)); return (NULL); } if (name != aname) { fdp->fd_flags &= ~FLG_FD_SLASH; name = aname; } } fdp->fd_oname = name; return (name); } /* * Having loaded an object and created a link-map to describe it, finish * processing this stage, including verifying any versioning requirements, * updating the objects mode, creating a handle if necessary, and adding this * object to existing handles if required. */ static int load_finish(Lm_list *lml, const char *name, Rt_map *clmp, int nmode, uint_t flags, Grp_hdl **hdl, Rt_map *nlmp) { Aliste idx1; Grp_hdl *ghp; int promote; uint_t rdflags; /* * If this dependency is associated with a required version ensure that * the version is present in the loaded file. */ if (((rtld_flags & RT_FL_NOVERSION) == 0) && THIS_IS_ELF(clmp) && VERNEED(clmp) && (elf_verify_vers(name, clmp, nlmp) == 0)) return (0); /* * If this object has indicated that it should be isolated as a group * (DT_FLAGS_1 contains DF_1_GROUP - object was built with -B group), * or if the callers direct bindings indicate it should be isolated as * a group (DYNINFO flags contains FLG_DI_GROUP - dependency following * -zgroupperm), establish the appropriate mode. * * The intent of an object defining itself as a group is to isolate the * relocation of the group within its own members, however, unless * opened through dlopen(), in which case we assume dlsym() will be used * to locate symbols in the new object, we still need to associate the * new object with the caller so that the caller can bind to this new * object. This is equivalent to a dlopen(RTLD_GROUP) and dlsym() * using the returned handle. */ if ((FLAGS(nlmp) | flags) & FLG_RT_SETGROUP) { nmode &= ~RTLD_WORLD; nmode |= RTLD_GROUP; /* * If the object wasn't explicitly dlopen()'ed, in which case a * handle would have been requested, associate the object with * the parent. */ if ((flags & FLG_RT_PUBHDL) == 0) nmode |= RTLD_PARENT; } /* * Establish new mode and flags. */ promote = update_mode(nlmp, MODE(nlmp), nmode); FLAGS(nlmp) |= flags; /* * Establish the flags for any referenced dependency descriptors * (Grp_desc). * * - The referenced object is available for dlsym(). * - The referenced object is available to relocate against. * - The referenced object should have it's dependencies * added to this handle */ rdflags = (GPD_DLSYM | GPD_RELOC | GPD_ADDEPS); /* * If we've been asked to establish a handle create one for this object. * Or, if this object has already been analyzed, but this reference * requires that the mode of the object be promoted, create a private * handle to propagate the new modes to all this objects dependencies. */ if ((FLAGS(nlmp) & (FLG_RT_PUBHDL | FLG_RT_PRIHDL)) || (promote && (FLAGS(nlmp) & FLG_RT_ANALYZED))) { uint_t oflags, hflags, cdflags = 0; /* * Establish any flags for the handle (Grp_hdl). * * - Public handles establish dependencies between objects * that must be taken into account when dlclose()'ing * objects. Private handles provide for collecting * dependencies, but do not affect dlclose(). Note that * a handle may already exist, but the public/private * state is set to trigger the required propagation of the * handle's flags and any dependency gathering. * - Use of the RTLD_FIRST flag indicates that only the first * dependency on the handle (the new object) can be used * to satisfy dlsym() requests. */ if (FLAGS(nlmp) & FLG_RT_PUBHDL) hflags = GPH_PUBLIC; else hflags = GPH_PRIVATE; if (nmode & RTLD_FIRST) hflags |= GPH_FIRST; /* * Establish the flags for this callers dependency descriptor * (Grp_desc). * * - The creation of a public handle creates a descriptor * for the referenced object and the caller (parent). * Typically, the handle is created for dlopen() or for * filtering. A private handle does not need to maintain * a descriptor to the parent. * - Use of the RTLD_PARENT flag indicates that the parent * can be relocated against. */ if (FLAGS(nlmp) & FLG_RT_PUBHDL) { cdflags |= GPD_PARENT; if (nmode & RTLD_PARENT) cdflags |= GPD_RELOC; } /* * Now that the handle flags have been established, remove any * handle definition from the referenced object so that the * definitions don't mistakenly get inherited by a dependency. */ oflags = FLAGS(nlmp); FLAGS(nlmp) &= ~(FLG_RT_PUBHDL | FLG_RT_PRIHDL); DBG_CALL(Dbg_file_hdl_title(DBG_HDL_ADD)); if ((ghp = hdl_create(lml, nlmp, clmp, hflags, rdflags, cdflags)) == NULL) return (0); /* * Add any dependencies that are already loaded, to the handle. */ if (hdl_initialize(ghp, nlmp, nmode, promote) == 0) return (0); if (hdl) *hdl = ghp; /* * If we were asked to create a public handle, we're done. * * If this is a private handle request, then the handle is left * intact with a GPH_PRIVATE identifier. This handle is a * convenience for processing the dependencies of this object, * but does not affect how this object might be dlclose()'d. * For a private handle, fall through to carry out any group * processing. */ if (oflags & FLG_RT_PUBHDL) return (1); } /* * If the caller isn't part of a group we're done. */ if (GROUPS(clmp) == NULL) return (1); /* * Determine if our caller is already associated with a handle, if so * we need to add this object to any handles that already exist. * Traverse the list of groups our caller is a member of and add this * new link-map to those groups. */ for (APLIST_TRAVERSE(GROUPS(clmp), idx1, ghp)) { Aliste idx2; Grp_desc *gdp; int ale; Rt_map *dlmp1; APlist *lmalp = NULL; DBG_CALL(Dbg_file_hdl_title(DBG_HDL_ADD)); /* * If the caller doesn't indicate that its dependencies should * be added to a handle, ignore it. This case identifies a * parent of a dlopen(RTLD_PARENT) request. */ for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { if (gdp->gd_depend == clmp) break; } if ((gdp->gd_flags & GPD_ADDEPS) == 0) continue; if ((gdp = hdl_add(ghp, nlmp, rdflags, &ale)) == NULL) return (0); /* * If this member already exists then its dependencies will * have already been processed. */ if (ale == ALE_EXISTS) continue; /* * If the object we've added has just been opened, it will not * yet have been processed for its dependencies, these will be * added on later calls to load_one(). If it doesn't have any * dependencies we're also done. */ if (((FLAGS(nlmp) & FLG_RT_ANALYZED) == 0) || (DEPENDS(nlmp) == NULL)) continue; /* * Otherwise, this object exists and has dependencies, so add * all of its dependencies to the handle were operating on. */ if (aplist_append(&lmalp, nlmp, AL_CNT_DEPCLCT) == NULL) return (0); for (APLIST_TRAVERSE(lmalp, idx2, dlmp1)) { Aliste idx3; Bnd_desc *bdp; /* * Add any dependencies of this dependency to the * dynamic dependency list so they can be further * processed. */ for (APLIST_TRAVERSE(DEPENDS(dlmp1), idx3, bdp)) { Rt_map *dlmp2 = bdp->b_depend; if ((bdp->b_flags & BND_NEEDED) == 0) continue; if (aplist_test(&lmalp, dlmp2, AL_CNT_DEPCLCT) == 0) { free(lmalp); return (0); } } if (nlmp == dlmp1) continue; if ((gdp = hdl_add(ghp, dlmp1, rdflags, &ale)) == NULL) { free(lmalp); return (0); } if (ale == ALE_CREATE) (void) update_mode(dlmp1, MODE(dlmp1), nmode); } free(lmalp); } return (1); } /* * The central routine for loading shared objects. Insures ldd() diagnostics, * handle creation, and any other related additions are all done in one place. */ Rt_map * load_path(Lm_list *lml, Aliste lmco, Rt_map *clmp, int nmode, uint_t flags, Grp_hdl **hdl, Fdesc *fdp, Rej_desc *rej, int *in_nfavl) { const char *name = fdp->fd_oname; Rt_map *nlmp; if ((nmode & RTLD_NOLOAD) == 0) { int oin_nfavl; /* * Keep track of the number of not-found loads. */ if (in_nfavl) oin_nfavl = *in_nfavl; /* * If this isn't a noload request attempt to load the file. */ if ((nlmp = load_so(lml, lmco, clmp, flags, fdp, rej, in_nfavl)) == NULL) return (NULL); /* * If this file has been found, reset the not-found load count. * Although a search for this file might have inspected a number * of non-existent path names, the file has been found so there * is no need to accumulate a non-found count, as this may * trigger unnecessary fall back (retry) processing. */ if (in_nfavl) *in_nfavl = oin_nfavl; /* * If we've loaded a library which identifies itself as not * being dlopen()'able catch it here. Let non-dlopen()'able * objects through under RTLD_CONFGEN as they're only being * mapped to be dldump()'ed. */ if ((rtld_flags & RT_FL_APPLIC) && ((FLAGS(nlmp) & (FLG_RT_NOOPEN | FLG_RT_RELOCED)) == FLG_RT_NOOPEN) && ((nmode & RTLD_CONFGEN) == 0)) { Rej_desc _rej = { 0 }; _rej.rej_name = name; _rej.rej_type = SGS_REJ_STR; _rej.rej_str = MSG_INTL(MSG_GEN_NOOPEN); DBG_CALL(Dbg_file_rejected(lml, &_rej, M_MACH)); rejection_inherit(rej, &_rej); remove_so(lml, nlmp, clmp); return (NULL); } } else { /* * If it's a NOLOAD request - check to see if the object * has already been loaded. */ nlmp = is_so_loaded(lml, name, in_nfavl); if (nlmp != NULL) { if ((lml->lm_flags & LML_FLG_TRC_VERBOSE) && ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0)) { (void) printf(MSG_INTL(MSG_LDD_FIL_FIND), name, NAME(clmp)); /* BEGIN CSTYLED */ if (*name == '/') (void) printf(MSG_ORIG(MSG_LDD_FIL_PATH), name, MSG_ORIG(MSG_STR_EMPTY), MSG_ORIG(MSG_STR_EMPTY)); else (void) printf(MSG_ORIG(MSG_LDD_FIL_EQUIV), name, NAME(nlmp), MSG_ORIG(MSG_STR_EMPTY), MSG_ORIG(MSG_STR_EMPTY)); /* END CSTYLED */ } } else { Rej_desc _rej = { 0 }; _rej.rej_name = name; _rej.rej_type = SGS_REJ_STR; _rej.rej_str = strerror(ENOENT); DBG_CALL(Dbg_file_rejected(lml, &_rej, M_MACH)); rejection_inherit(rej, &_rej); return (NULL); } } /* * Finish processing this loaded object. */ if (load_finish(lml, name, clmp, nmode, flags, hdl, nlmp) == 0) { FLAGS(nlmp) &= ~FLG_RT_NEWLOAD; /* * If this object has already been analyzed, then it is in use, * so even though this operation has failed, it should not be * torn down. */ if ((FLAGS(nlmp) & FLG_RT_ANALYZED) == 0) remove_so(lml, nlmp, clmp); return (NULL); } /* * If this object is new, and we're being audited, tell the audit * libraries of the file we've just opened. Note, if the new link-map * requires local auditing of its dependencies we also register its * opening. */ if (FLAGS(nlmp) & FLG_RT_NEWLOAD) { FLAGS(nlmp) &= ~FLG_RT_NEWLOAD; if ((lml->lm_tflags | AFLAGS(clmp) | AFLAGS(nlmp)) & LML_TFLG_AUD_MASK) { if (audit_objopen(clmp, nlmp) == 0) { remove_so(lml, nlmp, clmp); return (NULL); } } } return (nlmp); } /* * Load one object from a possible list of objects. Typically, for requests * such as NEEDED's, only one object is specified. However, this object could * be specified using $ISALIST or $CAPABILITY, in which case only the first * object that can be loaded is used (ie. the best). */ Rt_map * load_one(Lm_list *lml, Aliste lmco, Alist *palp, Rt_map *clmp, int mode, uint_t flags, Grp_hdl **hdl, int *in_nfavl) { Rej_desc rej = { 0 }; Aliste idx; Pdesc *pdp; const char *name; for (ALIST_TRAVERSE(palp, idx, pdp)) { Rt_map *lmp = NULL; /* * A $CAPABILITY/$HWCAP requirement can expand into a number of * candidates. */ if (pdp->pd_flags & PD_TKN_CAP) { lmp = load_cap(lml, lmco, pdp->pd_pname, clmp, mode, (flags | FLG_RT_CAP), hdl, &rej, in_nfavl); } else { Fdesc fd = { 0 }; /* * Trace the inspection of this file, determine any * auditor substitution, and seed the file descriptor * with the originating name. */ if (load_trace(lml, pdp, clmp, &fd) == NULL) continue; /* * Locate and load the file. */ lmp = load_path(lml, lmco, clmp, mode, flags, hdl, &fd, &rej, in_nfavl); } if (lmp) return (lmp); } /* * If no objects can be found, use the first path name from the Alist * to provide a diagnostic. If this pathname originated from an * expanded token, use the original name for any diagnostic output. */ pdp = alist_item(palp, 0); if ((name = pdp->pd_oname) == 0) name = pdp->pd_pname; file_notfound(lml, name, clmp, flags, &rej); return (NULL); } /* * Determine whether a symbol is defined as an interposer. */ int is_sym_interposer(Rt_map *lmp, Sym *sym) { Syminfo *sip = SYMINFO(lmp); if (sip) { ulong_t ndx; ndx = (((ulong_t)sym - (ulong_t)SYMTAB(lmp)) / SYMENT(lmp)); /* LINTED */ sip = (Syminfo *)((char *)sip + (ndx * SYMINENT(lmp))); if (sip->si_flags & SYMINFO_FLG_INTERPOSE) return (1); } return (0); } /* * While processing direct or group bindings, determine whether the object to * which we've bound can be interposed upon. In this context, copy relocations * are a form of interposition. */ static int lookup_sym_interpose(Slookup *slp, Sresult *srp, uint_t *binfo, int *in_nfavl) { Rt_map *lmp, *clmp, *dlmp = srp->sr_dmap; Sym *osym = srp->sr_sym; Slookup sl; Lm_list *lml; /* * If we've bound to a copy relocation definition then we need to assign * this binding to the original copy reference. Fabricate an inter- * position diagnostic, as this is a legitimate form of interposition. */ if (osym && (FLAGS1(dlmp) & FL1_RT_COPYTOOK)) { Rel_copy *rcp; Aliste idx; for (ALIST_TRAVERSE(COPY_R(dlmp), idx, rcp)) { if ((osym == rcp->r_dsym) || (osym->st_value && (osym->st_value == rcp->r_dsym->st_value))) { srp->sr_dmap = rcp->r_rlmp; srp->sr_sym = rcp->r_rsym; *binfo |= (DBG_BINFO_INTERPOSE | DBG_BINFO_COPYREF); return (1); } } } /* * If a symbol binding has been established, inspect the link-map list * of the destination object, otherwise use the link-map list of the * original caller. */ if (osym) clmp = dlmp; else clmp = slp->sl_cmap; lml = LIST(clmp); lmp = lml->lm_head; /* * Prior to Solaris 8, external references from an executable that were * bound to an uninitialized variable (.bss) within a shared object did * not establish a copy relocation. This was thought to be an * optimization, to prevent copying zero's to zero's. Typically, * interposition took its course, with the shared object binding to the * executables data definition. * * This scenario can be broken when this old executable runs against a * new shared object that is directly bound. With no copy-relocation * record, ld.so.1 has no data to trigger the normal vectoring of the * binding to the executable. * * Starting with Solaris 8, a DT_FLAGS entry is written to all objects, * regardless of there being any DF_ flags entries. Therefore, an * object without this dynamic tag is susceptible to the copy relocation * issue. If the executable has no DT_FLAGS tag, and contains the same * .bss symbol definition as has been directly bound to, redirect the * binding to the executables data definition. */ if (osym && ((FLAGS1(lmp) & FL1_RT_DTFLAGS) == 0) && (FCT(lmp) == &elf_fct) && (ELF_ST_TYPE(osym->st_info) != STT_FUNC) && are_bits_zero(dlmp, osym, 0)) { Sresult sr; /* * Initialize a local symbol result descriptor, using the * original symbol name. Initialize a local symbol lookup * descriptor, using the original lookup information, and a * new initial link-map. */ SRESULT_INIT(sr, slp->sl_name); sl = *slp; sl.sl_imap = lmp; /* * Determine whether the same symbol name exists within the * executable, that the size and type of symbol are the same, * and that the symbol is also associated with .bss. */ if (SYMINTP(lmp)(&sl, &sr, binfo, in_nfavl)) { Sym *isym = sr.sr_sym; if ((isym->st_size == osym->st_size) && (isym->st_info == osym->st_info) && are_bits_zero(lmp, isym, 1)) { *srp = sr; *binfo |= (DBG_BINFO_INTERPOSE | DBG_BINFO_COPYREF); return (1); } } } if ((lml->lm_flags & LML_FLG_INTRPOSE) == 0) return (0); /* * Traverse the list of known interposers to determine whether any * offer the same symbol. Note, the head of the link-map could be * identified as an interposer. Otherwise, skip the head of the * link-map, so that we don't bind to any .plt references, or * copy-relocation destinations unintentionally. */ lmp = lml->lm_head; sl = *slp; if (((FLAGS(lmp) & MSK_RT_INTPOSE) == 0) || (sl.sl_flags & LKUP_COPY)) lmp = NEXT_RT_MAP(lmp); for (; lmp; lmp = NEXT_RT_MAP(lmp)) { if (FLAGS(lmp) & FLG_RT_DELETE) continue; if ((FLAGS(lmp) & MSK_RT_INTPOSE) == 0) break; /* * If we had already bound to this object, there's no point in * searching it again, we're done. */ if (lmp == dlmp) break; /* * If this interposer can be inspected by the caller, look for * the symbol within the interposer. */ if (callable(clmp, lmp, 0, sl.sl_flags)) { Sresult sr; /* * Initialize a local symbol result descriptor, using * the original symbol name. Initialize a local symbol * lookup descriptor, using the original lookup * information, and a new initial link-map. */ SRESULT_INIT(sr, slp->sl_name); sl.sl_imap = lmp; if (SYMINTP(lmp)(&sl, &sr, binfo, in_nfavl)) { Sym *isym = sr.sr_sym; Rt_map *ilmp = sr.sr_dmap; /* * If this object provides individual symbol * interposers, make sure that the symbol we * have found is tagged as an interposer. */ if ((FLAGS(ilmp) & FLG_RT_SYMINTPO) && (is_sym_interposer(ilmp, isym) == 0)) continue; /* * Indicate this binding has occurred to an * interposer, and return the symbol. */ *srp = sr; *binfo |= DBG_BINFO_INTERPOSE; return (1); } } } return (0); } /* * If an object specifies direct bindings (it contains a syminfo structure * describing where each binding was established during link-editing, and the * object was built -Bdirect), then look for the symbol in the specific object. */ static int lookup_sym_direct(Slookup *slp, Sresult *srp, uint_t *binfo, Syminfo *sip, Rt_map *lmp, int *in_nfavl) { Rt_map *dlmp, *clmp = slp->sl_cmap; int ret; Slookup sl; /* * If a direct binding resolves to the definition of a copy relocated * variable, it must be redirected to the copy (in the executable) that * will eventually be made. Typically, this redirection occurs in * lookup_sym_interpose(). But, there's an edge condition. If a * directly bound executable contains pic code, there may be a * reference to a definition that will eventually have a copy made. * However, this copy relocation may not yet have occurred, because * the relocation making this reference comes before the relocation * that will create the copy. * Under direct bindings, the syminfo indicates that a copy will be * taken (SYMINFO_FLG_COPY). This can only be set in an executable. * Thus, the caller must be the executable, so bind to the destination * of the copy within the executable. */ if (((slp->sl_flags & LKUP_COPY) == 0) && (sip->si_flags & SYMINFO_FLG_COPY)) { slp->sl_imap = LIST(clmp)->lm_head; ret = SYMINTP(clmp)(slp, srp, binfo, in_nfavl); if (ret != 0) *binfo |= (DBG_BINFO_DIRECT | DBG_BINFO_COPYREF); return (ret); } /* * If we need to directly bind to our parent, start looking in each * callers link map. */ sl = *slp; sl.sl_flags |= LKUP_DIRECT; ret = 0; if (sip->si_boundto == SYMINFO_BT_PARENT) { Aliste idx1; Bnd_desc *bdp; Grp_hdl *ghp; /* * Determine the parent of this explicit dependency from its * CALLERS()'s list. */ for (APLIST_TRAVERSE(CALLERS(clmp), idx1, bdp)) { sl.sl_imap = lmp = bdp->b_caller; ret = SYMINTP(lmp)(&sl, srp, binfo, in_nfavl); if (ret != 0) goto found; } /* * A caller can also be defined as the parent of a dlopen() * call. Determine whether this object has any handles. The * dependencies maintained with the handle represent the * explicit dependencies of the dlopen()'ed object, and the * calling parent. */ for (APLIST_TRAVERSE(HANDLES(clmp), idx1, ghp)) { Grp_desc *gdp; Aliste idx2; for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { if ((gdp->gd_flags & GPD_PARENT) == 0) continue; sl.sl_imap = lmp = gdp->gd_depend; ret = SYMINTP(lmp)(&sl, srp, binfo, in_nfavl); if (ret != 0) goto found; } } } else { /* * If we need to direct bind to anything else look in the * link map associated with this symbol reference. */ if (sip->si_boundto == SYMINFO_BT_SELF) sl.sl_imap = lmp = clmp; else sl.sl_imap = lmp; if (lmp) ret = SYMINTP(lmp)(&sl, srp, binfo, in_nfavl); } found: if (ret) *binfo |= DBG_BINFO_DIRECT; /* * If a reference to a directly bound symbol can't be satisfied, then * determine whether an interposer can provide the missing symbol. If * a reference to a directly bound symbol is satisfied, then determine * whether that object can be interposed upon for this symbol. */ dlmp = srp->sr_dmap; if ((ret == 0) || (dlmp && (LIST(dlmp)->lm_head != dlmp) && (LIST(dlmp) == LIST(clmp)))) { if (lookup_sym_interpose(slp, srp, binfo, in_nfavl)) return (1); } return (ret); } static int core_lookup_sym(Rt_map *ilmp, Slookup *slp, Sresult *srp, uint_t *binfo, Aliste off, int *in_nfavl) { Rt_map *lmp; /* * Copy relocations should start their search after the head of the * main link-map control list. */ if ((off == ALIST_OFF_DATA) && (slp->sl_flags & LKUP_COPY) && ilmp) lmp = NEXT_RT_MAP(ilmp); else lmp = ilmp; for (; lmp; lmp = NEXT_RT_MAP(lmp)) { if (callable(slp->sl_cmap, lmp, 0, slp->sl_flags)) { slp->sl_imap = lmp; if ((SYMINTP(lmp)(slp, srp, binfo, in_nfavl)) || (*binfo & BINFO_MSK_TRYAGAIN)) return (1); } } return (0); } static int rescan_lazy_find_sym(Rt_map *ilmp, Slookup *slp, Sresult *srp, uint_t *binfo, int *in_nfavl) { Rt_map *lmp; for (lmp = ilmp; lmp; lmp = NEXT_RT_MAP(lmp)) { if (LAZY(lmp) == 0) continue; if (callable(slp->sl_cmap, lmp, 0, slp->sl_flags)) { slp->sl_imap = lmp; if (elf_lazy_find_sym(slp, srp, binfo, in_nfavl)) return (1); } } return (0); } static int _lookup_sym(Slookup *slp, Sresult *srp, uint_t *binfo, int *in_nfavl) { const char *name = slp->sl_name; Rt_map *clmp = slp->sl_cmap; Lm_list *lml = LIST(clmp); Rt_map *ilmp = slp->sl_imap, *lmp; ulong_t rsymndx; int ret; Syminfo *sip; Slookup sl; /* * Search the initial link map for the required symbol (this category is * selected by dlsym(), where individual link maps are searched for a * required symbol. Therefore, we know we have permission to look at * the link map). */ if (slp->sl_flags & LKUP_FIRST) return (SYMINTP(ilmp)(slp, srp, binfo, in_nfavl)); /* * Determine whether this lookup can be satisfied by an objects direct, * or lazy binding information. This is triggered by a relocation from * the object (hence rsymndx is set). */ if (((rsymndx = slp->sl_rsymndx) != 0) && ((sip = SYMINFO(clmp)) != NULL)) { uint_t bound; /* * Find the corresponding Syminfo entry for the original * referencing symbol. */ /* LINTED */ sip = (Syminfo *)((char *)sip + (rsymndx * SYMINENT(clmp))); bound = sip->si_boundto; /* * Identify any EXTERN or PARENT references for ldd(1). */ if ((lml->lm_flags & LML_FLG_TRC_WARN) && (bound > SYMINFO_BT_LOWRESERVE)) { if (bound == SYMINFO_BT_PARENT) *binfo |= DBG_BINFO_REF_PARENT; if (bound == SYMINFO_BT_EXTERN) *binfo |= DBG_BINFO_REF_EXTERN; } /* * If the symbol information indicates a direct binding, * determine the link map that is required to satisfy the * binding. Note, if the dependency can not be found, but a * direct binding isn't required, we will still fall through * to perform any default symbol search. */ if (sip->si_flags & SYMINFO_FLG_DIRECT) { lmp = 0; if (bound < SYMINFO_BT_LOWRESERVE) lmp = elf_lazy_load(clmp, slp, bound, name, 0, NULL, in_nfavl); /* * If direct bindings have been disabled, and this isn't * a translator, skip any direct binding now that we've * ensured the resolving object has been loaded. * * If we need to direct bind to anything, we look in * ourselves, our parent, or in the link map we've just * loaded. Otherwise, even though we may have lazily * loaded an object we still continue to search for * symbols from the head of the link map list. */ if (((FLAGS(clmp) & FLG_RT_TRANS) || (((lml->lm_tflags & LML_TFLG_NODIRECT) == 0) && ((slp->sl_flags & LKUP_SINGLETON) == 0))) && ((FLAGS1(clmp) & FL1_RT_DIRECT) || (sip->si_flags & SYMINFO_FLG_DIRECTBIND))) { ret = lookup_sym_direct(slp, srp, binfo, sip, lmp, in_nfavl); /* * Determine whether this direct binding has * been rejected. If we've bound to a singleton * without following a singleton search, then * return. The caller detects this condition * and will trigger a new singleton search. * * For any other rejection (such as binding to * a symbol labeled as nodirect - presumably * because the symbol definition has been * changed since the referring object was last * built), fall through to a standard symbol * search. */ if (((*binfo & BINFO_MSK_REJECTED) == 0) || (*binfo & BINFO_MSK_TRYAGAIN)) return (ret); *binfo &= ~BINFO_MSK_REJECTED; } } } /* * Duplicate the lookup information, as we'll need to modify this * information for some of the following searches. */ sl = *slp; /* * If the referencing object has the DF_SYMBOLIC flag set, look in the * referencing object for the symbol first. Failing that, fall back to * our generic search. */ if ((FLAGS1(clmp) & FL1_RT_SYMBOLIC) && ((sl.sl_flags & LKUP_SINGLETON) == 0)) { sl.sl_imap = clmp; if (SYMINTP(clmp)(&sl, srp, binfo, in_nfavl)) { Rt_map *dlmp = srp->sr_dmap; ulong_t dsymndx = (((ulong_t)srp->sr_sym - (ulong_t)SYMTAB(dlmp)) / SYMENT(dlmp)); /* * Make sure this symbol hasn't explicitly been defined * as nodirect. */ if (((sip = SYMINFO(dlmp)) == 0) || /* LINTED */ ((sip = (Syminfo *)((char *)sip + (dsymndx * SYMINENT(dlmp)))) == 0) || ((sip->si_flags & SYMINFO_FLG_NOEXTDIRECT) == 0)) return (1); } } sl.sl_flags |= LKUP_STANDARD; /* * If this lookup originates from a standard relocation, then traverse * all link-map control lists, inspecting any object that is available * to this caller. Otherwise, traverse the link-map control list * associated with the caller. */ if (sl.sl_flags & LKUP_STDRELOC) { Aliste off; Lm_cntl *lmc; ret = 0; for (ALIST_TRAVERSE_BY_OFFSET(lml->lm_lists, off, lmc)) { if (((ret = core_lookup_sym(lmc->lc_head, &sl, srp, binfo, off, in_nfavl)) != 0) || (*binfo & BINFO_MSK_TRYAGAIN)) break; } } else ret = core_lookup_sym(ilmp, &sl, srp, binfo, ALIST_OFF_DATA, in_nfavl); /* * If a symbol binding should be retried, return so that the search can * be repeated. */ if (*binfo & BINFO_MSK_TRYAGAIN) return (0); /* * To allow transitioning into a world of lazy loading dependencies see * if this link map contains objects that have lazy dependencies still * outstanding. If so, and we haven't been able to locate a non-weak * symbol reference, start bringing in any lazy dependencies to see if * the reference can be satisfied. Use of dlsym(RTLD_PROBE) sets the * LKUP_NOFALLBACK flag, and this flag disables this fall back. */ if ((ret == 0) && ((sl.sl_flags & LKUP_NOFALLBACK) == 0)) { if ((lmp = ilmp) == 0) lmp = LIST(clmp)->lm_head; lml = LIST(lmp); if ((sl.sl_flags & LKUP_WEAK) || (lml->lm_lazy == 0)) return (0); DBG_CALL(Dbg_syms_lazy_rescan(lml, name)); /* * If this request originated from a dlsym(RTLD_NEXT) then start * looking for dependencies from the caller, otherwise use the * initial link-map. */ if (sl.sl_flags & LKUP_NEXT) ret = rescan_lazy_find_sym(clmp, &sl, srp, binfo, in_nfavl); else { Aliste idx; Lm_cntl *lmc; for (ALIST_TRAVERSE(lml->lm_lists, idx, lmc)) { sl.sl_flags |= LKUP_NOFALLBACK; ret = rescan_lazy_find_sym(lmc->lc_head, &sl, srp, binfo, in_nfavl); if (ret != 0) break; } } } return (ret); } /* * Symbol lookup routine. Takes an ELF symbol name, and a list of link maps to * search. If successful, return a pointer to the symbol table entry, a * pointer to the link map of the enclosing object, and information relating * to the type of binding. Else return a null pointer. */ int lookup_sym(Slookup *slp, Sresult *srp, uint_t *binfo, int *in_nfavl) { Rt_map *clmp = slp->sl_cmap; Sym *rsym = slp->sl_rsym; uchar_t rtype = slp->sl_rtype, vis; int ret, mode; if (slp->sl_hash == 0) slp->sl_hash = elf_hash(slp->sl_name); *binfo = 0; if (rsym) { vis = ELF_ST_VISIBILITY(rsym->st_other); /* * Symbols that are defined as protected, or hidden, within an * object usually have any relocation references from within * the same object bound at link-edit time. Therefore, ld.so.1 * is not involved. However, if a reference is to a * capabilities symbol, this reference must be resolved at * runtime. In this case look directly within the calling * object, and only within the calling object, for these * symbols. Note, an object may still use dlsym() to search * externally for a symbol which is defined as protected within * the same object. */ if ((rsym->st_shndx != SHN_UNDEF) && ((slp->sl_flags & LKUP_DLSYM) == 0) && ((vis == STV_PROTECTED) || (vis == STV_HIDDEN))) { slp->sl_imap = clmp; return (SYMINTP(clmp)(slp, srp, binfo, in_nfavl)); } /* * Establish any state that might be associated with a symbol * reference. */ if ((slp->sl_flags & LKUP_STDRELOC) && (ELF_ST_BIND(rsym->st_info) == STB_WEAK)) slp->sl_flags |= LKUP_WEAK; if (vis == STV_SINGLETON) slp->sl_flags |= LKUP_SINGLETON; } /* * Establish any lookup state required for this type of relocation. */ if ((slp->sl_flags & LKUP_STDRELOC) && rtype) { if (rtype == M_R_COPY) slp->sl_flags |= LKUP_COPY; if (rtype != M_R_JMP_SLOT) slp->sl_flags |= LKUP_SPEC; } /* * Under ldd -w, any unresolved weak references are diagnosed. Set the * symbol binding as global to trigger a relocation error if the symbol * can not be found. */ if (rsym) { if (LIST(slp->sl_cmap)->lm_flags & LML_FLG_TRC_NOUNRESWEAK) slp->sl_bind = STB_GLOBAL; else if ((slp->sl_bind = ELF_ST_BIND(rsym->st_info)) == STB_WEAK) slp->sl_flags |= LKUP_WEAK; } /* * Save the callers MODE(). */ mode = MODE(clmp); /* * Carry out an initial symbol search. This search takes into account * all the modes of the requested search. */ if (((ret = _lookup_sym(slp, srp, binfo, in_nfavl)) == 0) && (*binfo & BINFO_MSK_TRYAGAIN)) { Slookup sl = *slp; /* * Try the symbol search again. This retry can be necessary if: * * - a binding has been rejected because of binding to a * singleton without going through a singleton search. * - a group binding has resulted in binding to a symbol * that indicates no-direct binding. * * Reset the lookup data, and try again. */ sl.sl_imap = LIST(sl.sl_cmap)->lm_head; sl.sl_flags &= ~(LKUP_FIRST | LKUP_SELF | LKUP_NEXT); sl.sl_rsymndx = 0; if (*binfo & BINFO_REJSINGLE) sl.sl_flags |= LKUP_SINGLETON; if (*binfo & BINFO_REJGROUP) { sl.sl_flags |= LKUP_WORLD; mode |= RTLD_WORLD; } *binfo &= ~BINFO_MSK_REJECTED; ret = _lookup_sym(&sl, srp, binfo, in_nfavl); } /* * If the caller is restricted to a symbol search within its group, * determine if it is necessary to follow a binding from outside of * the group. */ if (((mode & (RTLD_GROUP | RTLD_WORLD)) == RTLD_GROUP) && (lookup_sym_interpose(slp, srp, binfo, in_nfavl))) return (1); return (ret); } /* * Associate a binding descriptor with a caller and its dependency, or update * an existing descriptor. */ int bind_one(Rt_map *clmp, Rt_map *dlmp, uint_t flags) { Bnd_desc *bdp; Aliste idx; int found = ALE_CREATE; /* * Determine whether a binding descriptor already exists between the * two objects. */ for (APLIST_TRAVERSE(DEPENDS(clmp), idx, bdp)) { if (bdp->b_depend == dlmp) { found = ALE_EXISTS; break; } } if (found == ALE_CREATE) { /* * Create a new binding descriptor. */ if ((bdp = malloc(sizeof (Bnd_desc))) == NULL) return (0); bdp->b_caller = clmp; bdp->b_depend = dlmp; bdp->b_flags = 0; /* * Append the binding descriptor to the caller and the * dependency. */ if (aplist_append(&DEPENDS(clmp), bdp, AL_CNT_DEPENDS) == NULL) return (0); if (aplist_append(&CALLERS(dlmp), bdp, AL_CNT_CALLERS) == NULL) return (0); } if ((found == ALE_CREATE) || ((bdp->b_flags & flags) != flags)) { bdp->b_flags |= flags; if (flags & BND_REFER) FLAGS1(dlmp) |= FL1_RT_USED; DBG_CALL(Dbg_file_bind_entry(LIST(clmp), bdp)); } return (found); } /* * Cleanup after relocation processing. */ int relocate_finish(Rt_map *lmp, APlist *bound, int ret) { DBG_CALL(Dbg_reloc_run(lmp, 0, ret, DBG_REL_FINISH)); /* * Establish bindings to all objects that have been bound to. */ if (bound) { Rt_map *_lmp; Word used; /* * Only create bindings if the callers relocation was * successful (ret != 0), otherwise the object will eventually * be torn down. Create these bindings if running under ldd(1) * with the -U/-u options regardless of relocation errors, as * the unused processing needs to traverse these bindings to * diagnose unused objects. */ used = LIST(lmp)->lm_flags & (LML_FLG_TRC_UNREF | LML_FLG_TRC_UNUSED); if (ret || used) { Aliste idx; for (APLIST_TRAVERSE(bound, idx, _lmp)) { if (bind_one(lmp, _lmp, BND_REFER) || used) continue; ret = 0; break; } } free(bound); } return (ret); } /* * Function to correct protection settings. Segments are all mapped initially * with permissions as given in the segment header. We need to turn on write * permissions on a text segment if there are any relocations against that * segment, and then turn write permission back off again before returning * control to the caller. This function turns the permission on or off * depending on the value of the permission argument. */ int set_prot(Rt_map *lmp, mmapobj_result_t *mpp, int perm) { int prot; /* * If this is an allocated image (ie. a relocatable object) we can't * mprotect() anything. */ if (FLAGS(lmp) & FLG_RT_IMGALLOC) return (1); DBG_CALL(Dbg_file_prot(lmp, perm)); if (perm) prot = mpp->mr_prot | PROT_WRITE; else prot = mpp->mr_prot & ~PROT_WRITE; if (mprotect((void *)(uintptr_t)mpp->mr_addr, mpp->mr_msize, prot) == -1) { int err = errno; eprintf(LIST(lmp), ERR_FATAL, MSG_INTL(MSG_SYS_MPROT), NAME(lmp), strerror(err)); return (0); } mpp->mr_prot = prot; return (1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, Joyent, Inc. All rights reserved. * * Audit interfaces. Auditing can be enabled in two ways: * * - Using the LD_AUDIT environment variable * * - From individual objects containing a DT_DEPAUDIT entry * (see ld(1) -P/-p options). * * The former establishes a global set of audit libraries which can inspect all * objects from a given process. The latter establishes a local set of audit * libraries which can inspect the immediate dependencies of the caller. * * Audit library capabilities are indicated by flags within the link-map list * header (for global auditing), see LML_TFLG_AUD_* flags, or by the same flags * within the individual link-map (for local auditing). Although both sets of * flags can occur in different data items they are defined as one to simplify * audit interface requirements. The basic test for all audit interfaces is: * * if ((lml->lm_tflags | AFLAGS(lmp)) & LML_TFLG_AUD_MASK) * * Note. Auditors themselves are identified with the LML_TFLG_NOAUDIT link-map * list flag, and no LML_TFLG_AUD_MASK flags. These flags get propagated from * a callers link-map list to any new link-map lists created. Thus, standard * link-maps lists have the LML_TFLG_AUD_MASK flags propagated, and should a * new link-map list be created by an auditor, that list gets tagged as * LML_TFLG_NOAUDIT. * * The latter link-map list equivalence test insures that auditors themselves * (invoked through DT_DEPAUDIT) are not audited. * * The history of version changes: * * LAV_VERSION1 (Solaris 2.6) * Auditing implementation added. * * LAV_VERSION2 (Solaris 2.6) * LA_SYMB_ALTVALUE support added. * * LAV_VERSION3 (Solaris 9 update 7) * ld_objfilter() added. * * LAV_VERSION4 (Solaris 10 update 5) * Correction of activity calls for local auditors, and introduction of * -z globalaudit concept. * * LAV_VERSION5 (Solaris 11) * Under this version, preinit and activity events are enabled from local * auditors. The la_preinit and la_activity interfaces require a cookie * that represents the head of the link-map list being audited. If a * local preinit or activity interface is detected, the local auditors * la_objopen() routine is called with a cookie that represents the object * that heads the link-map list of the object being audited. * * A local auditor is loaded through adding a new dependency that requests * auditing, and therefore an la_activity(ADD) event is already in effect. * Regardless, the local auditors la_activity() routine is called with the * cookie that represents the object that heads the link-map list of the * object being audited. * * A local auditor can be loaded prior to the preinit event. In this case, * the local auditors la_preinit() routine is called with the cookie that * represents the object that heads the link-map list of the object being * audited. After the preinit event, any la_preinit() routine within a * local auditor will not be called. * * These events are intended to follow the expected sequence of events * received by global auditors, ie: * * - la_objopen(main) * - la_activity(ADD) * - la_objopen(dependency) * - la_activity(CONSISTENT) * - la_preinit(main) */ #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" #include "msg.h" uint_t audit_flags = 0; /* Copy of specific audit flags to */ /* simplify boot_elf.s access. */ /* * Obtain a head link-map cookie. Local auditors can provide la_preinit() and * la_activity() routines, and these routines require a cookie that represents * the object that heads the link-map of the object being audited. A list of * these cookies is maintained on the link-map list. This list allows multiple * local objects to specify the same auditor, and to obtain the same cookie * for the link-map that heads the link-map list. * * The initial cookie is created by _audit_create_head_client() which is called * from _audit_add_head(). This cookies address is then passed to the local * auditors ld_objopen() and la_activity() routines. Subsequent preinit and * activity events use _audit_get_head_client() to dynamically retrieve the * cookies address. */ static Audit_client * _audit_get_head_client(Rt_map *hlmp, Rt_map *almp) { Audit_client *acp; Aliste idx; Lm_list *hlml = LIST(hlmp); for (ALIST_TRAVERSE(hlml->lm_aud_cookies, idx, acp)) { if (acp->ac_lmp == almp) return (acp); } return (NULL); } static Audit_client * _audit_create_head_client(Rt_map *hlmp, Rt_map *almp) { Audit_client ac, *acp; Lm_list *hlml = LIST(hlmp); ac.ac_lmp = almp; ac.ac_cookie = (uintptr_t)hlmp; ac.ac_flags = 0; if ((acp = alist_append(&(hlml->lm_aud_cookies), &ac, sizeof (Audit_client), AL_CNT_COOKIES)) == NULL) return (NULL); return (acp); } /* * Determine the appropriate client. Each client structure identifies the * link-map of the auditor it is associated with. From the client structure, * the address of the associated cookie, that represents the object being * audited, is retrieved so that the address can be passed to any audit call. * * Note, objects that are being locally audited, can provide la_preinit() and * la_activity() routines. These routines must be passed cookies that represent * the object that heads the link-map list of the object being audited. These * cookies are not maintained on this objects Audit_client structure, but are * obtained from the associated link-map lists lm_cookies alist. */ static Audit_client * _audit_client(Audit_info *aip, Rt_map *almp) { int ndx; if (aip == NULL) return (NULL); for (ndx = 0; ndx < aip->ai_cnt; ndx++) { if (aip->ai_clients[ndx].ac_lmp == almp) return (&(aip->ai_clients[ndx])); } return (NULL); } /* * la_filter() caller. Traverse through all audit libraries and call any * la_filter() entry points found. A zero return from an auditor indicates * that the filtee should be ignored. */ static int _audit_objfilter(APlist *list, Rt_map *frlmp, const char *ref, Rt_map *felmp, uint_t flags) { Audit_list *alp; Aliste idx; Lm_list *frlml = LIST(frlmp); for (APLIST_TRAVERSE(list, idx, alp)) { Audit_client *fracp, *feacp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); int ret; if (alp->al_objfilter == NULL) continue; if ((fracp = _audit_client(AUDINFO(frlmp), almp)) == NULL) continue; if ((feacp = _audit_client(AUDINFO(felmp), almp)) == NULL) continue; DBG_CALL(Dbg_audit_objfilter(frlml, DBG_AUD_CALL, alp->al_libname, NAME(frlmp), NAME(felmp), ref)); leave(alml, thr_flg_reenter); ret = (*alp->al_objfilter)(&(fracp->ac_cookie), ref, &(feacp->ac_cookie), flags); (void) enter(thr_flg_reenter); if (ret == 0) { DBG_CALL(Dbg_audit_objfilter(frlml, DBG_AUD_RET, alp->al_libname, NAME(frlmp), NULL, NULL)); return (0); } } return (1); } int audit_objfilter(Rt_map *frlmp, const char *ref, Rt_map *felmp, uint_t flags) { uint_t rtldflags; int respond = 1; if (rt_critical()) return (respond); APPLICATION_ENTER(rtldflags); if (auditors && (auditors->ad_flags & LML_TFLG_AUD_OBJFILTER)) respond = _audit_objfilter(auditors->ad_list, frlmp, ref, felmp, flags); if (respond && AUDITORS(frlmp) && (AUDITORS(frlmp)->ad_flags & LML_TFLG_AUD_OBJFILTER)) respond = _audit_objfilter(AUDITORS(frlmp)->ad_list, frlmp, ref, felmp, flags); APPLICATION_RETURN(rtldflags); return (respond); } /* * la_objsearch() caller. Traverse through all audit libraries and call any * la_objsearch() entry points found. * * Effectively any audit library can change the name we're working with, so we * continue to propagate the new name to each audit library. Any 0 return * terminates the search. */ static char * _audit_objsearch(APlist *list, char *oname, Rt_map *clmp, uint_t flags) { Audit_list *alp; Aliste idx; Lm_list *clml = LIST(clmp); for (APLIST_TRAVERSE(list, idx, alp)) { Audit_client *acp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); char *nname = oname; if (alp->al_objsearch == NULL) continue; if ((acp = _audit_client(AUDINFO(clmp), almp)) == NULL) continue; DBG_CALL(Dbg_audit_objsearch(clml, DBG_AUD_CALL, alp->al_libname, nname, flags, NULL)); leave(alml, thr_flg_reenter); nname = (*alp->al_objsearch)(nname, &(acp->ac_cookie), flags); (void) enter(thr_flg_reenter); /* * Diagnose any return name that differs from the original name * passed to the auditor. */ if (nname && (nname[0] == '\0')) nname = NULL; if ((nname != oname) || strcmp(nname, oname)) DBG_CALL(Dbg_audit_objsearch(clml, DBG_AUD_RET, alp->al_libname, oname, flags, nname)); if ((oname = nname) == NULL) break; } return (oname); } char * audit_objsearch(Rt_map *clmp, const char *name, uint_t flags) { char *nname = (char *)name; uint_t rtldflags; if (rt_critical()) return (nname); APPLICATION_ENTER(rtldflags); if (auditors && (auditors->ad_flags & LML_TFLG_AUD_OBJSEARCH)) nname = _audit_objsearch(auditors->ad_list, nname, clmp, flags); if (nname && AUDITORS(clmp) && (AUDITORS(clmp)->ad_flags & LML_TFLG_AUD_OBJSEARCH)) nname = _audit_objsearch(AUDITORS(clmp)->ad_list, nname, clmp, flags); APPLICATION_RETURN(rtldflags); DBG_CALL(Dbg_libs_audit(LIST(clmp), name, nname)); return (nname); } /* * la_activity() caller. Traverse through all audit libraries and call any * la_activity() entry points found. */ static void _audit_activity(APlist *list, Rt_map *clmp, uint_t flags, Boolean client) { Audit_list *alp; Aliste idx; Lm_list *clml = LIST(clmp); for (APLIST_TRAVERSE(list, idx, alp)) { Audit_client *acp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); uintptr_t *cookie; if (alp->al_activity == 0) continue; /* * Determine what cookie is required. Any auditing that * originates from the object that heads the link-map list has * its own cookie. Local auditors must obtain the cookie that * represents the object that heads the link-map list. */ if (client) acp = _audit_client(AUDINFO(clmp), almp); else acp = _audit_get_head_client(clml->lm_head, almp); if (acp == NULL) continue; cookie = &(acp->ac_cookie); /* * Make sure the audit library only sees one addition/deletion * at a time. This ensures the library doesn't see numerous * events from lazy loading a series of libraries. Keep track * of this caller having called an auditor, so that the * appropriate "consistent" event can be supplied on leaving * ld.so.1. */ if ((flags == LA_ACT_ADD) || (flags == LA_ACT_DELETE)) { if (alml->lm_flags & LML_FLG_AUDITNOTIFY) continue; alml->lm_flags |= LML_FLG_AUDITNOTIFY; clml->lm_flags |= LML_FLG_ACTAUDIT; } else { if ((alml->lm_flags & LML_FLG_AUDITNOTIFY) == 0) continue; alml->lm_flags &= ~LML_FLG_AUDITNOTIFY; } DBG_CALL(Dbg_audit_activity(clml, alp->al_libname, NAME(clml->lm_head), flags)); leave(alml, thr_flg_reenter); (*alp->al_activity)(cookie, flags); (void) enter(thr_flg_reenter); } } void audit_activity(Rt_map *clmp, uint_t flags) { Rt_map *lmp; Aliste idx; uint_t rtldflags; if (rt_critical()) return; APPLICATION_ENTER(rtldflags); if (auditors && (auditors->ad_flags & LML_TFLG_AUD_ACTIVITY)) _audit_activity(auditors->ad_list, clmp, flags, TRUE); if (AUDITORS(clmp) && (AUDITORS(clmp)->ad_flags & LML_TFLG_AUD_ACTIVITY)) _audit_activity(AUDITORS(clmp)->ad_list, clmp, flags, TRUE); for (APLIST_TRAVERSE(aud_activity, idx, lmp)) { if ((clmp != lmp) && AUDITORS(lmp) && (AUDITORS(lmp)->ad_flags & LML_TFLG_AUD_ACTIVITY)) { _audit_activity(AUDITORS(lmp)->ad_list, lmp, flags, FALSE); } } APPLICATION_RETURN(rtldflags); } /* * Determine whether an auditor is in use by the head link-map object. */ static int _audit_used_by_head(Rt_map *hlmp, Rt_map *almp) { Audit_list *alp; Aliste idx; for (APLIST_TRAVERSE(AUDITORS(hlmp)->ad_list, idx, alp)) { if (alp->al_lmp == almp) return (1); } return (0); } /* * la_objopen() caller for the head link-map. Global auditors, or an auditor * started from the object that heads a link-map list (typically the dynamic * executable), are passed to la_objopen(). However, local auditors can * provide activity and preinit events, and for these events, a cookie * representing the head link-map list object is expected. This routine obtains * these cookies from the link-map list lm_cookies element. This element * ensures all clients of the same auditor use the same cookie. * * Although a local auditor will get an la_objopen() call for the object that * heads the link-map list of the object being audited, the auditor is not * permitted to request binding information for this head object. The head * object has already been in existence, and bindings may have been resolved * with it. This local auditor is coming into existence too late, and thus we * don't allow any bindings to be caught at all. */ static int _audit_add_head(Rt_map *clmp, Rt_map *hlmp, int preinit, int activity) { Lm_list *clml = LIST(clmp); Lmid_t lmid = get_linkmap_id(clml); Audit_list *alp; Aliste idx; int save = 0; for (APLIST_TRAVERSE(AUDITORS(clmp)->ad_list, idx, alp)) { Audit_client *acp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); uintptr_t *cookie; uint_t rtldflags; /* * Ensure this local auditor isn't already in existence as an * auditor for the head of the link-map list. If it is, then * this auditor will have already receive preinit and activity * events. */ if (AUDITORS(hlmp) && _audit_used_by_head(hlmp, almp)) continue; /* * Create a cookie that represents the object that heads the * link-map list. If the cookie already exists, then this * auditor has already been established for another objects * local auditing. In this case, do not issue a la_objopen() * or la_activity() event, as these will have already occurred. */ if ((acp = _audit_get_head_client(clml->lm_head, almp)) != NULL) continue; if ((acp = _audit_create_head_client(clml->lm_head, almp)) == NULL) return (0); cookie = &(acp->ac_cookie); save++; /* * Call the la_objopen() if available. */ if (alp->al_objopen) { uint_t flags; DBG_CALL(Dbg_audit_objopen(clml, DBG_AUD_CALL, alp->al_libname, NAME(hlmp), 0, FALSE)); APPLICATION_ENTER(rtldflags); leave(alml, thr_flg_reenter); flags = (*alp->al_objopen)((Link_map *)hlmp, lmid, cookie); (void) enter(thr_flg_reenter); APPLICATION_RETURN(rtldflags); if (flags) { DBG_CALL(Dbg_audit_objopen(clml, DBG_AUD_RET, alp->al_libname, NAME(hlmp), flags, TRUE)); } } /* * Call the la_activity() if available. */ if (alp->al_activity) { alml->lm_flags |= LML_FLG_AUDITNOTIFY; clml->lm_flags |= LML_FLG_ACTAUDIT; DBG_CALL(Dbg_audit_activity(clml, alp->al_libname, NAME(clml->lm_head), LA_ACT_ADD)); APPLICATION_ENTER(rtldflags); leave(alml, thr_flg_reenter); (*alp->al_activity)(cookie, LA_ACT_ADD); (void) enter(thr_flg_reenter); APPLICATION_RETURN(rtldflags); } } /* * If new head link-map cookies have been generated, then maintain * any preinit and/or activity requirements. */ if (save) { if (preinit && (aplist_append(&aud_preinit, clmp, AL_CNT_AUDITORS) == NULL)) return (0); if (activity && (aplist_append(&aud_activity, clmp, AL_CNT_AUDITORS) == NULL)) return (0); } return (1); } /* * la_objopen() caller. Create an audit information structure for the indicated * link-map, regardless of an la_objopen() entry point. This structure is used * to supply information to various audit interfaces (see LML_MSK_AUDINFO). * Traverse through all audit libraries and call any la_objopen() entry points * found. */ static int _audit_objopen(APlist *list, Rt_map *nlmp, Lmid_t lmid, Audit_info *aip, int *ndx) { Lm_list *nlml = LIST(nlmp); Audit_list *alp; Aliste idx; for (APLIST_TRAVERSE(list, idx, alp)) { uint_t flags; Audit_client *acp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); /* * Associate a cookie with the audit library, and assign the * initial cookie as the present link-map. */ acp = &aip->ai_clients[(*ndx)++]; acp->ac_lmp = alp->al_lmp; acp->ac_cookie = (uintptr_t)nlmp; if (alp->al_objopen == NULL) continue; DBG_CALL(Dbg_audit_objopen(nlml, DBG_AUD_CALL, alp->al_libname, NAME(nlmp), 0, FALSE)); leave(alml, thr_flg_reenter); flags = (*alp->al_objopen)((Link_map *)nlmp, lmid, &(acp->ac_cookie)); (void) enter(thr_flg_reenter); /* * Diagnose any flags returned by the auditor. */ if (flags) { DBG_CALL(Dbg_audit_objopen(nlml, DBG_AUD_RET, alp->al_libname, NAME(nlmp), flags, FALSE)); } if (flags & LA_FLG_BINDTO) acp->ac_flags |= FLG_AC_BINDTO; if (flags & LA_FLG_BINDFROM) { ulong_t pltcnt; acp->ac_flags |= FLG_AC_BINDFROM; /* * We only need dynamic plt's if a pltenter and/or a * pltexit() entry point exist in one of our auditing * libraries. */ if (aip->ai_dynplts || (JMPREL(nlmp) == 0) || ((audit_flags & (AF_PLTENTER | AF_PLTEXIT)) == 0)) continue; /* * Create one dynplt for every 'PLT' that exists in the * object. */ pltcnt = PLTRELSZ(nlmp) / RELENT(nlmp); if ((aip->ai_dynplts = calloc(pltcnt, dyn_plt_ent_size)) == NULL) return (0); } } return (1); } int audit_objopen(Rt_map *clmp, Rt_map *nlmp) { Lmid_t lmid = get_linkmap_id(LIST(nlmp)); int respond = 1, ndx = 0; uint_t rtldflags; uint_t clients = 0; Audit_info *aip; if (rt_critical()) return (respond); /* * Determine the number of auditors that can receive information * regarding this object. This provides the number of client * structures required for this object. */ if (auditors) clients = auditors->ad_cnt; if (AUDITORS(clmp)) clients += AUDITORS(clmp)->ad_cnt; if ((nlmp != clmp) && AUDITORS(nlmp)) clients += AUDITORS(nlmp)->ad_cnt; /* * Allocate an audit information structure. Each audited object * maintains a AUDINFO() structure. As this structure can only be * created once all auditors are loaded, a client count can now be * computed. * * The allocation of the audit information structure includes an array * of audit clients, 1 per audit library that has been loaded. * * --------------- * | ai_cnt | * Audit_info | ai_clients |------- * | ai_dynplts | | * |---------------| | * Audit_client | 1 |<------ * |---------------| * | 2 | * ......... */ if ((AUDINFO(nlmp) = aip = calloc(1, sizeof (Audit_info) + (sizeof (Audit_client) * clients))) == NULL) return (0); aip->ai_cnt = clients; aip->ai_clients = (Audit_client *)((uintptr_t)aip + sizeof (Audit_info)); APPLICATION_ENTER(rtldflags); if (auditors) respond = _audit_objopen(auditors->ad_list, nlmp, lmid, aip, &ndx); if (respond && AUDITORS(clmp)) respond = _audit_objopen(AUDITORS(clmp)->ad_list, nlmp, lmid, aip, &ndx); if (respond && (nlmp != clmp) && AUDITORS(nlmp)) respond = _audit_objopen(AUDITORS(nlmp)->ad_list, nlmp, lmid, aip, &ndx); APPLICATION_RETURN(rtldflags); return (respond); } /* * la_objclose() caller. Traverse through all audit libraries and call any * la_objclose() entry points found. */ void _audit_objclose(APlist *list, Rt_map *lmp) { Audit_list *alp; Aliste idx; Lm_list *lml = LIST(lmp); for (APLIST_TRAVERSE(list, idx, alp)) { Audit_client *acp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); if (alp->al_objclose == NULL) continue; if ((acp = _audit_client(AUDINFO(lmp), almp)) == NULL) continue; DBG_CALL(Dbg_audit_objclose(lml, alp->al_libname, NAME(lmp))); leave(alml, thr_flg_reenter); (void) (*alp->al_objclose)(&(acp->ac_cookie)); (void) enter(thr_flg_reenter); } } /* * Determine any la_objclose() requirements. An object that is about to be * deleted needs to trigger an la_objclose() event to any associated auditors. * In the case of local auditing, a deleted object may have a number of callers, * and each of these callers may have their own auditing requirements. To * ensure only one la_objclose() event is sent to each auditor, collect the * auditors from any callers and make sure there's no duplication. */ inline static void add_objclose_list(Rt_map *lmp, APlist **alpp) { if (AFLAGS(lmp) & LML_TFLG_AUD_OBJCLOSE) { Audit_list *alp; Aliste idx; for (APLIST_TRAVERSE(AUDITORS(lmp)->ad_list, idx, alp)) { if (aplist_test(alpp, alp, AL_CNT_AUDITORS) == 0) return; } } } void audit_objclose(Rt_map *lmp, Rt_map *clmp) { APlist *alp = NULL; uint_t rtldflags; if (rt_critical()) return; APPLICATION_ENTER(rtldflags); if (auditors && (auditors->ad_flags & LML_TFLG_AUD_OBJCLOSE)) _audit_objclose(auditors->ad_list, lmp); /* * If this link-map list contains local auditors, determine if this * object, or any of this objects CALLERS have instantiated auditors * that need to know of la_objclose() events. */ if (LIST(lmp)->lm_flags & LML_FLG_LOCAUDIT) { Bnd_desc *bdp; Aliste idx; add_objclose_list(lmp, &alp); for (APLIST_TRAVERSE(CALLERS(lmp), idx, bdp)) add_objclose_list(bdp->b_caller, &alp); } /* * If this close originated from dlclose(), determine whether the caller * requires a la_objclose() event. */ if (clmp) add_objclose_list(clmp, &alp); if (alp) { _audit_objclose(alp, lmp); free((void *)alp); } APPLICATION_RETURN(rtldflags); } /* * la_pltenter() caller. Traverse through all audit libraries and call any * la_pltenter() entry points found. NOTE: this routine is called via the * glue code established in elf_plt_trace_write(), the symbol descriptor is * created as part of the glue and for 32bit environments the st_name is a * pointer to the real symbol name (ie. it's already been adjusted with the * objects base offset). For 64bit environments the st_name remains the * original symbol offset and in this case it is used to compute the real name * pointer and pass as a separate argument to the auditor. */ static void _audit_pltenter(APlist *list, Rt_map *rlmp, Rt_map *dlmp, Sym *sym, uint_t ndx, void *regs, uint_t *flags) { Audit_list *alp; Aliste idx; Lm_list *rlml = LIST(rlmp); #if defined(_ELF64) const char *name = (const char *)(sym->st_name + STRTAB(dlmp)); #else const char *name = (const char *)(sym->st_name); #endif for (APLIST_TRAVERSE(list, idx, alp)) { Audit_client *racp, *dacp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); Addr ovalue = sym->st_value; if (alp->al_pltenter == 0) continue; if ((racp = _audit_client(AUDINFO(rlmp), almp)) == NULL) continue; if ((dacp = _audit_client(AUDINFO(dlmp), almp)) == NULL) continue; if (((racp->ac_flags & FLG_AC_BINDFROM) == 0) || ((dacp->ac_flags & FLG_AC_BINDTO) == 0)) continue; DBG_CALL(Dbg_audit_pltenter(rlml, DBG_AUD_CALL, alp->al_libname, name, ovalue)); leave(alml, thr_flg_reenter); sym->st_value = (Addr)(*alp->al_pltenter)(sym, ndx, &(racp->ac_cookie), &(dacp->ac_cookie), regs, /* BEGIN CSTYLED */ #if defined(_ELF64) flags, name); #else flags); #endif /* END CSTYLED */ (void) enter(thr_flg_reenter); if (ovalue != sym->st_value) { DBG_CALL(Dbg_audit_pltenter(rlml, DBG_AUD_RET, alp->al_libname, name, sym->st_value)); } } } Addr audit_pltenter(Rt_map *rlmp, Rt_map *dlmp, Sym *sym, uint_t ndx, void *regs, uint_t *flags) { Sym nsym = *sym; uint_t rtldflags; if (rt_critical()) return (nsym.st_value); /* * We're effectively entering ld.so.1 from user (glue) code. */ (void) enter(0); APPLICATION_ENTER(rtldflags); if (auditors && (auditors->ad_flags & LML_TFLG_AUD_PLTENTER)) _audit_pltenter(auditors->ad_list, rlmp, dlmp, &nsym, ndx, regs, flags); if (AUDITORS(rlmp) && (AUDITORS(rlmp)->ad_flags & LML_TFLG_AUD_PLTENTER)) _audit_pltenter(AUDITORS(rlmp)->ad_list, rlmp, dlmp, &nsym, ndx, regs, flags); APPLICATION_RETURN(rtldflags); leave(LIST(rlmp), 0); return (nsym.st_value); } /* * la_pltexit() caller. Traverse through all audit libraries and call any * la_pltexit() entry points found. See notes above (_audit_pltenter) for * discussion on st_name. */ static Addr _audit_pltexit(APlist *list, uintptr_t retval, Rt_map *rlmp, Rt_map *dlmp, Sym *sym, uint_t ndx) { Audit_list *alp; Aliste idx; #if defined(_ELF64) const char *name = (const char *)(sym->st_name + STRTAB(dlmp)); #else const char *name = (const char *)(sym->st_name); #endif Lm_list *rlml = LIST(rlmp); for (APLIST_TRAVERSE(list, idx, alp)) { Audit_client *racp, *dacp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); if (alp->al_pltexit == 0) continue; if ((racp = _audit_client(AUDINFO(rlmp), almp)) == NULL) continue; if ((dacp = _audit_client(AUDINFO(dlmp), almp)) == NULL) continue; if (((racp->ac_flags & FLG_AC_BINDFROM) == 0) || ((dacp->ac_flags & FLG_AC_BINDTO) == 0)) continue; DBG_CALL(Dbg_audit_pltexit(rlml, alp->al_libname, name)); leave(alml, thr_flg_reenter); retval = (*alp->al_pltexit)(sym, ndx, &(racp->ac_cookie), &(dacp->ac_cookie), /* BEGIN CSTYLED */ #if defined(_ELF64) retval, name); #else retval); #endif /* END CSTYLED */ (void) enter(thr_flg_reenter); } return (retval); } Addr audit_pltexit(uintptr_t retval, Rt_map *rlmp, Rt_map *dlmp, Sym *sym, uint_t ndx) { uintptr_t _retval = retval; uint_t rtldflags; if (rt_critical()) return (_retval); /* * We're effectively entering ld.so.1 from user (glue) code. */ (void) enter(0); APPLICATION_ENTER(rtldflags); if (auditors && (auditors->ad_flags & LML_TFLG_AUD_PLTEXIT)) _retval = _audit_pltexit(auditors->ad_list, _retval, rlmp, dlmp, sym, ndx); if (AUDITORS(rlmp) && (AUDITORS(rlmp)->ad_flags & LML_TFLG_AUD_PLTEXIT)) _retval = _audit_pltexit(AUDITORS(rlmp)->ad_list, _retval, rlmp, dlmp, sym, ndx); APPLICATION_RETURN(rtldflags); leave(LIST(rlmp), 0); return (_retval); } /* * la_symbind() caller. Traverse through all audit libraries and call any * la_symbind() entry points found. */ static Addr _audit_symbind(APlist *list, Rt_map *rlmp, Rt_map *dlmp, Sym *sym, uint_t ndx, uint_t *flags, int *called) { Audit_list *alp; Aliste idx; Lm_list *rlml = LIST(rlmp); #if defined(_ELF64) const char *name = (const char *)(sym->st_name + STRTAB(dlmp)); #else const char *name = (const char *)(sym->st_name); #endif for (APLIST_TRAVERSE(list, idx, alp)) { Audit_client *racp, *dacp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); Addr ovalue = sym->st_value; uint_t lflags, oflags = *flags; if (alp->al_symbind == 0) continue; if ((racp = _audit_client(AUDINFO(rlmp), almp)) != NULL && (racp->ac_flags & FLG_AC_BINDFROM) == 0) continue; if ((dacp = _audit_client(AUDINFO(dlmp), almp)) == NULL) continue; if ((dacp->ac_flags & FLG_AC_BINDTO) == 0) continue; /* * The la_symbind interface is only called when the destination * object has been identified as BINDTO and either the * destination object is being locally audited or the calling * object has been identified as BINDFROM. Use a local version * of the flags, so that any user update can be collected. */ (*called)++; lflags = (oflags & ~(LA_SYMB_NOPLTENTER | LA_SYMB_NOPLTEXIT)); DBG_CALL(Dbg_audit_symbind(rlml, DBG_AUD_CALL, alp->al_libname, name, ovalue, oflags)); leave(alml, thr_flg_reenter); sym->st_value = (*alp->al_symbind)(sym, ndx, racp == NULL ? NULL : &(racp->ac_cookie), &(dacp->ac_cookie), /* BEGIN CSTYLED */ #if defined(_ELF64) &lflags, name); #else &lflags); #endif /* END CSTYLED */ (void) enter(thr_flg_reenter); /* * If the auditor indicated that they did not want to process * pltenter, or pltexit audits for this symbol, retain this * information. Also retain whether an alternative symbol value * has been supplied. */ *flags |= (lflags & (LA_SYMB_NOPLTENTER | LA_SYMB_NOPLTEXIT)); if ((ovalue != sym->st_value) && (alp->al_vernum >= LAV_VERSION2)) *flags |= LA_SYMB_ALTVALUE; if ((ovalue != sym->st_value) || (oflags != *flags)) { DBG_CALL(Dbg_audit_symbind(rlml, DBG_AUD_RET, alp->al_libname, name, sym->st_value, *flags)); } } return (sym->st_value); } Addr audit_symbind(Rt_map *rlmp, Rt_map *dlmp, Sym *sym, uint_t ndx, Addr value, uint_t *flags) { Sym nsym; int called = 0; uint_t rtldflags; /* * Construct a new symbol from that supplied but with the real address. * In the 64-bit world the st_name field is only 32-bits which isn't * big enough to hold a character pointer. We pass this pointer as a * separate parameter for 64-bit audit libraries. */ nsym = *sym; nsym.st_value = value; if (rt_critical()) return (nsym.st_value); #if !defined(_ELF64) nsym.st_name += (Word)STRTAB(dlmp); #endif APPLICATION_ENTER(rtldflags); if (auditors && (auditors->ad_flags & LML_TFLG_AUD_SYMBIND)) nsym.st_value = _audit_symbind(auditors->ad_list, rlmp, dlmp, &nsym, ndx, flags, &called); if (AUDITORS(rlmp) && (AUDITORS(rlmp)->ad_flags & LML_TFLG_AUD_SYMBIND)) nsym.st_value = _audit_symbind(AUDITORS(rlmp)->ad_list, rlmp, dlmp, &nsym, ndx, flags, &called); if (dlmp != rlmp && AUDITORS(dlmp) && (AUDITORS(dlmp)->ad_flags & LML_TFLG_AUD_SYMBIND)) { nsym.st_value = _audit_symbind(AUDITORS(dlmp)->ad_list, rlmp, dlmp, &nsym, ndx, flags, &called); } /* * If no la_symbind() was called for this interface, fabricate that no * la_pltenter, or la_pltexit is required. This helps reduce the glue * code created for further auditing. */ if (called == 0) *flags |= (LA_SYMB_NOPLTENTER | LA_SYMB_NOPLTEXIT); APPLICATION_RETURN(rtldflags); return (nsym.st_value); } /* * la_preinit() caller. Traverse through all audit libraries and call any * la_preinit() entry points found. */ static void _audit_preinit(APlist *list, Rt_map *clmp, Boolean client) { Audit_list *alp; Aliste idx; Lm_list *clml = LIST(clmp); for (APLIST_TRAVERSE(list, idx, alp)) { Audit_client *acp; Rt_map *almp = alp->al_lmp; Lm_list *alml = LIST(almp); uintptr_t *cookie; if (alp->al_preinit == 0) continue; /* * Determine what cookie is required. Any auditing that * originates from the object that heads the link-map list has * its own cookie. Local auditors must obtain the cookie that * represents the object that heads the link-map list. */ if (client) acp = _audit_client(AUDINFO(clmp), almp); else acp = _audit_get_head_client(clml->lm_head, almp); if (acp == NULL) continue; cookie = &(acp->ac_cookie); DBG_CALL(Dbg_audit_preinit(clml, alp->al_libname, NAME(clml->lm_head))); leave(alml, thr_flg_reenter); (*alp->al_preinit)(cookie); (void) enter(thr_flg_reenter); } } void audit_preinit(Rt_map *mlmp) { Rt_map *clmp; Aliste idx; uint_t rtldflags; if (rt_critical()) return; APPLICATION_ENTER(rtldflags); if (auditors && (auditors->ad_flags & LML_TFLG_AUD_PREINIT)) _audit_preinit(auditors->ad_list, mlmp, TRUE); if (AUDITORS(mlmp) && (AUDITORS(mlmp)->ad_flags & LML_TFLG_AUD_PREINIT)) _audit_preinit(AUDITORS(mlmp)->ad_list, mlmp, TRUE); for (APLIST_TRAVERSE(aud_preinit, idx, clmp)) { if (AUDITORS(clmp) && (AUDITORS(clmp)->ad_flags & LML_TFLG_AUD_PREINIT)) _audit_preinit(AUDITORS(clmp)->ad_list, clmp, FALSE); } APPLICATION_RETURN(rtldflags); } /* * Clean up (free) an audit descriptor. First, gather a list of all handles, * and then close each one down. This is done rather than using the handles * directly from the auditors, as the audit list can be torn down as a result * of the dlclose. In other words, what you're pointing at can be removed * while you're still pointing at it. */ void audit_desc_cleanup(Rt_map *clmp) { Audit_desc *adp = AUDITORS(clmp); Audit_list *alp; Aliste idx; APlist *ghalp = NULL; if (adp == NULL) return; if (adp->ad_name) free(adp->ad_name); for (APLIST_TRAVERSE(adp->ad_list, idx, alp)) (void) aplist_append(&ghalp, alp->al_ghp, AL_CNT_GROUPS); free(adp->ad_list); adp->ad_list = NULL; free(adp); /* * Indicate that the caller is no longer being audited. */ AUDITORS(clmp) = NULL; AFLAGS(clmp) &= ~LML_TFLG_AUD_MASK; if (ghalp) { Grp_hdl *ghp; Aliste idx; for (APLIST_TRAVERSE(ghalp, idx, ghp)) { (void) dlclose_intn(ghp, clmp); } free(ghalp); } } /* * Objects that establish local auditors may have been added to preinit or * activity lists. Remove the object from this list if it is present. */ static void remove_auditor(APlist *alp, Rt_map *clmp) { Rt_map *lmp; Aliste idx; for (APLIST_TRAVERSE(alp, idx, lmp)) { if (lmp == clmp) { aplist_delete(alp, &idx); return; } } } /* * Clean up (free) an audit information structure. */ void audit_info_cleanup(Rt_map *clmp) { Audit_info *aip = AUDINFO(clmp); if (aip == NULL) return; if (aip->ai_dynplts) free(aip->ai_dynplts); if (aud_preinit) remove_auditor(aud_preinit, clmp); if (aud_activity) remove_auditor(aud_activity, clmp); free(aip); } /* * Create a data structure of symbol lookup names and associated flags to help * simplify audit_symget() use. */ typedef struct { Msg sname; uint_t alflag; uint_t auflag; } Aud_info; static const Aud_info aud_info[] = { { MSG_SYM_LAVERSION, 0, 0 }, /* MSG_ORIG(MSG_SYM_LAVERSION) */ { MSG_SYM_LAPREINIT, /* MSG_ORIG(MSG_SYM_LAPREINIT) */ LML_TFLG_AUD_PREINIT, 0 }, { MSG_SYM_LAOBJSEARCH, /* MSG_ORIG(MSG_SYM_LAOBJSEARCH) */ LML_TFLG_AUD_OBJSEARCH, 0 }, { MSG_SYM_LAOBJOPEN, /* MSG_ORIG(MSG_SYM_LAOBJOPEN) */ LML_TFLG_AUD_OBJOPEN, 0 }, { MSG_SYM_LAOBJFILTER, /* MSG_ORIG(MSG_SYM_LAOBJFILTER */ LML_TFLG_AUD_OBJFILTER, 0 }, { MSG_SYM_LAOBJCLOSE, /* MSG_ORIG(MSG_SYM_LAOBJCLOSE) */ LML_TFLG_AUD_OBJCLOSE, 0 }, { MSG_SYM_LAACTIVITY, /* MSG_ORIG(MSG_SYM_LAACTIVITY) */ LML_TFLG_AUD_ACTIVITY, 0 }, #if defined(_ELF64) { MSG_SYM_LASYMBIND_64, /* MSG_ORIG(MSG_SYM_LASYMBIND_64) */ #else { MSG_SYM_LASYMBIND, /* MSG_ORIG(MSG_SYM_LASYMBIND) */ #endif LML_TFLG_AUD_SYMBIND, 0 }, #if defined(__sparcv9) { MSG_SYM_LAV9PLTENTER, /* MSG_ORIG(MSG_SYM_LAV9PLTENTER) */ #elif defined(__sparc) { MSG_SYM_LAV8PLTENTER, /* MSG_ORIG(MSG_SYM_LAV8PLTENTER) */ #elif defined(__amd64) { MSG_SYM_LAAMD64PLTENTER, /* MSG_ORIG(MSG_SYM_LAAMD64PLTENTER) */ #elif defined(__i386) { MSG_SYM_LAX86PLTENTER, /* MSG_ORIG(MSG_SYM_LAX86PLTENTER) */ #else #error platform not defined! #endif LML_TFLG_AUD_PLTENTER, AF_PLTENTER }, #if defined(_ELF64) { MSG_SYM_LAPLTEXIT_64, /* MSG_ORIG(MSG_SYM_LAPLTEXIT_64) */ #else { MSG_SYM_LAPLTEXIT, /* MSG_ORIG(MSG_SYM_LAPLTEXIT) */ #endif LML_TFLG_AUD_PLTEXIT, AF_PLTEXIT } }; #define AI_LAVERSION 0 #define AI_LAPREINIT 1 #define AI_LAOBJSEARCH 2 #define AI_LAOBJOPEN 3 #define AI_LAOBJFILTER 4 #define AI_LAOBJCLOSE 5 #define AI_LAACTIVITY 6 #define AI_LASYMBIND 7 #define AI_LAPLTENTER 8 #define AI_LAPLTEXIT 9 static Addr audit_symget(Audit_list *alp, int info, int *in_nfavl) { Rt_map *lmp = alp->al_lmp; const char *sname = MSG_ORIG(aud_info[info].sname); uint_t alflag = aud_info[info].alflag; uint_t auflag = aud_info[info].auflag; uint_t binfo; Slookup sl; Sresult sr; /* * Initialize the symbol lookup, and symbol result, data structures. */ SLOOKUP_INIT(sl, sname, lml_rtld.lm_head, lmp, ld_entry_cnt, 0, 0, 0, 0, (LKUP_FIRST | LKUP_DLSYM)); SRESULT_INIT(sr, sname); if (LM_LOOKUP_SYM(lmp)(&sl, &sr, &binfo, in_nfavl)) { Addr addr = sr.sr_sym->st_value; if (!(FLAGS(lmp) & FLG_RT_FIXED)) addr += ADDR(lmp); if (alflag) alp->al_flags |= alflag; if (auflag) audit_flags |= auflag; /* * Note, unlike most other diagnostics, where we wish to * identify the lmid of the caller, here we use the lmid of * the auditor itself to show the association of the auditor * and the interfaces it provides. */ DBG_CALL(Dbg_audit_interface(LIST(alp->al_lmp), alp->al_libname, sr.sr_name)); return (addr); } return (0); } /* * Centralize cleanup routines. */ static int audit_disable(char *name, Rt_map *clmp, Grp_hdl *ghp, Audit_list *alp) { eprintf(LIST(clmp), ERR_FATAL, MSG_INTL(MSG_AUD_DISABLED), name); if (ghp) (void) dlclose_intn(ghp, clmp); if (alp) free(alp); return (0); } /* * Given a list of one or more audit libraries, open each one and establish a * a descriptor representing the entry points it provides. */ int audit_setup(Rt_map *clmp, Audit_desc *adp, uint_t orig, int *in_nfavl) { char *ptr, *next; Lm_list *clml = LIST(clmp); Rt_map *hlmp; int error = 1, activity = 0, preinit = 0; uint_t rtldflags; /* * Determine the type of auditing for diagnostics. */ if (DBG_ENABLED) { int type; if (orig & PD_FLG_EXTLOAD) type = DBG_AUD_PRELOAD; else if (FLAGS1(clmp) & FL1_RT_GLOBAUD) type = DBG_AUD_GLOBAL; else type = DBG_AUD_LOCAL; DBG_CALL(Dbg_audit_lib(clmp, adp->ad_name, type)); } /* * Mark that we have at least one auditing link map */ rtld_flags2 |= RT_FL2_HASAUDIT; /* * The audit definitions may be a list (which will already have been * dupped) so split it into individual tokens. */ for (ptr = strtok_r(adp->ad_name, MSG_ORIG(MSG_STR_DELIMIT), &next); ptr; ptr = strtok_r(NULL, MSG_ORIG(MSG_STR_DELIMIT), &next)) { Grp_hdl *ghp; Rt_map *lmp; Lm_list *lml; Rt_map **tobj; Audit_list *alp; DBG_CALL(Dbg_util_nl(clml, DBG_NL_STD)); /* * Open the audit library on its own link-map. */ if ((ghp = dlmopen_intn((Lm_list *)LM_ID_NEWLM, ptr, (RTLD_FIRST | RTLD_GLOBAL | RTLD_WORLD), clmp, FLG_RT_AUDIT, orig)) == NULL) { error = audit_disable(ptr, clmp, 0, 0); continue; } lmp = ghp->gh_ownlmp; lml = LIST(lmp); /* * If this auditor has already been loaded, reuse it. */ if ((alp = lml->lm_alp) != NULL) { if (aplist_append(&(adp->ad_list), alp, AL_CNT_AUDITORS) == NULL) return (audit_disable(ptr, clmp, ghp, alp)); adp->ad_cnt++; adp->ad_flags |= alp->al_flags; /* * If this existing auditor provides preinit or * activity routines, track their existence. The * instantiation of a local auditor requires a cookie * be created that represents the object that heads * the link-map list of the object being audited. */ if (alp->al_preinit) preinit++; if (alp->al_activity) activity++; continue; } /* * Prior to the Unified Process Model (UPM) environment, an * rtld lock had to be held upon leave(). However, even within * a UPM environment, an old auditor, that has a lazy dependency * on libc, is still a possibility. As libc isn't loaded, we * don't know the process model, and will determine this later. * Refer to external.c:get_lcinterface(). */ if ((rtld_flags2 & RT_FL2_UNIFPROC) == 0) lml->lm_flags |= LML_FLG_HOLDLOCK; /* * Allocate an audit list descriptor for this object and * search for all known entry points. */ if ((alp = calloc(1, sizeof (Audit_list))) == NULL) return (audit_disable(ptr, clmp, ghp, 0)); alp->al_libname = NAME(lmp); alp->al_lmp = lmp; alp->al_ghp = ghp; /* * All audit libraries must handshake through la_version(). * Determine that the symbol exists, finish initializing the * object, and then call the function. */ if ((alp->al_version = (uint_t(*)())audit_symget(alp, AI_LAVERSION, in_nfavl)) == 0) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_GEN_NOSYM), MSG_ORIG(MSG_SYM_LAVERSION)); error = audit_disable(ptr, clmp, ghp, alp); continue; } if ((tobj = tsort(lmp, lml->lm_init, RT_SORT_REV)) == (Rt_map **)S_ERROR) return (audit_disable(ptr, clmp, ghp, alp)); if (tobj) call_init(tobj, DBG_INIT_SORT); APPLICATION_ENTER(rtldflags); leave(lml, thr_flg_reenter); alp->al_vernum = (*alp->al_version)(LAV_CURRENT); (void) enter(thr_flg_reenter); APPLICATION_RETURN(rtldflags); DBG_CALL(Dbg_audit_version(clml, alp->al_libname, LAV_CURRENT, alp->al_vernum)); if ((alp->al_vernum < LAV_VERSION1) || (alp->al_vernum > LAV_CURRENT)) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_AUD_BADVERS), LAV_CURRENT, alp->al_vernum); error = audit_disable(ptr, clmp, ghp, alp); continue; } if (aplist_append(&(adp->ad_list), alp, AL_CNT_AUDITORS) == NULL) return (audit_disable(ptr, clmp, ghp, alp)); adp->ad_cnt++; /* * Collect any remaining entry points. */ alp->al_objsearch = (char *(*)())audit_symget(alp, AI_LAOBJSEARCH, in_nfavl); alp->al_objopen = (uint_t(*)())audit_symget(alp, AI_LAOBJOPEN, in_nfavl); alp->al_objfilter = (int(*)())audit_symget(alp, AI_LAOBJFILTER, in_nfavl); alp->al_objclose = (uint_t(*)())audit_symget(alp, AI_LAOBJCLOSE, in_nfavl); alp->al_symbind = (uintptr_t(*)())audit_symget(alp, AI_LASYMBIND, in_nfavl); alp->al_pltenter = (uintptr_t(*)())audit_symget(alp, AI_LAPLTENTER, in_nfavl); alp->al_pltexit = (uintptr_t(*)())audit_symget(alp, AI_LAPLTEXIT, in_nfavl); if ((alp->al_preinit = (void(*)())audit_symget(alp, AI_LAPREINIT, in_nfavl)) != NULL) preinit++; if ((alp->al_activity = (void(*)())audit_symget(alp, AI_LAACTIVITY, in_nfavl)) != NULL) activity++; /* * Collect the individual object flags, and assign this audit * list descriptor to its associated link-map list. */ adp->ad_flags |= alp->al_flags; lml->lm_alp = alp; } /* * If the caller isn't the head of its own link-map list, then any * preinit or activity entry points need to be tracked separately. * These "events" are not associated with a particular link-map, and * thus a traversal of any existing preinit and activity clients is * required. * * If either of these events are required, establish a cookie for the * object at the head of the link-map list, and make an initial ADD * activity for these local auditors. */ if ((preinit || activity) && ((hlmp = clml->lm_head) != clmp) && (_audit_add_head(clmp, hlmp, preinit, activity) == 0)) return (0); /* * Free the original audit string, as this descriptor may be used again * to add additional auditing. */ free(adp->ad_name); adp->ad_name = NULL; return (error); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998 by Sun Microsystems, Inc. */ #ifndef _CACHE_DOT_H #define _CACHE_DOT_H /* * ld.so directory caching */ #include /* * Shared object lookup performance in the run-time link editor is * enhanced through the use of caches for directories that the editor * searches. A given "cache" describes the contents of a single directory, * and each cache entry contains the canonical name for a shared object * as well as its absolute pathname. * * Within a cache, "pointers" are really relative addresses to some absolute * address (often the base address of the containing database). */ /* * Relative pointer macros. */ #define RELPTR(base, absptr) ((long)(absptr) - (long)(base)) #define AP(base) ((caddr_t)base) /* * Definitions for cache structures. */ #define DB_HASH 11 /* number of hash buckets in caches */ #define LD_CACHE_MAGIC 0x041155 /* cookie to identify data structure */ #define LD_CACHE_VERSION 0 /* version number of cache structure */ struct dbe { /* element of a directory cache */ long dbe_next; /* (rp) next element on this list */ long dbe_lop; /* (rp) canonical name for object */ long dbe_name; /* (rp) absolute name */ }; struct db { /* directory cache database */ long db_name; /* (rp) directory contained here */ struct dbe db_hash[DB_HASH]; /* hash buckets */ caddr_t db_chain; /* private to database mapping */ }; struct dbf { /* cache file image */ long dbf_magic; /* identifying cookie */ long dbf_version; /* version no. of these dbs */ long dbf_machtype; /* machine type */ long dbf_db; /* directory cache dbs */ }; /* * Structures used to describe and access a database. */ struct dbd { /* data base descriptor */ struct dbd *dbd_next; /* next one on this list */ struct db *dbd_db; /* data base described by this */ }; struct dd { /* directory descriptor */ struct dd *dd_next; /* next one on this list */ struct db *dd_db; /* data base described by this */ }; /* * Interfaces imported/exported by the lookup code. */ char *ask_db(); /* ask db for highest minor number */ struct db *lo_cache(); /* obtain cache for directory name */ #endif /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2022 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_elf.h" #include "_audit.h" #include "msg.h" /* * qsort(3c) capability comparison function. */ static int compare(const void *vp_a, const void *vp_b) { Fdesc *fdp_a = (Fdesc *)vp_a, *fdp_b = (Fdesc *)vp_b; char *strcap_a, *strcap_b; Xword hwcap_a, hwcap_b; /* * First, investigate any platform capability. */ strcap_a = fdp_a->fd_scapset.sc_plat; strcap_b = fdp_b->fd_scapset.sc_plat; if (strcap_a && (strcap_b == NULL)) return (-1); if (strcap_b && (strcap_a == NULL)) return (1); /* * Second, investigate any machine capability. */ strcap_a = fdp_a->fd_scapset.sc_mach; strcap_b = fdp_b->fd_scapset.sc_mach; if (strcap_a && (strcap_b == NULL)) return (-1); if (strcap_b && (strcap_a == NULL)) return (1); /* * Third, investigate any CA_SUNW_HW_3 hardware capabilities. */ hwcap_a = fdp_a->fd_scapset.sc_hw_3; hwcap_b = fdp_b->fd_scapset.sc_hw_3; if (hwcap_a > hwcap_b) return (-1); if (hwcap_a < hwcap_b) return (1); /* * Fourth, investigate any CA_SUNW_HW_2 hardware capabilities. */ hwcap_a = fdp_a->fd_scapset.sc_hw_2; hwcap_b = fdp_b->fd_scapset.sc_hw_2; if (hwcap_a > hwcap_b) return (-1); if (hwcap_a < hwcap_b) return (1); /* * Finally, investigate any CA_SUNW_HW_1 hardware capabilities. */ hwcap_a = fdp_a->fd_scapset.sc_hw_1; hwcap_b = fdp_b->fd_scapset.sc_hw_1; if (hwcap_a > hwcap_b) return (-1); if (hwcap_a < hwcap_b) return (1); /* * Normally, a capabilities directory contains one or more capabilities * files, each with different capabilities. The role of ld.so.1 is to * select the best candidate from these variants. However, we've come * across cases where files containing the same capabilities have been * placed in the same capabilities directory. As we can't tell which * file is the best, we select neither, and diagnose this suspicious * scenario. */ DBG_CALL(Dbg_cap_identical(fdp_a->fd_lml, fdp_a->fd_nname, fdp_b->fd_nname)); fdp_a->fd_flags |= FLG_FD_IGNORE; fdp_b->fd_flags |= FLG_FD_IGNORE; return (0); } /* * Determine whether HWCAP1 capabilities value is supported. */ int hwcap1_check(Syscapset *scapset, Xword val, Rej_desc *rej) { Xword mval; /* * Ensure that the kernel can cope with the required capabilities. */ if ((rtld_flags2 & RT_FL2_HWCAP) && ((mval = (val & ~scapset->sc_hw_1)) != 0)) { if (rej) { static Conv_cap_val_hw1_buf_t cap_buf; rej->rej_type = SGS_REJ_HWCAP_1; rej->rej_str = conv_cap_val_hw1(mval, M_MACH, 0, &cap_buf); } return (0); } return (1); } /* * Determine whether HWCAP2 capabilities value is supported. */ int hwcap2_check(Syscapset *scapset, Xword val, Rej_desc *rej) { Xword mval; /* * Ensure that the kernel can cope with the required capabilities. */ if ((mval = (val & ~scapset->sc_hw_2)) != 0) { if (rej) { static Conv_cap_val_hw2_buf_t cap_buf; rej->rej_type = SGS_REJ_HWCAP_2; rej->rej_str = conv_cap_val_hw2(mval, M_MACH, 0, &cap_buf); } return (0); } return (1); } /* * Determine whether HWCAP3 capabilities value is supported. */ int hwcap3_check(Syscapset *scapset, Xword val, Rej_desc *rej) { Xword mval; /* * Ensure that the kernel can cope with the required capabilities. */ if ((mval = (val & ~scapset->sc_hw_3)) != 0) { if (rej) { static Conv_cap_val_hw3_buf_t cap_buf; rej->rej_type = SGS_REJ_HWCAP_3; rej->rej_str = conv_cap_val_hw3(mval, M_MACH, 0, &cap_buf); } return (0); } return (1); } /* * Process any software capabilities. */ /* ARGSUSED0 */ int sfcap1_check(Syscapset *scapset, Xword val, Rej_desc *rej) { #if defined(_ELF64) /* * A 64-bit executable that started the process can be restricted to a * 32-bit address space. A 64-bit dependency that is restricted to a * 32-bit address space can not be loaded unless the executable has * established this requirement. */ if ((val & SF1_SUNW_ADDR32) && ((rtld_flags2 & RT_FL2_ADDR32) == 0)) { if (rej) { static Conv_cap_val_sf1_buf_t cap_buf; rej->rej_type = SGS_REJ_SFCAP_1; rej->rej_str = conv_cap_val_sf1(SF1_SUNW_ADDR32, M_MACH, 0, &cap_buf); } return (0); } #endif return (1); } /* * Process any platform capability. */ int platcap_check(Syscapset *scapset, const char *str, Rej_desc *rej) { /* * If the platform name hasn't been set, try and obtain it. */ if ((scapset->sc_plat == NULL) && (scapset->sc_platsz == 0)) platform_name(scapset); if ((scapset->sc_plat == NULL) || (str && strcmp(scapset->sc_plat, str))) { if (rej) { /* * Note, the platform name points to a string within an * objects string table, and if that object can't be * loaded, it will be unloaded and thus invalidate the * string. Duplicate the string here for rejection * message inheritance. */ rej->rej_type = SGS_REJ_PLATCAP; rej->rej_str = stravl_insert(str, 0, 0, 0); } return (0); } return (1); } /* * Process any machine capability. */ int machcap_check(Syscapset *scapset, const char *str, Rej_desc *rej) { /* * If the machine name hasn't been set, try and obtain it. */ if ((scapset->sc_mach == NULL) && (scapset->sc_machsz == 0)) machine_name(scapset); if ((scapset->sc_mach == NULL) || (str && strcmp(scapset->sc_mach, str))) { if (rej) { /* * Note, the machine name points to a string within an * objects string table, and if that object can't be * loaded, it will be unloaded and thus invalidate the * string. Duplicate the string here for rejection * message inheritance. */ rej->rej_type = SGS_REJ_MACHCAP; rej->rej_str = stravl_insert(str, 0, 0, 0); } return (0); } return (1); } /* * Generic front-end to capabilities validation. */ static int cap_check(Cap *cptr, char *strs, int alt, Fdesc *fdp, Rej_desc *rej) { Syscapset *scapset; int totplat, ivlplat, totmach, ivlmach; /* * If the caller has no capabilities, then the object is valid. */ if (cptr == NULL) return (1); if (alt) scapset = alt_scapset; else scapset = org_scapset; totplat = ivlplat = totmach = ivlmach = 0; while (cptr->c_tag != CA_SUNW_NULL) { Xword val = cptr->c_un.c_val; char *str; switch (cptr->c_tag) { case CA_SUNW_HW_1: /* * Remove any historic values that should not be * involved with any validation. */ val &= ~AV_HW1_IGNORE; if (hwcap1_check(scapset, val, rej) == 0) return (0); if (fdp) fdp->fd_scapset.sc_hw_1 = val; break; case CA_SUNW_SF_1: if (sfcap1_check(scapset, val, rej) == 0) return (0); if (fdp) fdp->fd_scapset.sc_sf_1 = val; break; case CA_SUNW_HW_2: if (hwcap2_check(scapset, val, rej) == 0) return (0); if (fdp) fdp->fd_scapset.sc_hw_2 = val; break; case CA_SUNW_PLAT: /* * A capabilities group can define multiple platform * names that are appropriate. Only if all the names * are deemed invalid is the group determined * inappropriate. */ if (totplat == ivlplat) { totplat++; str = strs + val; if (platcap_check(scapset, str, rej) == 0) ivlplat++; else if (fdp) fdp->fd_scapset.sc_plat = str; } break; case CA_SUNW_MACH: /* * A capabilities group can define multiple machine * names that are appropriate. Only if all the names * are deemed invalid is the group determined * inappropriate. */ if (totmach == ivlmach) { totmach++; str = strs + val; if (machcap_check(scapset, str, rej) == 0) ivlmach++; else if (fdp) fdp->fd_scapset.sc_mach = str; } break; case CA_SUNW_ID: /* * Capabilities identifiers provide for diagnostics, * but are not attributes that must be compared with * the system. They are ignored. */ break; case CA_SUNW_HW_3: if (hwcap3_check(scapset, val, rej) == 0) return (0); if (fdp) fdp->fd_scapset.sc_hw_3 = val; break; default: rej->rej_type = SGS_REJ_UNKCAP; rej->rej_info = cptr->c_tag; return (0); } cptr++; } /* * If any platform names, or machine names were found, and all were * invalid, indicate that the object is inappropriate. */ if ((totplat && (totplat == ivlplat)) || (totmach && (totmach == ivlmach))) return (0); return (1); } #define HWAVL_RECORDED(n) pnavl_recorded(&capavl, n, 0, NULL) /* * Determine whether a link-map should use alternative system capabilities. */ static void cap_check_lmp_init(Rt_map *lmp) { int alt = 0; /* * If an alternative set of system capabilities have been established, * and only specific files should use these alternative system * capabilities, determine whether this file is one of those specified. */ if (capavl) { const char *file; /* * The simplest way to reference a file is to use its file name * (soname), however try all of the names that this file is * known by. */ if ((file = strrchr(NAME(lmp), '/')) != NULL) file++; else file = NULL; if ((file && (HWAVL_RECORDED(file) != 0)) || (HWAVL_RECORDED(NAME(lmp)) != 0) || ((PATHNAME(lmp) != NAME(lmp)) && (HWAVL_RECORDED(PATHNAME(lmp)) != 0))) alt = 1; if (alt == 0) { Aliste idx; const char *cp; for (APLIST_TRAVERSE(ALIAS(lmp), idx, cp)) { if ((alt = HWAVL_RECORDED(cp)) != 0) break; } } } /* * Indicate if this link-map should use alternative system capabilities, * and that the alternative system capabilities check has been carried * out. */ if ((org_scapset != alt_scapset) && ((capavl == NULL) || alt)) FLAGS1(lmp) |= FL1_RT_ALTCAP; FLAGS1(lmp) |= FL1_RT_ALTCHECK; } /* * Validate the capabilities requirements of a link-map. * * This routine is called for main, where a link-map is constructed from the * mappings returned from exec(), and for any symbol capabilities comparisons. */ int cap_check_lmp(Rt_map *lmp, Rej_desc *rej) { if ((FLAGS1(lmp) & FL1_RT_ALTCHECK) == 0) cap_check_lmp_init(lmp); return (cap_check(CAP(lmp), STRTAB(lmp), (FLAGS1(lmp) & FL1_RT_ALTCAP), NULL, rej)); } /* * Validate the capabilities requirements of a file under inspection. * This file is still under the early stages of loading, and has no link-map * yet. The file must have an object capabilities definition (PT_SUNWCAP), to * have gotten us here. The logic here is the same as cap_check_lmp(). */ int cap_check_fdesc(Fdesc *fdp, Cap *cptr, char *strs, Rej_desc *rej) { int alt = 0; /* * If an alternative set of system capabilities have been established, * and only specific files should use these alternative system * capabilities, determine whether this file is one of those specified. */ if (capavl) { const char *file; /* * The simplest way to reference a file is to use its file name * (soname), however try all of the names that this file is * known by. */ if (fdp->fd_oname && ((file = strrchr(fdp->fd_oname, '/')) != NULL)) file++; else file = NULL; if ((file && (HWAVL_RECORDED(file) != 0)) || (fdp->fd_oname && (HWAVL_RECORDED(fdp->fd_oname) != 0)) || (fdp->fd_nname && (HWAVL_RECORDED(fdp->fd_nname) != 0)) || (fdp->fd_pname && (fdp->fd_pname != fdp->fd_nname) && (HWAVL_RECORDED(fdp->fd_pname) != 0))) alt = 1; } /* * Indicate if this file descriptor should use alternative system * capabilities, and that the alternative system capabilities check has * been carried out. */ if ((org_scapset != alt_scapset) && ((capavl == NULL) || alt)) fdp->fd_flags |= FLG_FD_ALTCAP; fdp->fd_flags |= FLG_FD_ALTCHECK; /* * Verify that the required capabilities are supported by the reference. */ return (cap_check(cptr, strs, (fdp->fd_flags & FLG_FD_ALTCAP), fdp, rej)); } /* * Free a file descriptor list. As part of building this list, the original * names for each capabilities candidate were duplicated for use in later * diagnostics. These names need to be freed. */ void free_fd(Alist *fdalp) { if (fdalp) { Aliste idx; Fdesc *fdp; for (ALIST_TRAVERSE(fdalp, idx, fdp)) { if (fdp->fd_oname) free((void *)fdp->fd_oname); } free(fdalp); } } /* * When $CAPABILITY (or $HWCAP) is used to represent dependencies, take the * associated directory and analyze all the files it contains. */ static int cap_dir(Alist **fdalpp, Lm_list *lml, const char *dname, Rt_map *clmp, uint_t flags, Rej_desc *rej, int *in_nfavl) { char path[PATH_MAX], *dst; const char *src; DIR *dir; struct dirent *dirent; Alist *fdalp = NULL; Aliste idx; Fdesc *fdp; int error = 0; /* * Access the directory in preparation for reading its entries. If * successful, establish the initial pathname. */ if ((dir = opendir(dname)) == NULL) { Rej_desc _rej = { 0 }; _rej.rej_type = SGS_REJ_STR; _rej.rej_name = dname; _rej.rej_str = strerror(errno); DBG_CALL(Dbg_file_rejected(lml, &_rej, M_MACH)); rejection_inherit(rej, &_rej); return (0); } for (dst = path, src = dname; *src; dst++, src++) *dst = *src; *dst++ = '/'; /* * Read each entry from the directory and determine whether it is a * valid ELF file. */ while ((dirent = readdir(dir)) != NULL) { const char *file = dirent->d_name; char *_dst; Fdesc fd = { 0 }; Rej_desc _rej = { 0 }; Pdesc pd = { 0 }; /* * Ignore "." and ".." entries. */ if ((file[0] == '.') && ((file[1] == '\0') || ((file[1] == '.') && (file[2] == '\0')))) continue; /* * Complete the full pathname. */ for (_dst = dst, src = file, file = dst; *src; _dst++, src++) *_dst = *src; *_dst = '\0'; /* * Trace the inspection of this file, and determine any * auditor substitution. */ pd.pd_pname = path; pd.pd_flags = PD_FLG_PNSLASH; if (load_trace(lml, &pd, clmp, &fd) == NULL) continue; /* * Note, all directory entries are processed by find_path(), * even entries that are directories themselves. This single * point for control keeps the number of stat()'s down, and * provides a single point for error diagnostics. */ if (find_path(lml, clmp, flags, &fd, &_rej, in_nfavl) == 0) { rejection_inherit(rej, &_rej); continue; } DBG_CALL(Dbg_cap_candidate(lml, fd.fd_nname)); /* * If this object has already been loaded, save the capabilities * for later sorting. Otherwise we have a new candidate. */ if (fd.fd_lmp) fd.fd_scapset = CAPSET(fd.fd_lmp); fd.fd_lml = lml; /* * Duplicate the original name, as this may be required for * later diagnostics. Keep a copy of the file descriptor for * analysis once all capabilities candidates have been * determined. */ if (((fd.fd_oname = strdup(fd.fd_oname)) == NULL) || (alist_append(&fdalp, &fd, sizeof (Fdesc), AL_CNT_CAP) == NULL)) { error = 1; break; } } (void) closedir(dir); /* * If no objects have been found, we're done. Also, if an allocation * error occurred while processing any object, remove any objects that * had already been added to the list and return. */ if ((fdalp == NULL) || error) { if (fdalp) free_fd(fdalp); return (0); } /* * Having processed and retained all candidates from this directory, * sort them, based on the precedence of their hardware capabilities. */ qsort(fdalp->al_data, fdalp->al_nitems, fdalp->al_size, compare); /* * If any objects were found to have the same capabilities, then these * objects must be rejected, as we can't tell which object is more * appropriate. */ for (ALIST_TRAVERSE(fdalp, idx, fdp)) { if (fdp->fd_flags & FLG_FD_IGNORE) alist_delete(fdalp, &idx); } if (fdalp->al_nitems == 0) { free_fd(fdalp); return (0); } *fdalpp = fdalp; return (1); } int cap_filtees(Alist **alpp, Aliste oidx, const char *dir, Aliste nlmco, Rt_map *flmp, Rt_map *clmp, const char *ref, int mode, uint_t flags, int *in_nfavl) { Alist *fdalp = NULL; Aliste idx; Fdesc *fdp; Lm_list *lml = LIST(flmp); int unused = 0; Rej_desc rej = { 0 }; if (cap_dir(&fdalp, lml, dir, flmp, flags, &rej, in_nfavl) == 0) return (0); /* * Now complete the mapping of each of the ordered objects, adding * each object to a new pathname descriptor. */ for (ALIST_TRAVERSE(fdalp, idx, fdp)) { Rt_map *nlmp; Grp_hdl *ghp = NULL; Pdesc *pdp; int audit = 0; if (unused) continue; /* * Complete mapping the file, obtaining a handle, and continue * to analyze the object, establishing dependencies and * relocating. Remove the file descriptor at this point, as it * is no longer required. */ DBG_CALL(Dbg_file_filtee(lml, NAME(flmp), fdp->fd_nname, 0)); nlmp = load_path(lml, nlmco, flmp, mode, (flags | FLG_RT_PUBHDL), &ghp, fdp, &rej, in_nfavl); if (nlmp == NULL) continue; /* * Create a new pathname descriptor to represent this filtee, * and insert this descriptor in the Alist following the * hardware descriptor that seeded this processing. */ if ((pdp = alist_insert(alpp, 0, sizeof (Pdesc), AL_CNT_FILTEES, ++oidx)) == NULL) { if (ghp) remove_lmc(lml, flmp, nlmco, NAME(nlmp)); return (0); } pdp->pd_pname = NAME(nlmp); pdp->pd_plen = strlen(NAME(nlmp)); /* * Establish the filter handle to prevent any recursion. */ if (nlmp && ghp) { ghp->gh_flags |= GPH_FILTEE; pdp->pd_info = (void *)ghp; } /* * Audit the filter/filtee established. A return of 0 * indicates the auditor wishes to ignore this filtee. */ if (nlmp && (lml->lm_tflags | FLAGS1(flmp)) & LML_TFLG_AUD_OBJFILTER) { if (audit_objfilter(flmp, ref, nlmp, 0) == 0) { audit = 1; nlmp = NULL; } } /* * Finish processing the objects associated with this request. */ if (nlmp && ghp && (((nlmp = analyze_lmc(lml, nlmco, nlmp, clmp, in_nfavl)) == NULL) || (relocate_lmc(lml, nlmco, flmp, nlmp, in_nfavl) == 0))) nlmp = NULL; /* * If the filtee has been successfully processed, then create * an association between the filter and the filtee. This * association provides sufficient information to tear down the * filter and filtee if necessary. */ DBG_CALL(Dbg_file_hdl_title(DBG_HDL_ADD)); if (nlmp && ghp && (hdl_add(ghp, flmp, GPD_FILTER, NULL) == NULL)) nlmp = NULL; /* * If this object is marked an end-filtee, we're done. */ if (nlmp && ghp && (FLAGS1(nlmp) & FL1_RT_ENDFILTE)) unused = 1; /* * If this filtee loading has failed, generate a diagnostic. * Null out the path name descriptor entry, and continue the * search. */ if (nlmp == NULL) { DBG_CALL(Dbg_file_filtee(lml, 0, pdp->pd_pname, audit)); /* * If attempting to load this filtee required a new * link-map control list to which this request has * added objects, then remove all the objects that * have been associated to this request. */ if (nlmco != ALIST_OFF_DATA) remove_lmc(lml, flmp, nlmco, pdp->pd_pname); pdp->pd_plen = 0; pdp->pd_info = NULL; } } free_fd(fdalp); return (1); } /* * Load an individual capabilities object. */ Rt_map * load_cap(Lm_list *lml, Aliste lmco, const char *dir, Rt_map *clmp, uint_t mode, uint_t flags, Grp_hdl **hdl, Rej_desc *rej, int *in_nfavl) { Alist *fdalp = NULL; Aliste idx; Fdesc *fdp; int found = 0; Rt_map *lmp = NULL; /* * Obtain the sorted list of hardware capabilities objects available. */ if (cap_dir(&fdalp, lml, dir, clmp, flags, rej, in_nfavl) == 0) return (NULL); /* * From the list of hardware capability objects, use the first and * discard the rest. */ for (ALIST_TRAVERSE(fdalp, idx, fdp)) { Fdesc fd = *fdp; if ((found == 0) && ((lmp = load_path(lml, lmco, clmp, mode, flags, hdl, &fd, rej, in_nfavl)) != NULL)) found++; } free_fd(fdalp); return (lmp); } /* * Use a case insensitive string match when looking up capability mask * values by name, and omit the AV_ prefix. */ #define ELFCAP_STYLE ELFCAP_STYLE_LC | ELFCAP_STYLE_F_ICMP /* * To aid in the development and testing of capabilities, an alternative system * capabilities group can be specified. This alternative set is initialized * from the system capabilities that are normally used to validate all object * loading. However, the user can disable, enable or override flags within * this alternative set, and thus affect object loading. * * This technique is usually combined with defining the family of objects * that should be compared against this alternative set. Without defining the * family of objects, all objects loaded by ld.so.1 are validated against the * alternative set. This can prevent the loading of critical system objects * like libc, and thus prevent process execution. */ typedef enum { CAP_OVERRIDE = 0, /* override existing capabilities */ CAP_ENABLE = 1, /* enable capabilities */ CAP_DISABLE = 2 /* disable capabilities */ } cap_mode; /* * The override indexes originally followed the values of CA_SUNW_HW_1, SF_1, * etc. */ typedef enum { CAP_OVR_HW_1 = 0, CAP_OVR_SF_1, CAP_OVR_HW_2, CAP_OVR_HW_3, CAP_OVR_MAX } cap_index_t; static struct { elfcap_mask_t cs_val[3]; /* value settings, and indicator for */ int cs_set[3]; /* OVERRIDE, ENABLE and DISABLE */ elfcap_mask_t *cs_aval; /* alternative variable for final */ /* update */ } cap_settings[CAP_OVR_MAX] = { { { 0, 0, 0 }, { 0, 0, 0 }, NULL }, /* CA_SUNW_HW_1 */ { { 0, 0, 0 }, { 0, 0, 0 }, NULL }, /* CA_SUNW_SF_1 */ { { 0, 0, 0 }, { 0, 0, 0 }, NULL }, /* CA_SUNW_HW_2 */ { { 0, 0, 0 }, { 0, 0, 0 }, NULL } /* CA_SUNW_HW_3 */ }; static int cap_modify(Xword tag, const char *str) { char *caps, *ptr, *next; cap_mode mode = CAP_OVERRIDE; cap_index_t ndx; if ((caps = strdup(str)) == NULL) return (0); for (ptr = strtok_r(caps, MSG_ORIG(MSG_CAP_DELIMIT), &next); ptr != NULL; ptr = strtok_r(NULL, MSG_ORIG(MSG_CAP_DELIMIT), &next)) { Xword val = 0; /* * Determine whether this token should be enabled (+), * disabled (-), or override any existing settings. */ if (*ptr == '+') { mode = CAP_ENABLE; ptr++; } else if (*ptr == '-') { mode = CAP_DISABLE; ptr++; } /* * Process the capabilities as directed by the calling tag. */ switch (tag) { case CA_SUNW_HW_1: /* * Determine whether the capabilities string matches * a known hardware capability mask. Note, the caller * indicates that these are hardware capabilities by * passing in the CA_SUNW_HW_1 tag. However, the * tokens could be CA_SUNW_HW_1, CA_SUNW_HW_2, or * CA_SUNW_HW_3. */ if ((val = (Xword)elfcap_hw3_from_str(ELFCAP_STYLE, ptr, M_MACH)) != 0) { ndx = CAP_OVR_HW_3; break; } if ((val = (Xword)elfcap_hw2_from_str(ELFCAP_STYLE, ptr, M_MACH)) != 0) { ndx = CAP_OVR_HW_2; break; } if ((val = (Xword)elfcap_hw1_from_str(ELFCAP_STYLE, ptr, M_MACH)) != 0) ndx = CAP_OVR_HW_1; break; case CA_SUNW_SF_1: /* * Determine whether the capabilities string matches a * known software capability mask. Note, the callers * indication of what capabilities to process are * triggered by a tag of CA_SUNW_SF_1, but the tokens * processed could be CA_SUNW_SF_1, CA_SUNW_SF_2, etc. */ if ((val = (Xword)elfcap_sf1_from_str(ELFCAP_STYLE, ptr, M_MACH)) != 0) ndx = CAP_OVR_SF_1; break; } /* * If a capabilities token has not been matched, interpret the * string as a number. To provide for setting the various * families (CA_SUNW_HW_1, CA_SUNW_HW_2), the number can be * prefixed with the (bracketed) family index. * * LD_HWCAP=[1]0x40 sets CA_SUNW_HW_1 with 0x40 * LD_HWCAP=[2]0x80 sets CA_SUNW_HW_2 with 0x80 * LD_HWCAP=[3]0x44 sets CA_SUNW_HW_3 with 0x44 * * Invalid indexes are ignored. */ if (val == 0) { char *end; if ((*ptr == '[') && (*(ptr + 2) == ']')) { if (*(ptr + 1) == '1') { ndx = CAP_OVR_HW_1; ptr += 3; } else if (*(ptr + 1) == '3') { if (tag == CA_SUNW_HW_1) { ndx = CAP_OVR_HW_3; ptr += 3; } else { /* invalid index */ continue; } } else if (*(ptr + 1) == '2') { if (tag == CA_SUNW_HW_1) { ndx = CAP_OVR_HW_2; ptr += 3; } else { /* invalid index */ continue; } } else { /* invalid index */ continue; } } else { ndx = tag - 1; } errno = 0; if (((val = strtol(ptr, &end, 16)) == 0) && errno) continue; /* * If the value wasn't an entirely valid hexadecimal * integer, assume it was intended as a capability * name and skip it. */ if (*end != '\0') { eprintf(NULL, ERR_WARNING, MSG_INTL(MSG_CAP_IGN_UNKCAP), ptr); continue; } } cap_settings[ndx].cs_val[mode] |= val; cap_settings[ndx].cs_set[mode]++; } /* * If the "override" token was supplied, set the alternative * system capabilities, then enable or disable others. */ for (ndx = 0; ndx < CAP_OVR_MAX; ndx++) { if (cap_settings[ndx].cs_set[CAP_OVERRIDE]) *(cap_settings[ndx].cs_aval) = cap_settings[ndx].cs_val[CAP_OVERRIDE]; if (cap_settings[ndx].cs_set[CAP_ENABLE]) *(cap_settings[ndx].cs_aval) |= cap_settings[ndx].cs_val[CAP_ENABLE]; if (cap_settings[ndx].cs_set[CAP_DISABLE]) *(cap_settings[ndx].cs_aval) &= ~cap_settings[ndx].cs_val[CAP_DISABLE]; } free(caps); return (1); } #undef ELFCAP_STYLE /* * Create an AVL tree of objects that are to be validated against an alternative * system capabilities value. */ static int cap_files(const char *str) { char *caps, *name, *next; if ((caps = strdup(str)) == NULL) return (0); for (name = strtok_r(caps, MSG_ORIG(MSG_CAP_DELIMIT), &next); name != NULL; name = strtok_r(NULL, MSG_ORIG(MSG_CAP_DELIMIT), &next)) { avl_index_t where; PathNode *pnp; uint_t hash = sgs_str_hash(name); /* * Determine whether this pathname has already been recorded. */ if (pnavl_recorded(&capavl, name, hash, &where)) continue; if ((pnp = calloc(1, sizeof (PathNode))) != NULL) { pnp->pn_name = name; pnp->pn_hash = hash; avl_insert(capavl, pnp, where); } } return (1); } /* * Set alternative system capabilities. A user can establish alternative system * capabilities from the environment, or from a configuration file. This * routine is called in each instance. Environment variables only set the * replaceable (rpl) variables. Configuration files can set both replaceable * (rpl) and permanent (prm) variables. */ int cap_alternative(void) { /* * If no capabilities have been set, we're done. */ if ((rpl_hwcap == NULL) && (rpl_sfcap == NULL) && (rpl_machcap == NULL) && (rpl_platcap == NULL) && (prm_hwcap == NULL) && (prm_sfcap == NULL) && (prm_machcap == NULL) && (prm_platcap == NULL)) return (1); /* * If the user has requested to modify any capabilities, establish a * unique set from the present system capabilities. */ if ((alt_scapset = malloc(sizeof (Syscapset))) == NULL) return (0); *alt_scapset = *org_scapset; cap_settings[CAP_OVR_HW_1].cs_aval = &alt_scapset->sc_hw_1; cap_settings[CAP_OVR_SF_1].cs_aval = &alt_scapset->sc_sf_1; cap_settings[CAP_OVR_HW_2].cs_aval = &alt_scapset->sc_hw_2; cap_settings[CAP_OVR_HW_3].cs_aval = &alt_scapset->sc_hw_3; /* * Process any replaceable variables. */ if (rpl_hwcap && (cap_modify(CA_SUNW_HW_1, rpl_hwcap) == 0)) return (0); if (rpl_sfcap && (cap_modify(CA_SUNW_SF_1, rpl_sfcap) == 0)) return (0); if (rpl_platcap) { alt_scapset->sc_plat = (char *)rpl_platcap; alt_scapset->sc_platsz = strlen(rpl_platcap); } if (rpl_machcap) { alt_scapset->sc_mach = (char *)rpl_machcap; alt_scapset->sc_machsz = strlen(rpl_machcap); } if (rpl_cap_files && (cap_files(rpl_cap_files) == 0)) return (0); /* * Process any permanent variables. */ if (prm_hwcap && (cap_modify(CA_SUNW_HW_1, prm_hwcap) == 0)) return (0); if (prm_sfcap && (cap_modify(CA_SUNW_SF_1, prm_sfcap) == 0)) return (0); if (prm_platcap) { alt_scapset->sc_plat = (char *)prm_platcap; alt_scapset->sc_platsz = strlen(prm_platcap); } if (prm_machcap) { alt_scapset->sc_mach = (char *)prm_machcap; alt_scapset->sc_machsz = strlen(prm_machcap); } if (prm_cap_files && (cap_files(prm_cap_files) == 0)) return (0); /* * Reset the replaceable variables. If this is the environment variable * processing, these variables are now available for configuration file * initialization. */ rpl_hwcap = rpl_sfcap = rpl_machcap = rpl_platcap = rpl_cap_files = NULL; return (1); } /* * Take the index from a Capinfo entry and determine the associated capabilities * set. Verify that the capabilities are available for this system. */ static int sym_cap_check(Cap *cptr, uint_t cndx, Syscapset *bestcapset, Rt_map *lmp, const char *name, uint_t ndx) { Syscapset *scapset; int totplat, ivlplat, totmach, ivlmach, capfail = 0; /* * Determine whether this file requires validation against alternative * system capabilities. */ if ((FLAGS1(lmp) & FL1_RT_ALTCHECK) == 0) cap_check_lmp_init(lmp); if (FLAGS1(lmp) & FL1_RT_ALTCAP) scapset = alt_scapset; else scapset = org_scapset; totplat = ivlplat = totmach = ivlmach = 0; /* * A capabilities index points to a capabilities group that can consist * of one or more capabilities, terminated with a CA_SUNW_NULL entry. */ for (cptr += cndx; cptr->c_tag != CA_SUNW_NULL; cptr++) { Xword val = cptr->c_un.c_val; char *str; switch (cptr->c_tag) { case CA_SUNW_HW_1: /* * Remove any historic values that should not be * involved with any validation. */ val &= ~AV_HW1_IGNORE; bestcapset->sc_hw_1 = val; DBG_CALL(Dbg_syms_cap_lookup(lmp, DBG_CAP_HW_1, name, ndx, M_MACH, bestcapset)); if (hwcap1_check(scapset, val, NULL) == 0) capfail++; break; case CA_SUNW_SF_1: bestcapset->sc_sf_1 = val; DBG_CALL(Dbg_syms_cap_lookup(lmp, DBG_CAP_SF_1, name, ndx, M_MACH, bestcapset)); if (sfcap1_check(scapset, val, NULL) == 0) capfail++; break; case CA_SUNW_HW_2: bestcapset->sc_hw_2 = val; DBG_CALL(Dbg_syms_cap_lookup(lmp, DBG_CAP_HW_2, name, ndx, M_MACH, bestcapset)); if (hwcap2_check(scapset, val, NULL) == 0) capfail++; break; case CA_SUNW_PLAT: /* * A capabilities set can define multiple platform names * that are appropriate. Only if all the names are * deemed invalid is the group determined inappropriate. */ if (totplat == ivlplat) { totplat++; str = STRTAB(lmp) + val; bestcapset->sc_plat = str; DBG_CALL(Dbg_syms_cap_lookup(lmp, DBG_CAP_PLAT, name, ndx, M_MACH, bestcapset)); if (platcap_check(scapset, str, NULL) == 0) ivlplat++; } break; case CA_SUNW_MACH: /* * A capabilities set can define multiple machine names * that are appropriate. Only if all the names are * deemed invalid is the group determined inappropriate. */ if (totmach == ivlmach) { totmach++; str = STRTAB(lmp) + val; bestcapset->sc_mach = str; DBG_CALL(Dbg_syms_cap_lookup(lmp, DBG_CAP_MACH, name, ndx, M_MACH, bestcapset)); if (machcap_check(scapset, str, NULL) == 0) ivlmach++; } break; case CA_SUNW_HW_3: bestcapset->sc_hw_3 = val; DBG_CALL(Dbg_syms_cap_lookup(lmp, DBG_CAP_HW_3, name, ndx, M_MACH, bestcapset)); if (hwcap3_check(scapset, val, NULL) == 0) capfail++; break; default: break; } } /* * If any platform definitions, or machine definitions were found, and * all were invalid, indicate that the object is inappropriate. */ if (capfail || (totplat && (totplat == ivlplat)) || (totmach && (totmach == ivlmach))) { DBG_CALL(Dbg_syms_cap_lookup(lmp, DBG_CAP_REJECTED, name, ndx, M_MACH, NULL)); return (0); } DBG_CALL(Dbg_syms_cap_lookup(lmp, DBG_CAP_CANDIDATE, name, ndx, M_MACH, NULL)); return (1); } /* * Determine whether a symbols capabilities are more significant than any that * have already been validated. The precedence of capabilities are: * * PLATCAP -> MACHCAP -> HWCAP_2 -> HWCAP_1 * * * Presently we make no comparisons of software capabilities. However, should * this symbol capability have required the SF1_SUNW_ADDR32 attribute, then * this would have been validated as appropriate or not. * * bestcapset is the presently available 'best' capabilities group, and * symcapset is the present capabilities group under investigation. Return 0 * if the bestcapset should remain in affect, or 1 if the symcapset is better. */ inline static int is_sym_the_best(Syscapset *bestcapset, Syscapset *symcapset) { /* * Check any platform capability. If the new symbol isn't associated * with a CA_SUNW_PLAT capability, and the best symbol is, then retain * the best capabilities group. If the new symbol is associated with a * CA_SUNW_PLAT capability, and the best symbol isn't, then the new * symbol needs to be taken. */ if (bestcapset->sc_plat && (symcapset->sc_plat == NULL)) return (0); if ((bestcapset->sc_plat == NULL) && symcapset->sc_plat) return (1); /* * Check any machine name capability. If the new symbol isn't * associated with a CA_SUNW_MACH capability, and the best symbol is, * then retain the best capabilities group. If the new symbol is * associated with a CA_SUNW_MACH capability, and the best symbol isn't, * then the new symbol needs to be taken. */ if (bestcapset->sc_mach && (symcapset->sc_mach == NULL)) return (0); if ((bestcapset->sc_mach == NULL) && symcapset->sc_mach) return (1); /* * Check the hardware capabilities. If the best symbols CA_SUNW_HW_3 * capabilities are greater than the new symbols capabilities, then * retain the best capabilities group. If the new symbols CA_SUNW_HW_3 * capabilities are greater than the best symbol, then the new symbol * needs to be taken. Repeat the same process for CA_SUNW_HW_2. */ if (bestcapset->sc_hw_3 > symcapset->sc_hw_3) return (0); if (bestcapset->sc_hw_3 < symcapset->sc_hw_3) return (1); if (bestcapset->sc_hw_2 > symcapset->sc_hw_2) return (0); if (bestcapset->sc_hw_2 < symcapset->sc_hw_2) return (1); /* * Check the remaining hardware capabilities. If the best symbols * CA_SUNW_HW_1 capabilities are greater than the new symbols * capabilities, then retain the best capabilities group. If the new * symbols CA_SUNW_HW_1 capabilities are greater than the best symbol, * then the new symbol needs to be taken. */ if (bestcapset->sc_hw_1 > symcapset->sc_hw_1) return (0); if (bestcapset->sc_hw_1 < symcapset->sc_hw_1) return (1); /* * Both capabilities are the same. Retain the best on a first-come * first-served basis. */ return (0); } /* * Initiate symbol capabilities processing. If an initial symbol lookup * results in binding to a symbol that has an associated SUNW_capinfo entry, * we arrive here. * * The standard model is that this initial symbol is the lead capabilities * symbol (defined as CAPINFO_SUNW_GLOB) of a capabilities family. This lead * symbol's SUNW_capinfo information points to the SUNW_capchain entry that * provides the family symbol indexes. We traverse this chain, looking at * each family member, to discover the best capabilities instance. This * instance name and symbol information is returned to establish the final * symbol binding. * * If the symbol that got us here is not CAPINFO_SUNW_GLOB, then we've bound * directly to a capabilities symbol which must be verified. This is not the * model created by ld(1) using -z symbolcap, but might be created directly * within a relocatable object by the compilation system. */ int cap_match(Sresult *srp, uint_t symndx, Sym *symtabptr, char *strtabptr) { Rt_map *ilmp = srp->sr_dmap; Sym *bsym = NULL; const char *bname; Syscapset bestcapset = { 0 }; Cap *cap; Capchain *capchain; uchar_t grpndx; uint_t ochainndx, nchainndx, bndx; cap = CAP(ilmp); capchain = CAPCHAIN(ilmp); grpndx = (uchar_t)ELF_C_GROUP(CAPINFO(ilmp)[symndx]); /* * If this symbols capability group is not a lead symbol, then simply * verify the symbol. */ if (grpndx != CAPINFO_SUNW_GLOB) { Syscapset symcapset = { 0 }; return (sym_cap_check(cap, grpndx, &symcapset, ilmp, srp->sr_name, symndx)); } /* * If there is no capabilities chain, return the lead symbol. */ if (capchain == NULL) return (1); ochainndx = (uint_t)ELF_C_SYM(CAPINFO(ilmp)[symndx]); /* * If there is only one member for this family, take it. Once a family * has been processed, the best family instance is written to the head * of the chain followed by a null entry. This caching ensures that the * same family comparison doesn't have to be undertaken more than once. */ if (capchain[ochainndx] && (capchain[ochainndx + 1] == 0)) { Sym *fsym = symtabptr + capchain[ochainndx]; const char *fname = strtabptr + fsym->st_name; DBG_CALL(Dbg_syms_cap_lookup(ilmp, DBG_CAP_USED, fname, capchain[ochainndx], M_MACH, NULL)); srp->sr_sym = fsym; srp->sr_name = fname; return (1); } /* * As this symbol is the lead symbol of a capabilities family, it is * considered the generic member, and therefore forms the basic * fall-back for the capabilities family. */ DBG_CALL(Dbg_syms_cap_lookup(ilmp, DBG_CAP_DEFAULT, srp->sr_name, symndx, M_MACH, NULL)); bsym = srp->sr_sym; bname = srp->sr_name; bndx = symndx; /* * Traverse the capabilities chain analyzing each family member. */ for (nchainndx = ochainndx + 1, symndx = capchain[nchainndx]; symndx; nchainndx++, symndx = capchain[nchainndx]) { Sym *nsym = symtabptr + symndx; const char *nname = strtabptr + nsym->st_name; Syscapset symcapset = { 0 }; if ((grpndx = (uchar_t)ELF_C_GROUP(CAPINFO(ilmp)[symndx])) == 0) continue; if (sym_cap_check(cap, grpndx, &symcapset, ilmp, nname, symndx) == 0) continue; /* * Determine whether a symbol's capabilities are more * significant than any that have already been validated. */ if (is_sym_the_best(&bestcapset, &symcapset)) { bestcapset = symcapset; bsym = nsym; bname = nname; bndx = symndx; } } DBG_CALL(Dbg_syms_cap_lookup(ilmp, DBG_CAP_USED, bname, bndx, M_MACH, NULL)); /* * Having found the best symbol, cache the results by overriding the * first element of the associated chain. */ capchain[ochainndx] = bndx; capchain[ochainndx + 1] = 0; /* * Update the symbol result information for return to the user. */ srp->sr_sym = bsym; srp->sr_name = bname; return (1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "msg.h" static Config _config = { 0 }; Config *config = &_config; /* * Validate a configuration file. */ static void elf_config_validate(Addr addr, Rtc_head *head, Rt_map *lmp) { Lm_list *lml = LIST(lmp); const char *str, *strtbl = config->c_strtbl; Rtc_obj *obj; Rtc_dir *dirtbl; Rtc_file *filetbl; rtld_stat_t status; int err; /* * If this configuration file is for a specific application make sure * we've been invoked by the application. Note that we only check the * basename component of the application as the original application * and its cached equivalent are never going to have the same pathnames. * Also, we use PATHNAME() and not NAME() - this catches things like vi * that exec shells using execv(/usr/bin/ksh, sh ...). */ if (head->ch_app) { char *_str, *_cname, *cname; const char *aname = PATHNAME(lmp); obj = (Rtc_obj *)(head->ch_app + addr); cname = _cname = (char *)(strtbl + obj->co_name); if ((_str = strrchr(aname, '/')) != NULL) aname = ++_str; if ((_str = strrchr(cname, '/')) != NULL) cname = ++_str; if (strcmp(aname, cname)) { /* * It's possible a user is trying to ldd(1) an alternate * shared object and point to a configuration file that * the shared object is part of. In this case ignore * any mismatch name warnings. */ if ((lml->lm_flags & LML_FLG_TRC_ENABLE) && ((FLAGS1(lmp) & FL1_RT_LDDSTUB) == 0)) { eprintf(lml, ERR_WARNING, MSG_INTL(MSG_CONF_APP), config->c_name, _cname); return; } } /* * If we have a valid alternative application reset its original * name for possible $ORIGIN processing. */ if ((FLAGS1(lmp) & FL1_RT_LDDSTUB) == 0) { ORIGNAME(lmp) = _cname; DIRSZ(lmp) = cname - _cname - 1; } } /* * If alternative objects are specified traverse the directories * specified in the configuration file, if any directory is newer than * the time it was recorded in the cache then continue to inspect its * files. Any file determined newer than its configuration recording * questions the the use of any alternative objects. The intent here * is to make sure no-one abuses a configuration as a means of static * linking. */ for (dirtbl = (Rtc_dir *)(head->ch_dir + addr); dirtbl->cd_obj; dirtbl++) { /* * Skip directories that provide no files - this also catches * RTC_OBJ_NOEXIST directories. */ filetbl = (Rtc_file *)(dirtbl->cd_file + addr); if (filetbl->cf_obj == 0) continue; /* * Skip directories that haven't provided real, dumped files. */ obj = (Rtc_obj *)(dirtbl->cd_obj + addr); if ((obj->co_flags & (RTC_OBJ_DUMP | RTC_OBJ_REALPTH)) != (RTC_OBJ_DUMP | RTC_OBJ_REALPTH)) continue; str = strtbl + obj->co_name; if (rtld_stat(str, &status) != 0) { err = errno; eprintf(lml, ERR_WARNING, MSG_INTL(MSG_CONF_DSTAT), config->c_name, str, strerror(err)); continue; } if (status.st_mtime == obj->co_info) continue; /* * The system directory is newer than the configuration files * entry, start checking any dumped files. */ for (; filetbl->cf_obj; filetbl++) { obj = (Rtc_obj *)(filetbl->cf_obj + addr); str = strtbl + obj->co_name; /* * Skip any files that aren't real, dumped files. */ if ((obj->co_flags & (RTC_OBJ_DUMP | RTC_OBJ_REALPTH)) != (RTC_OBJ_DUMP | RTC_OBJ_REALPTH)) continue; if (rtld_stat(str, &status) != 0) { err = errno; eprintf(lml, ERR_WARNING, MSG_INTL(MSG_CONF_FSTAT), config->c_name, str, strerror(err)); continue; } /* * If the files size is different somethings been * changed. */ if (status.st_size != obj->co_info) { eprintf(lml, ERR_WARNING, MSG_INTL(MSG_CONF_FCMP), config->c_name, str); } } } } /* * Process a configuration file. * * A configuration file can be specified using the LD_CONFIG environment * variable, from a DT_CONFIG string recorded in the executable (see ld(1) -c), * or in the case of a crle(1) dumped image, the file is "fabricated" to a * configuration file that may have been associated with the dumped image. In * the absence of any of these techniques, a default configuration file is used. * * The LD_CONFIG variable takes precedence, unless the application is secure * (see ld.so.1(1), issetugid(2)), in which case the environment variable is * ignored (see ld_generic_env()). * * A DT_CONFIG string is honored, even if the application is secure. However, * the path name follows the same rules as RUNPATH's, which must be a full path * name with no use of $ORIGIN. */ int elf_config(Rt_map *lmp) { Rtc_id *id; Rtc_head *head; int fd, features = 0; rtld_stat_t status; Addr addr; const char *str; char path[PATH_MAX]; /* * If we're dealing with an alternative application, fabricate the need * for a $ORIGIN/ld.config.app-name configuration file. */ if (rtld_flags & RT_FL_CONFAPP) { if ((str = strrchr(PATHNAME(lmp), '/')) != NULL) str++; else str = PATHNAME(lmp); (void) snprintf(path, PATH_MAX, MSG_ORIG(MSG_ORG_CONFIG), str); str = path; } else str = config->c_name; /* * If a configuration file name is known, expand and verify the name. */ if (str) { size_t size = strlen(str); char *estr = (char *)str; uint_t tkns; /* * Expand any configuration string. */ if ((tkns = expand(&estr, &size, 0, 0, (PD_TKN_ISALIST | PD_TKN_CAP), lmp)) == 0) return (0); /* * If this is a secure application, validate the configuration * file path name. Ignore any untrustworthy path name, and * fall through to pick up the defaults. */ if ((rtld_flags & RT_FL_SECURE) && (is_path_secure(estr, lmp, PD_FLG_FULLPATH, tkns) == 0)) str = NULL; else str = (const char *)estr; } /* * If a configuration file has not been specified try opening up the * default. */ if (str == NULL) { #if defined(_ELF64) str = MSG_ORIG(MSG_PTH_CONFIG_64); #else str = MSG_ORIG(MSG_PTH_CONFIG); #endif } config->c_name = str; /* * If we can't open the configuration file return silently. */ if ((fd = open(str, O_RDONLY, 0)) == -1) return (DBG_CONF_PRCFAIL); /* * Determine the configuration file size and map the file. */ (void) rtld_fstat(fd, &status); if (status.st_size < sizeof (Rtc_head)) { (void) close(fd); return (DBG_CONF_CORRUPT); } if ((addr = (Addr)mmap(0, status.st_size, PROT_READ, MAP_SHARED, fd, 0)) == (Addr)MAP_FAILED) { (void) close(fd); return (DBG_CONF_PRCFAIL); } (void) close(fd); /* * If we have an Rtc_id block at the beginning, then validate it * and advance the address to the Rtc_head. If not, then trust * that the file is compatible with us and move ahead (there is * some error checking for Rtc_head below as well). */ id = (Rtc_id *) addr; if (RTC_ID_TEST(id)) { addr += sizeof (*id); status.st_size -= sizeof (*id); if (status.st_size < sizeof (Rtc_head)) return (DBG_CONF_CORRUPT); if ((id->id_class != M_CLASS) || (id->id_data != M_DATA) || (id->id_machine != M_MACH)) return (DBG_CONF_ABIMISMATCH); } config->c_bgn = addr; config->c_end = addr + status.st_size; head = (Rtc_head *)addr; /* * Make sure we can handle this version of the configuration file. */ if (head->ch_version > RTC_VER_CURRENT) return (DBG_CONF_VERSION); /* * When crle(1) creates a temporary configuration file the * RTC_HDR_IGNORE flag is set. Thus the mapping of the configuration * file is taken into account but not its content. */ if (head->ch_cnflags & RTC_HDR_IGNORE) return (DBG_CONF_IGNORE); /* * Apply any new default library pathname. */ if (head->ch_edlibpath) { str = (const char *)(head->ch_edlibpath + addr); if ((head->ch_cnflags & RTC_HDR_UPM) == 0) { #if defined(_ELF64) str = conv_config_upm(str, MSG_ORIG(MSG_PTH_USRLIB_64), MSG_ORIG(MSG_PTH_LIB_64), MSG_PTH_LIB_64_SIZE); #else str = conv_config_upm(str, MSG_ORIG(MSG_PTH_USRLIB), MSG_ORIG(MSG_PTH_LIB), MSG_PTH_LIB_SIZE); #endif } if (expand_paths(lmp, str, &elf_def_dirs, AL_CNT_SEARCH, (LA_SER_DEFAULT | LA_SER_CONFIG), PD_TKN_CAP) != 0) features |= CONF_EDLIBPATH; } if (head->ch_eslibpath) { str = (const char *)(head->ch_eslibpath + addr); if ((head->ch_cnflags & RTC_HDR_UPM) == 0) { #if defined(_ELF64) str = conv_config_upm(str, MSG_ORIG(MSG_PTH_USRLIBSE_64), MSG_ORIG(MSG_PTH_LIBSE_64), MSG_PTH_LIBSE_64_SIZE); #else str = conv_config_upm(str, MSG_ORIG(MSG_PTH_USRLIBSE), MSG_ORIG(MSG_PTH_LIBSE), MSG_PTH_LIBSE_SIZE); #endif } if (expand_paths(lmp, str, &elf_sec_dirs, AL_CNT_SEARCH, (LA_SER_SECURE | LA_SER_CONFIG), PD_TKN_CAP) != 0) features |= CONF_ESLIBPATH; } /* * Apply any environment variables. This attribute was added with * RTC_VER_THREE. */ if ((head->ch_version >= RTC_VER_THREE) && head->ch_env && (!(rtld_flags & RT_FL_NOENVCFG))) { if (readenv_config((Rtc_env *)(head->ch_env + addr), addr) != 0) return (-1); features |= CONF_ENVS; } /* * Determine whether filter/filtee associations are available. */ if ((head->ch_version >= RTC_VER_FOUR) && head->ch_fltr && (!(rtld_flags2 & RT_FL2_NOFLTCFG))) { rtld_flags2 |= RT_FL2_FLTCFG; config->c_fltr = (Rtc_fltr *)(head->ch_fltr + addr); config->c_flte = (Rtc_flte *)(head->ch_flte + addr); features |= CONF_FLTR; } /* * Determine whether directory configuration is available. */ if ((!(rtld_flags & RT_FL_NODIRCFG)) && head->ch_hash) { config->c_hashtbl = (Word *)(head->ch_hash + addr); config->c_hashchain = &config->c_hashtbl[2 + config->c_hashtbl[0]]; config->c_objtbl = (Rtc_obj *)(head->ch_obj + addr); config->c_strtbl = (const char *)(head->ch_str + addr); rtld_flags |= RT_FL_DIRCFG; features |= CONF_DIRCFG; } /* * Determine whether alternative objects are specified or an object * reservation area is required. If the reservation can't be completed * (either because the configuration information is out-of-date, or the * the reservation can't be allocated), then alternative objects are * ignored. */ if ((!(rtld_flags & (RT_FL_NODIRCFG | RT_FL_NOOBJALT))) && (head->ch_cnflags & RTC_HDR_ALTER)) { rtld_flags |= RT_FL_OBJALT; features |= CONF_OBJALT; elf_config_validate(addr, head, lmp); if (head->ch_resbgn) { if (((config->c_bgn <= head->ch_resbgn) && (config->c_bgn >= head->ch_resend)) || (nu_map(LIST(lmp), (caddr_t)(uintptr_t)head->ch_resbgn, (head->ch_resend - head->ch_resbgn), PROT_NONE, MAP_FIXED | MAP_PRIVATE) == MAP_FAILED)) return (-1); rtld_flags |= RT_FL_MEMRESV; features |= CONF_MEMRESV; } } return (features); } /* * Determine whether the given file exists in the configuration file. */ Rtc_obj * elf_config_ent(const char *name, Word hash, int id, const char **alternate) { Word bkt, ndx; const char *str; Rtc_obj *obj; bkt = hash % config->c_hashtbl[0]; ndx = config->c_hashtbl[2 + bkt]; while (ndx) { obj = config->c_objtbl + ndx; str = config->c_strtbl + obj->co_name; if ((obj->co_hash != hash) || (strcmp(name, str) != 0) || (id && (id != obj->co_id))) { ndx = config->c_hashchain[ndx]; continue; } if ((obj->co_flags & RTC_OBJ_ALTER) && alternate) *alternate = config->c_strtbl + obj->co_alter; return (obj); } return (0); } /* * Determine whether a filter and filtee string pair exists in the configuration * file. If so, return the cached filtees that are associated with this pair as * an Alist. */ void elf_config_flt(Lm_list *lml, const char *filter, const char *string, Alist **alpp, Aliste alni) { Rtc_fltr *fltrtbl; for (fltrtbl = (Rtc_fltr *)config->c_fltr; fltrtbl->fr_filter; fltrtbl++) { Rtc_flte *fltetbl; const char *fltr, *str; fltr = config->c_strtbl + fltrtbl->fr_filter; str = config->c_strtbl + fltrtbl->fr_string; if (strcmp(filter, fltr) || strcmp(string, str)) continue; /* * Create a path descriptor for each filtee associated with this * filter/filtee string pair. Note, no expansion of filtee * entries is called for, as any original expansion would have * been carried out before they were recorded in the * configuration file. */ /* LINTED */ for (fltetbl = (Rtc_flte *)((char *)config->c_flte + fltrtbl->fr_filtee); fltetbl->fe_filtee; fltetbl++) { const char *flte; Pdesc *pdp; flte = config->c_strtbl + fltetbl->fe_filtee; if ((pdp = alist_append(alpp, NULL, sizeof (Pdesc), alni)) == NULL) return; pdp->pd_pname = (char *)flte; pdp->pd_plen = strlen(flte) + 1; pdp->pd_flags = LA_SER_CONFIG; DBG_CALL(Dbg_file_filter(lml, fltr, flte, 1)); } } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_elf.h" #include "msg.h" static int dbg_fd; /* debugging output file descriptor */ static dev_t dbg_dev; static rtld_ino_t dbg_ino; static int dbg_add_pid; /* True to add pid to debug file name */ static pid_t pid; /* * Enable diagnostic output. All debugging functions reside in the linker * debugging library liblddbg.so which is lazy loaded when required. */ int dbg_setup(const char *options, Dbg_desc *dbp) { rtld_stat_t status; const char *ofile; /* * If we're running secure, only allow debugging if ld.so.1 itself is * owned by root and has its mode setuid. Fail silently. */ if ((rtld_flags & RT_FL_SECURE) && (is_rtld_setuid() == 0)) return (1); /* * As Dbg_setup() will effectively lazy load the necessary support * libraries, make sure ld.so.1 is initialized for plt relocations. */ if (elf_rtld_load() == 0) return (1); /* * Call the debugging setup routine. This function verifies the * debugging tokens provided and returns a mask indicating the debugging * categories selected. The mask effectively enables calls to the * debugging library. */ if (Dbg_setup(DBG_CALLER_RTLD, options, dbp, &ofile) == 0) return (0); /* * Obtain the process id. */ pid = getpid(); /* * If an LD_DEBUG_OUTPUT file was specified then we need to direct all * diagnostics to the specified file. Add the process id as a file * suffix so that multiple processes that inherit the same debugging * environment variable don't fight over the same file. * * If LD_DEBUG_OUTPUT is not specified, and the output=file token * was, then we direct all diagnostics to that file. Unlike * LD_DEBUG_OUTPUT, we do not add the process id suffix. This * is more convenient for interactive use. * * If neither redirection option is present, we send debugging * output to stderr. Note that the caller will not be able * to pipe or redirect this output at the shell level. libc * has not yet initialized things to make that possible. */ if (dbg_file == NULL) { if (ofile && (*ofile != '\0')) dbg_file = ofile; } else { dbg_add_pid = 1; } if (dbg_file) { char _file[MAXPATHLEN]; const char *file; if (dbg_add_pid) { file = _file; (void) snprintf(_file, MAXPATHLEN, MSG_ORIG(MSG_DBG_FILE), dbg_file, pid); } else { file = dbg_file; } dbg_fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 0666); if (dbg_fd == -1) { int err = errno; eprintf(&lml_rtld, ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), file, strerror(err)); dbp->d_class = 0; return (0); } } else { /* * The default is to direct debugging to the stderr. */ dbg_fd = 2; } /* * Initialize the dev/inode pair to enable us to determine if * the debugging file descriptor is still available once the * application has been entered. */ (void) rtld_fstat(dbg_fd, &status); dbg_dev = status.st_dev; dbg_ino = status.st_ino; /* * Now that the output file is established, identify the linker * package, and generate help output if the user specified the * debug help token. */ Dbg_version(); if (dbp->d_extra & DBG_E_HELP) Dbg_help(); return (1); } /* * Return True (1) if dbg_print() should produce output for the * specified link-map list, and False (0) otherwise. */ static int dbg_lmid_validate(Lm_list *lml) { const char *str; Aliste idx; /* * The LDSO link-map list is a special case, requiring * an explicit user request. */ if (lml->lm_flags & LML_FLG_RTLDLM) return ((dbg_desc->d_extra & DBG_E_LMID_LDSO) != 0); /* * Approve special cases: * - The link-map list has no name * - lmid=all was set * - lmid=alt was set, and this is not the BASE linkmap */ if ((lml->lm_lmidstr == NULL) || ((dbg_desc->d_extra & DBG_E_LMID_ALL) != 0) || (((dbg_desc->d_extra & DBG_E_LMID_ALT) != 0) && ((lml->lm_flags & LML_FLG_BASELM) == 0))) return (1); /* * If there is no list of specific link-map list names to check, * then approval depends on lmid={ldso|alt} not being specified. */ if (aplist_nitems(dbg_desc->d_list) == 0) return ((dbg_desc->d_extra & (DBG_E_LMID_LDSO | DBG_E_LMID_ALT)) == 0); /* * Compare the link-map list name against the list of approved names */ for (APLIST_TRAVERSE(dbg_desc->d_list, idx, str)) if (strcmp(lml->lm_lmidstr, str) == 0) return (1); /* Output for this linkmap is denied */ return (0); } /* * All diagnostic requests are funneled to this routine. */ /* PRINTFLIKE2 */ void dbg_print(Lm_list *lml, const char *format, ...) { va_list args; char buffer[ERRSIZE + 1]; pid_t _pid; rtld_stat_t status; Prfbuf prf; /* * Knock off any newline indicator to signify that a diagnostic has * been processed. */ dbg_desc->d_extra &= ~DBG_E_STDNL; /* * If debugging has been isolated to individual link-map lists, * determine whether this request originates from a link-map list that * is being monitored. */ if (lml && (dbg_lmid_validate(lml) == 0)) return; /* * If we're in the application make sure the debugging file descriptor * is still available (ie, the user hasn't closed and/or reused the * same descriptor). */ if (rtld_flags & RT_FL_APPLIC) { if ((rtld_fstat(dbg_fd, &status) == -1) || (status.st_dev != dbg_dev) || (status.st_ino != dbg_ino)) { if (dbg_file) { /* * If the user specified output file has been * disconnected try and reconnect to it. */ char _file[MAXPATHLEN]; const char *file; if (dbg_add_pid) { file = _file; (void) snprintf(_file, MAXPATHLEN, MSG_ORIG(MSG_DBG_FILE), dbg_file, pid); } else { file = dbg_file; } if ((dbg_fd = open(file, (O_RDWR | O_APPEND), 0)) == -1) { dbg_desc->d_class = 0; return; } (void) rtld_fstat(dbg_fd, &status); dbg_dev = status.st_dev; dbg_ino = status.st_ino; } else { /* * If stderr has been stolen from us simply * turn debugging off. */ dbg_desc->d_class = 0; return; } } } prf.pr_fd = dbg_fd; /* * Obtain the process id. */ _pid = getpid(); /* * Each time ld.so.1 is entered, the diagnostic times are reset. It is * useful to convey this reset as part of our diagnostics, but only if * other diagnostics will follow. If a reset has preceded this * diagnostic, print a division line. */ if (DBG_ISRESET()) { DBG_OFFRESET(); prf.pr_buf = prf.pr_cur = buffer; prf.pr_len = ERRSIZE; if (lml) (void) bufprint(&prf, MSG_ORIG(MSG_DBG_PID), _pid); else (void) bufprint(&prf, MSG_ORIG(MSG_DBG_UNDEF)); prf.pr_cur--; (void) bufprint(&prf, MSG_ORIG(MSG_DBG_RESET)); (void) dowrite(&prf); } /* * Reestablish the buffer for standard printing. */ prf.pr_buf = prf.pr_cur = buffer; prf.pr_len = ERRSIZE; /* * Establish any diagnostic prefix strings. */ if (lml) (void) bufprint(&prf, MSG_ORIG(MSG_DBG_PID), _pid); else (void) bufprint(&prf, MSG_ORIG(MSG_DBG_UNDEF)); prf.pr_cur--; if (DBG_ISLMID() && lml && lml->lm_lmidstr) { (void) bufprint(&prf, MSG_ORIG(MSG_DBG_LMID), lml->lm_lmidstr); prf.pr_cur--; } if (DBG_ISTIME()) { struct timeval new; if (gettimeofday(&new, NULL) == 0) { Conv_time_buf_t buf; if (DBG_ISTTIME()) { (void) bufprint(&prf, conv_time(&DBG_TOTALTIME, &new, &buf)); prf.pr_cur--; } if (DBG_ISDTIME()) { (void) bufprint(&prf, conv_time(&DBG_DELTATIME, &new, &buf)); prf.pr_cur--; } DBG_DELTATIME = new; } } if (rtld_flags & RT_FL_THREADS) { (void) bufprint(&prf, MSG_ORIG(MSG_DBG_THREAD), rt_thr_self()); prf.pr_cur--; } /* * Format the message and print it. */ va_start(args, format); (void) doprf(format, args, &prf); *(prf.pr_cur - 1) = '\n'; (void) dowrite(&prf); va_end(args); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001 by Sun Microsystems, Inc. * All rights reserved. */ #include "_rtld.h" /* * Note: for initialization of librtld_db - it's required that * the r_debug & rtlddb_priv structures be the *only data item* * in the .data section, this is the only way we can gain * full control over where it is placed. This section is in turn * placed at the beginning of ld.so.1's data segment (which * AT_SUN_LDDATA points to :)). */ /* * Private structure for passing of information between librltd_db * and rtld. * * Note: Any data that's being 'exported' to librtld_db must not * require any 'relocations' before it can be examined. That's * because librtld_db will examine this structure before rtld has * started to execute (and before it's relocated itself). So - all * data in this structure must be available at that point. */ struct rtld_db_priv r_debug = { { R_DEBUG_VERSION, /* version no. */ 0, /* r_map */ (unsigned long)rtld_db_dlactivity, /* r_brk */ RT_CONSISTENT, /* r_state */ 0, /* r_ldbase */ 0, /* r_ldsomap */ RD_NONE, /* r_rdevent */ RD_FL_NONE /* r_flags */ }, R_RTLDDB_VERSION, /* rtd_version */ 0, /* rtd_objpad */ 0 /* rtd_dynlmlst */ }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1988 AT&T * All Rights Reserved * * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, Joyent, Inc. All rights reserved. */ /* * Programmatic interface to the run_time linker. */ #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" #include "_inline_gen.h" #include "msg.h" /* * Determine who called us - given a pc determine in which object it resides. * * For dlopen() the link map of the caller must be passed to load_so() so that * the appropriate search rules (4.x or 5.0) are used to locate any * dependencies. Also, if we've been called from a 4.x module it may be * necessary to fix the specified pathname so that it conforms with the 5.0 elf * rules. * * For dlsym() the link map of the caller is used to determine RTLD_NEXT * requests, together with requests based off of a dlopen(0). * For dladdr() this routines provides a generic means of scanning all loaded * segments. */ Rt_map * _caller(caddr_t cpc, int flags) { Lm_list *lml; Aliste idx1; for (APLIST_TRAVERSE(dynlm_list, idx1, lml)) { Aliste idx2; Lm_cntl *lmc; for (ALIST_TRAVERSE(lml->lm_lists, idx2, lmc)) { Rt_map *lmp; for (lmp = lmc->lc_head; lmp; lmp = NEXT_RT_MAP(lmp)) { if (find_segment(cpc, lmp)) return (lmp); } } } /* * No mapping can be determined. If asked for a default, assume this * is from the executable. */ if (flags & CL_EXECDEF) return ((Rt_map *)lml_main.lm_head); return (0); } #pragma weak _dlerror = dlerror /* * External entry for dlerror(3C). Returns a pointer to the string describing * the last occurring error. The last occurring error is cleared. */ char * dlerror() { char *error; Rt_map *clmp; int entry; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); DBG_CALL(Dbg_dl_dlerror(clmp, lasterr)); error = lasterr; lasterr = NULL; if (entry) leave(LIST(clmp), 0); return (error); } /* * Add a dependency as a group descriptor to a group handle. Returns 0 on * failure. On success, returns the group descriptor, and if alep is non-NULL * the *alep is set to ALE_EXISTS if the dependency already exists, or to * ALE_CREATE if the dependency is newly created. */ Grp_desc * hdl_add(Grp_hdl *ghp, Rt_map *lmp, uint_t dflags, int *alep) { Grp_desc *gdp; Aliste idx; int ale = ALE_CREATE; uint_t oflags; /* * Make sure this dependency hasn't already been recorded. */ for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) { if (gdp->gd_depend == lmp) { ale = ALE_EXISTS; break; } } if (ale == ALE_CREATE) { Grp_desc gd; /* * Create a new handle descriptor. */ gd.gd_depend = lmp; gd.gd_flags = 0; /* * Indicate this object is a part of this handles group. */ if (aplist_append(&GROUPS(lmp), ghp, AL_CNT_GROUPS) == NULL) return (NULL); /* * Append the new dependency to this handle. */ if ((gdp = alist_append(&ghp->gh_depends, &gd, sizeof (Grp_desc), AL_CNT_DEPENDS)) == NULL) return (NULL); } oflags = gdp->gd_flags; gdp->gd_flags |= dflags; if (DBG_ENABLED) { if (ale == ALE_CREATE) { DBG_CALL(Dbg_file_hdl_action(ghp, lmp, DBG_DEP_ADD, gdp->gd_flags)); } else if (gdp->gd_flags != oflags) { DBG_CALL(Dbg_file_hdl_action(ghp, lmp, DBG_DEP_UPDATE, gdp->gd_flags)); } } if (alep) *alep = ale; return (gdp); } /* * Create a handle. * * rlmp - represents the reference link-map for which the handle is being * created. * clmp - represents the caller who is requesting the handle. * hflags - provide group handle flags (GPH_*) that affect the use of the * handle, such as dlopen(0), or use or use of RTLD_FIRST. * rdflags - provide group dependency flags for the reference link-map rlmp, * such as whether the dependency can be used for dlsym(), can be * relocated against, or whether this objects dependencies should * be processed. * cdflags - provide group dependency flags for the caller. */ Grp_hdl * hdl_create(Lm_list *lml, Rt_map *rlmp, Rt_map *clmp, uint_t hflags, uint_t rdflags, uint_t cdflags) { Grp_hdl *ghp = NULL, *aghp; APlist **alpp; Aliste idx; /* * For dlopen(0) the handle is maintained as part of the link-map list, * otherwise the handle is associated with the reference link-map. */ if (hflags & GPH_ZERO) alpp = &(lml->lm_handle); else alpp = &(HANDLES(rlmp)); /* * Objects can contain multiple handles depending on the handle flags * supplied. Most RTLD flags pertain to the object itself and the * bindings that it can achieve. Multiple handles for these flags * don't make sense. But if the flag determines how the handle might * be used, then multiple handles may exist. Presently this only makes * sense for RTLD_FIRST. Determine if an appropriate handle already * exists. */ for (APLIST_TRAVERSE(*alpp, idx, aghp)) { if ((aghp->gh_flags & GPH_FIRST) == (hflags & GPH_FIRST)) { ghp = aghp; break; } } if (ghp == NULL) { uint_t ndx; /* * If this is the first request for this handle, allocate and * initialize a new handle. */ DBG_CALL(Dbg_file_hdl_title(DBG_HDL_CREATE)); if ((ghp = malloc(sizeof (Grp_hdl))) == NULL) return (NULL); /* * Associate the handle with the link-map list or the reference * link-map as appropriate. */ if (aplist_append(alpp, ghp, AL_CNT_GROUPS) == NULL) { free(ghp); return (NULL); } /* * Record the existence of this handle for future verification. */ /* LINTED */ ndx = (uintptr_t)ghp % HDLIST_SZ; if (aplist_append(&hdl_alp[ndx], ghp, AL_CNT_HANDLES) == NULL) { (void) aplist_delete_value(*alpp, ghp); free(ghp); return (NULL); } ghp->gh_depends = NULL; ghp->gh_refcnt = 1; ghp->gh_flags = hflags; /* * A dlopen(0) handle is identified by the GPH_ZERO flag, the * head of the link-map list is defined as the owner. There is * no need to maintain a list of dependencies, for when this * handle is used (for dlsym()) a dynamic search through the * entire link-map list provides for searching all objects with * GLOBAL visibility. */ if (hflags & GPH_ZERO) { ghp->gh_ownlmp = lml->lm_head; ghp->gh_ownlml = lml; } else { ghp->gh_ownlmp = rlmp; ghp->gh_ownlml = LIST(rlmp); if (hdl_add(ghp, rlmp, rdflags, NULL) == NULL) return (NULL); /* * If this new handle is a private handle, there's no * need to track the caller, so we're done. */ if (hflags & GPH_PRIVATE) return (ghp); /* * If this new handle is public, and isn't a special * handle representing ld.so.1, indicate that a local * group now exists. This state allows singleton * searches to be optimized. */ if ((hflags & GPH_LDSO) == 0) LIST(rlmp)->lm_flags |= LML_FLG_GROUPSEXIST; } } else { /* * If a handle already exists, bump its reference count. * * If the previous reference count was 0, then this is a handle * that an earlier call to dlclose() was unable to remove. Such * handles are put on the orphan list. As this handle is back * in use, it must be removed from the orphan list. * * Note, handles associated with a link-map list itself (i.e. * dlopen(0)) can have a reference count of 0. However, these * handles are never deleted, and therefore are never moved to * the orphan list. */ if ((ghp->gh_refcnt++ == 0) && ((ghp->gh_flags & GPH_ZERO) == 0)) { uint_t ndx; /* LINTED */ ndx = (uintptr_t)ghp % HDLIST_SZ; (void) aplist_delete_value(hdl_alp[HDLIST_ORP], ghp); (void) aplist_append(&hdl_alp[ndx], ghp, AL_CNT_HANDLES); if (DBG_ENABLED) { Aliste idx; Grp_desc *gdp; DBG_CALL(Dbg_file_hdl_title(DBG_HDL_REINST)); for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) DBG_CALL(Dbg_file_hdl_action(ghp, gdp->gd_depend, DBG_DEP_REINST, 0)); } } /* * If we've been asked to create a private handle, there's no * need to track the caller. */ if (hflags & GPH_PRIVATE) { /* * Negate the reference count increment. */ ghp->gh_refcnt--; return (ghp); } else { /* * If a private handle already exists, promote this * handle to public by initializing both the reference * count and the handle flags. */ if (ghp->gh_flags & GPH_PRIVATE) { ghp->gh_refcnt = 1; ghp->gh_flags &= ~GPH_PRIVATE; ghp->gh_flags |= hflags; } } } /* * Keep track of the parent (caller). As this object can be referenced * by different parents, this processing is carried out every time a * handle is requested. */ if (clmp && (hdl_add(ghp, clmp, cdflags, NULL) == NULL)) return (NULL); return (ghp); } /* * Initialize a handle that has been created for an object that is already * loaded. The handle is initialized with the present dependencies of that * object. Once this initialization has occurred, any new objects that might * be loaded as dependencies (lazy-loading) are added to the handle as each new * object is loaded. */ int hdl_initialize(Grp_hdl *ghp, Rt_map *nlmp, int mode, int promote) { Aliste idx; Grp_desc *gdp; /* * If the handle has already been initialized, and the initial object's * mode hasn't been promoted, there's no need to recompute the modes of * any dependencies. If the object we've added has just been opened, * the objects dependencies will not yet have been processed. These * dependencies will be added on later calls to load_one(). Otherwise, * this object already exists, so add all of its dependencies to the * handle were operating on. */ if (((ghp->gh_flags & GPH_INITIAL) && (promote == 0)) || ((FLAGS(nlmp) & FLG_RT_ANALYZED) == 0)) { ghp->gh_flags |= GPH_INITIAL; return (1); } DBG_CALL(Dbg_file_hdl_title(DBG_HDL_ADD)); for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) { Rt_map *lmp = gdp->gd_depend; Aliste idx1; Bnd_desc *bdp; /* * If this dependency doesn't indicate that its dependencies * should be added to a handle, ignore it. This case identifies * a parent of a dlopen(RTLD_PARENT) request. */ if ((gdp->gd_flags & GPD_ADDEPS) == 0) continue; for (APLIST_TRAVERSE(DEPENDS(lmp), idx1, bdp)) { Rt_map *dlmp = bdp->b_depend; if ((bdp->b_flags & BND_NEEDED) == 0) continue; if (hdl_add(ghp, dlmp, (GPD_DLSYM | GPD_RELOC | GPD_ADDEPS), NULL) == NULL) return (0); (void) update_mode(dlmp, MODE(dlmp), mode); } } ghp->gh_flags |= GPH_INITIAL; return (1); } /* * Sanity check a program-provided handle. */ static int hdl_validate(Grp_hdl *ghp) { Aliste idx; Grp_hdl *lghp; uint_t ndx; /* LINTED */ ndx = (uintptr_t)ghp % HDLIST_SZ; for (APLIST_TRAVERSE(hdl_alp[ndx], idx, lghp)) { if ((lghp == ghp) && (ghp->gh_refcnt != 0)) return (1); } return (0); } /* * Core dlclose activity. */ int dlclose_core(Grp_hdl *ghp, Rt_map *clmp, Lm_list *lml) { int error; Rt_map *lmp; /* * If we're already at atexit() there's no point processing further, * all objects have already been tsorted for fini processing. */ if (rtld_flags & RT_FL_ATEXIT) return (0); /* * Diagnose what we're up to. */ if (ghp->gh_flags & GPH_ZERO) { DBG_CALL(Dbg_dl_dlclose(clmp, MSG_ORIG(MSG_STR_ZERO), DBG_DLCLOSE_IGNORE)); } else { DBG_CALL(Dbg_dl_dlclose(clmp, NAME(ghp->gh_ownlmp), DBG_DLCLOSE_NULL)); } /* * Decrement reference count of this object. */ if (--(ghp->gh_refcnt)) return (0); /* * If this handle is special (dlopen(0)), then leave it around - it * has little overhead. */ if (ghp->gh_flags & GPH_ZERO) return (0); /* * If this handle is associated with an object that is not on the base * link-map control list, or it has not yet been relocated, then this * handle must have originated from an auditors interaction, or some * permutation of RTLD_CONFGEN use (crle(1), moe(1), etc.). User code * can only execute and bind to relocated objects on the base link-map * control list. Outside of RTLD_CONFGEN use, a non-relocated object, * or an object on a non-base link-map control list, is in the process * of being loaded, and therefore we do not attempt to remove the * handle. */ if (((lmp = ghp->gh_ownlmp) != NULL) && ((MODE(lmp) & RTLD_CONFGEN) == 0) && ((CNTL(lmp) != ALIST_OFF_DATA) || ((FLAGS(lmp) & FLG_RT_RELOCED) == 0))) return (0); /* * This handle is no longer being referenced, remove it. If this handle * is part of an alternative link-map list, determine if the whole list * can be removed also. */ error = remove_hdl(ghp, clmp, NULL); if ((lml->lm_flags & (LML_FLG_BASELM | LML_FLG_RTLDLM)) == 0) remove_lml(lml); return (error); } /* * Internal dlclose activity. Called from user level or directly for internal * error cleanup. */ int dlclose_intn(Grp_hdl *ghp, Rt_map *clmp) { Rt_map *nlmp = NULL; Lm_list *olml = NULL; int error; /* * Although we're deleting object(s) it's quite possible that additional * objects get loaded from running the .fini section(s) of the objects * being deleted. These objects will have been added to the same * link-map list as those objects being deleted. Remember this list * for later investigation. */ olml = ghp->gh_ownlml; error = dlclose_core(ghp, clmp, olml); /* * Determine whether the original link-map list still exists. In the * case of a dlclose of an alternative (dlmopen) link-map the whole * list may have been removed. */ if (olml) { Aliste idx; Lm_list *lml; for (APLIST_TRAVERSE(dynlm_list, idx, lml)) { if (olml == lml) { nlmp = olml->lm_head; break; } } } load_completion(nlmp); return (error); } /* * Argument checking for dlclose. Only called via external entry. */ static int dlclose_check(void *handle, Rt_map *clmp) { Grp_hdl *ghp = (Grp_hdl *)handle; if (hdl_validate(ghp) == 0) { Conv_inv_buf_t inv_buf; (void) conv_invalid_val(&inv_buf, EC_NATPTR(ghp), 0); DBG_CALL(Dbg_dl_dlclose(clmp, inv_buf.buf, DBG_DLCLOSE_NULL)); eprintf(LIST(clmp), ERR_FATAL, MSG_INTL(MSG_ARG_INVHNDL), EC_NATPTR(handle)); return (1); } return (dlclose_intn(ghp, clmp)); } #pragma weak _dlclose = dlclose /* * External entry for dlclose(3C). Returns 0 for success, non-zero otherwise. */ int dlclose(void *handle) { int error, entry; Rt_map *clmp; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); error = dlclose_check(handle, clmp); if (entry) leave(LIST(clmp), 0); return (error); } static uint_t lmid = 0; /* * The addition of new link-map lists is assumed to be in small quantities. * Here, we assign a unique link-map id for diagnostic use. Simply update the * running link-map count until we max out. */ int newlmid(Lm_list *lml) { char buffer[MSG_LMID_ALT_SIZE + 12]; if (lmid == UINT_MAX) { lml->lm_lmid = UINT_MAX; (void) strncpy(buffer, MSG_ORIG(MSG_LMID_MAXED), MSG_LMID_ALT_SIZE + 12); } else { lml->lm_lmid = lmid++; (void) snprintf(buffer, MSG_LMID_ALT_SIZE + 12, MSG_ORIG(MSG_LMID_FMT), MSG_ORIG(MSG_LMID_ALT), lml->lm_lmid); } if ((lml->lm_lmidstr = strdup(buffer)) == NULL) return (0); return (1); } /* * Core dlopen activity. */ static Grp_hdl * dlmopen_core(Lm_list *lml, Lm_list *olml, const char *path, int mode, Rt_map *clmp, uint_t flags, uint_t orig, int *in_nfavl) { Alist *palp = NULL; Rt_map *nlmp; Grp_hdl *ghp; Aliste olmco, nlmco; DBG_CALL(Dbg_dl_dlopen(clmp, (path ? path : MSG_ORIG(MSG_STR_ZERO)), in_nfavl, mode)); /* * Having diagnosed the originally defined modes, assign any defaults * or corrections. */ if (((mode & (RTLD_GROUP | RTLD_WORLD)) == 0) && ((mode & RTLD_NOLOAD) == 0)) mode |= (RTLD_GROUP | RTLD_WORLD); if ((mode & RTLD_NOW) && (rtld_flags2 & RT_FL2_BINDLAZY)) { mode &= ~RTLD_NOW; mode |= RTLD_LAZY; } /* * If the path specified is null then we're operating on global * objects. Associate a dummy handle with the link-map list. */ if (path == NULL) { Grp_hdl *ghp; uint_t hflags, rdflags, cdflags; int promote = 0; /* * Establish any flags for the handle (Grp_hdl). * * - This is a dummy, public, handle (0) that provides for a * dynamic search of all global objects within the process. * - Use of the RTLD_FIRST mode indicates that only the first * dependency on the handle (the referenced object) can be * used to satisfy dlsym() requests. */ hflags = (GPH_PUBLIC | GPH_ZERO); if (mode & RTLD_FIRST) hflags |= GPH_FIRST; /* * Establish the flags for the referenced dependency descriptor * (Grp_desc). * * - The referenced object is available for dlsym(). * - The referenced object is available to relocate against. * - The referenced object should have it's dependencies * added to this handle. */ rdflags = (GPD_DLSYM | GPD_RELOC | GPD_ADDEPS); /* * Establish the flags for this callers dependency descriptor * (Grp_desc). * * - The explicit creation of a handle creates a descriptor * for the referenced object and the parent (caller). * - Use of the RTLD_PARENT flag indicates that the parent * can be relocated against. */ cdflags = GPD_PARENT; if (mode & RTLD_PARENT) cdflags |= GPD_RELOC; if ((ghp = hdl_create(lml, 0, clmp, hflags, rdflags, cdflags)) == NULL) return (NULL); /* * Traverse the main link-map control list, updating the mode * of any objects as necessary. Call the relocation engine if * this mode promotes the existing state of any relocations. * crle()'s first pass loads all objects necessary for building * a configuration file, however none of them are relocated. * crle()'s second pass relocates objects in preparation for * dldump()'ing using dlopen(0, RTLD_NOW). */ if ((mode & (RTLD_NOW | RTLD_CONFGEN)) == RTLD_CONFGEN) return (ghp); for (nlmp = lml->lm_head; nlmp; nlmp = NEXT_RT_MAP(nlmp)) { if (((MODE(nlmp) & RTLD_GLOBAL) == 0) || (FLAGS(nlmp) & FLG_RT_DELETE)) continue; if (update_mode(nlmp, MODE(nlmp), mode)) promote = 1; } if (promote) (void) relocate_lmc(lml, ALIST_OFF_DATA, clmp, lml->lm_head, in_nfavl); return (ghp); } /* * Fix the pathname. If this object expands to multiple paths (ie. * $ISALIST or $HWCAP have been used), then make sure the user has also * furnished the RTLD_FIRST flag. As yet, we don't support opening * more than one object at a time, so enforcing the RTLD_FIRST flag * provides flexibility should we be able to support dlopening more * than one object in the future. */ if (LM_FIX_NAME(clmp)(path, clmp, &palp, AL_CNT_NEEDED, orig) == 0) return (NULL); if ((palp->al_arritems > 1) && ((mode & RTLD_FIRST) == 0)) { remove_alist(&palp, 1); eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLMODE_5)); return (NULL); } /* * Establish a link-map control list for this request, and load the * associated object. */ if ((nlmco = create_cntl(lml, 1)) == 0) { remove_alist(&palp, 1); return (NULL); } olmco = nlmco; nlmp = load_one(lml, nlmco, palp, clmp, mode, (flags | FLG_RT_PUBHDL), &ghp, in_nfavl); /* * Remove any expanded pathname infrastructure, and if the dependency * couldn't be loaded, cleanup. */ remove_alist(&palp, 1); if (nlmp == NULL) { remove_cntl(lml, olmco); return (NULL); } /* * If loading an auditor was requested, and the auditor already existed, * then the link-map returned will be to the original auditor. The new * link-map list that was initially created, and the associated link-map * control list are no longer needed. As the auditor is already loaded, * we're probably done, but fall through in case additional relocations * would be triggered by the mode of the caller. */ if ((flags & FLG_RT_AUDIT) && (LIST(nlmp) != lml)) { remove_cntl(lml, olmco); lml = LIST(nlmp); olmco = 0; nlmco = ALIST_OFF_DATA; } /* * Finish processing the objects associated with this request. */ if (((nlmp = analyze_lmc(lml, nlmco, nlmp, clmp, in_nfavl)) == NULL) || (relocate_lmc(lml, nlmco, clmp, nlmp, in_nfavl) == 0)) { ghp = NULL; nlmp = NULL; } /* * If the dlopen has failed, clean up any objects that might have been * loaded successfully on this new link-map control list. */ if (olmco && (nlmp == NULL)) remove_lmc(lml, clmp, olmco, path); /* * Finally, remove any temporary link-map control list. Note, if this * operation successfully established a new link-map list, then a base * link-map control list will have been created, which must remain. */ if (olmco && ((nlmp == NULL) || (olml != (Lm_list *)LM_ID_NEWLM))) remove_cntl(lml, olmco); return (ghp); } /* * dlopen() and dlsym() operations are the means by which a process can * test for the existence of required dependencies. If the necessary * dependencies don't exist, then associated functionality can't be used. * However, the lack of dependencies can be fixed, and the dlopen() and * dlsym() requests can be repeated. As we use a "not-found" AVL tree to * cache any failed full path loads, secondary dlopen() and dlsym() requests * will fail, even if the dependencies have been installed. * * dlopen() and dlsym() retry any failures by removing the "not-found" AVL * tree. Should any dependencies be found, their names are added to the * FullPath AVL tree. This routine removes any new "not-found" AVL tree, * so that the dlopen() or dlsym() can replace the original "not-found" tree. */ inline static void nfavl_remove(avl_tree_t *avlt) { PathNode *pnp; void *cookie = NULL; if (avlt) { while ((pnp = avl_destroy_nodes(avlt, &cookie)) != NULL) free(pnp); avl_destroy(avlt); free(avlt); } } /* * Internal dlopen() activity. Called from user level or directly for internal * opens that require a handle. */ Grp_hdl * dlmopen_intn(Lm_list *lml, const char *path, int mode, Rt_map *clmp, uint_t flags, uint_t orig) { Lm_list *olml = lml; Rt_map *dlmp = NULL; Grp_hdl *ghp; int in_nfavl = 0; /* * Check for magic link-map list values: * * LM_ID_BASE: Operate on the PRIMARY (executables) link map * LM_ID_LDSO: Operation on ld.so.1's link map * LM_ID_NEWLM: Create a new link-map. */ if (lml == (Lm_list *)LM_ID_NEWLM) { if ((lml = calloc(1, sizeof (Lm_list))) == NULL) return (NULL); /* * Establish the new link-map flags from the callers and those * explicitly provided. */ lml->lm_tflags = LIST(clmp)->lm_tflags; if (flags & FLG_RT_AUDIT) { /* * Unset any auditing flags - an auditor shouldn't be * audited. Insure all audit dependencies are loaded. */ lml->lm_tflags &= ~LML_TFLG_AUD_MASK; lml->lm_tflags |= (LML_TFLG_NOLAZYLD | LML_TFLG_LOADFLTR | LML_TFLG_NOAUDIT); } if (aplist_append(&dynlm_list, lml, AL_CNT_DYNLIST) == NULL) { free(lml); return (NULL); } if (newlmid(lml) == 0) { (void) aplist_delete_value(dynlm_list, lml); free(lml); return (NULL); } } else if ((uintptr_t)lml < LM_ID_NUM) { if ((uintptr_t)lml == LM_ID_BASE) lml = &lml_main; else if ((uintptr_t)lml == LM_ID_LDSO) lml = &lml_rtld; } /* * Open the required object on the associated link-map list. */ ghp = dlmopen_core(lml, olml, path, mode, clmp, flags, orig, &in_nfavl); /* * If the object could not be found it is possible that the "not-found" * AVL tree had indicated that the file does not exist. In case the * file system has changed since this "not-found" recording was made, * retry the dlopen() with a clean "not-found" AVL tree. */ if ((ghp == NULL) && in_nfavl) { avl_tree_t *oavlt = nfavl; nfavl = NULL; ghp = dlmopen_core(lml, olml, path, mode, clmp, flags, orig, NULL); /* * If the file is found, then its full path name will have been * registered in the FullPath AVL tree. Remove any new * "not-found" AVL information, and restore the former AVL tree. */ nfavl_remove(nfavl); nfavl = oavlt; } /* * Establish the new link-map from which .init processing will begin. * Ignore .init firing when constructing a configuration file (crle(1)). */ if (ghp && ((mode & RTLD_CONFGEN) == 0)) dlmp = ghp->gh_ownlmp; /* * If loading an auditor was requested, and the auditor already existed, * then the link-map returned will be to the original auditor. Remove * the link-map control list that was created for this request. */ if (dlmp && (flags & FLG_RT_AUDIT) && (LIST(dlmp) != lml)) { remove_lml(lml); lml = LIST(dlmp); } /* * If this load failed, remove any alternative link-map list. */ if ((ghp == NULL) && ((lml->lm_flags & (LML_FLG_BASELM | LML_FLG_RTLDLM)) == 0)) { remove_lml(lml); lml = NULL; } /* * Finish this load request. If objects were loaded, .init processing * is computed. Finally, the debuggers are informed of the link-map * lists being stable. */ load_completion(dlmp); return (ghp); } /* * Argument checking for dlopen. Only called via external entry. */ static Grp_hdl * dlmopen_check(Lm_list *lml, const char *path, int mode, Rt_map *clmp) { /* * Verify that a valid pathname has been supplied. */ if (path && (*path == '\0')) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLPATH)); return (0); } /* * Historically we've always verified the mode is either RTLD_NOW or * RTLD_LAZY. RTLD_NOLOAD is valid by itself. Use of LM_ID_NEWLM * requires a specific pathname, and use of RTLD_PARENT is meaningless. */ if ((mode & (RTLD_NOW | RTLD_LAZY | RTLD_NOLOAD)) == 0) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLMODE_1)); return (0); } if ((mode & (RTLD_NOW | RTLD_LAZY)) == (RTLD_NOW | RTLD_LAZY)) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLMODE_2)); return (0); } if ((lml == (Lm_list *)LM_ID_NEWLM) && (path == NULL)) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLMODE_3)); return (0); } if ((lml == (Lm_list *)LM_ID_NEWLM) && (mode & RTLD_PARENT)) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLMODE_4)); return (0); } return (dlmopen_intn(lml, path, mode, clmp, 0, 0)); } #pragma weak _dlopen = dlopen /* * External entry for dlopen(3C). On success, returns a pointer (handle) to * the structure containing information about the newly added object, ie. can * be used by dlsym(). On failure, returns a null pointer. */ void * dlopen(const char *path, int mode) { int entry; Rt_map *clmp; Grp_hdl *ghp; Lm_list *lml; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); lml = LIST(clmp); ghp = dlmopen_check(lml, path, mode, clmp); if (entry) leave(lml, 0); return ((void *)ghp); } #pragma weak _dlmopen = dlmopen /* * External entry for dlmopen(3C). */ void * dlmopen(Lmid_t lmid, const char *path, int mode) { int entry; Rt_map *clmp; Grp_hdl *ghp; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); ghp = dlmopen_check((Lm_list *)lmid, path, mode, clmp); if (entry) leave(LIST(clmp), 0); return ((void *)ghp); } /* * Handle processing for dlsym. */ int dlsym_handle(Grp_hdl *ghp, Slookup *slp, Sresult *srp, uint_t *binfo, int *in_nfavl) { Rt_map *nlmp, * lmp = ghp->gh_ownlmp; Rt_map *clmp = slp->sl_cmap; const char *name = slp->sl_name; Slookup sl = *slp; sl.sl_flags = (LKUP_FIRST | LKUP_DLSYM | LKUP_SPEC); /* * Continue processing a dlsym request. Lookup the required symbol in * each link-map specified by the handle. * * To leverage off of lazy loading, dlsym() requests can result in two * passes. The first descends the link-maps of any objects already in * the address space. If the symbol isn't located, and lazy * dependencies still exist, then a second pass is made to load these * dependencies if applicable. This model means that in the case where * a symbol exists in more than one object, the one located may not be * constant - this is the standard issue with lazy loading. In addition, * attempting to locate a symbol that doesn't exist will result in the * loading of all lazy dependencies on the given handle, which can * defeat some of the advantages of lazy loading (look out JVM). */ if (ghp->gh_flags & GPH_ZERO) { Lm_list *lml; uint_t lazy = 0; /* * If this symbol lookup is triggered from a dlopen(0) handle, * traverse the present link-map list looking for promiscuous * entries. */ for (nlmp = lmp; nlmp; nlmp = NEXT_RT_MAP(nlmp)) { /* * If this handle indicates we're only to look in the * first object check whether we're done. */ if ((nlmp != lmp) && (ghp->gh_flags & GPH_FIRST)) return (0); if (!(MODE(nlmp) & RTLD_GLOBAL)) continue; if ((FLAGS(nlmp) & FLG_RT_DELETE) && ((FLAGS(clmp) & FLG_RT_DELETE) == 0)) continue; sl.sl_imap = nlmp; if (LM_LOOKUP_SYM(clmp)(&sl, srp, binfo, in_nfavl)) return (1); /* * Keep track of any global pending lazy loads. */ lazy += LAZY(nlmp); } /* * If we're unable to locate the symbol and this link-map list * still has pending lazy dependencies, start loading them in an * attempt to exhaust the search. Note that as we're already * traversing a dynamic linked list of link-maps there's no * need for elf_lazy_find_sym() to descend the link-maps itself. */ lml = LIST(lmp); if (lazy) { DBG_CALL(Dbg_syms_lazy_rescan(lml, name)); sl.sl_flags |= LKUP_NODESCENT; for (nlmp = lmp; nlmp; nlmp = NEXT_RT_MAP(nlmp)) { if (!(MODE(nlmp) & RTLD_GLOBAL) || !LAZY(nlmp)) continue; if ((FLAGS(nlmp) & FLG_RT_DELETE) && ((FLAGS(clmp) & FLG_RT_DELETE) == 0)) continue; sl.sl_imap = nlmp; if (elf_lazy_find_sym(&sl, srp, binfo, in_nfavl)) return (1); } } } else { /* * Traverse the dlopen() handle searching all presently loaded * link-maps. */ Grp_desc *gdp; Aliste idx; uint_t lazy = 0; for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) { nlmp = gdp->gd_depend; if ((gdp->gd_flags & GPD_DLSYM) == 0) continue; sl.sl_imap = nlmp; if (LM_LOOKUP_SYM(clmp)(&sl, srp, binfo, in_nfavl)) return (1); if (ghp->gh_flags & GPH_FIRST) return (0); /* * Keep track of any pending lazy loads associated * with this handle. */ lazy += LAZY(nlmp); } /* * If we're unable to locate the symbol and this handle still * has pending lazy dependencies, start loading the lazy * dependencies, in an attempt to exhaust the search. */ if (lazy) { DBG_CALL(Dbg_syms_lazy_rescan(LIST(lmp), name)); for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) { nlmp = gdp->gd_depend; if (((gdp->gd_flags & GPD_DLSYM) == 0) || (LAZY(nlmp) == 0)) continue; sl.sl_imap = nlmp; if (elf_lazy_find_sym(&sl, srp, binfo, in_nfavl)) return (1); } } } return (0); } /* * Determine whether a symbol resides in a caller. This may be a reference, * which is associated with a specific dependency. */ inline static Sym * sym_lookup_in_caller(Rt_map *clmp, Slookup *slp, Sresult *srp, uint_t *binfo) { if (THIS_IS_ELF(clmp) && SYMINTP(clmp)(slp, srp, binfo, NULL)) { Sym *sym = srp->sr_sym; slp->sl_rsymndx = (((ulong_t)sym - (ulong_t)SYMTAB(clmp)) / SYMENT(clmp)); slp->sl_rsym = sym; return (sym); } return (NULL); } /* * Core dlsym activity. Selects symbol lookup method from handle. */ static void * dlsym_core(void *handle, const char *name, Rt_map *clmp, Rt_map **dlmp, int *in_nfavl) { Sym *sym; int ret = 0; Syminfo *sip; Slookup sl; Sresult sr; uint_t binfo; /* * Initialize the symbol lookup data structure. * * Standard relocations are evaluated using the symbol index of the * associated relocation symbol. This index provides for loading * any lazy dependency and establishing a direct binding if necessary. * If a dlsym() operation originates from an object that contains a * symbol table entry for the same name, then we need to establish the * symbol index so that any dependency requirements can be triggered. * * Therefore, the first symbol lookup that is carried out is for the * symbol name within the calling object. If this symbol exists, the * symbols index is computed, added to the Slookup data, and thus used * to seed the real symbol lookup. */ SLOOKUP_INIT(sl, name, clmp, clmp, ld_entry_cnt, elf_hash(name), 0, 0, 0, LKUP_SYMNDX); SRESULT_INIT(sr, name); sym = sym_lookup_in_caller(clmp, &sl, &sr, &binfo); SRESULT_INIT(sr, name); if (sym && (ELF_ST_VISIBILITY(sym->st_other) == STV_SINGLETON)) { Rt_map *hlmp = LIST(clmp)->lm_head; /* * If a symbol reference is known, and that reference indicates * that the symbol is a singleton, then the search for the * symbol must follow the default search path. */ DBG_CALL(Dbg_dl_dlsym(clmp, name, in_nfavl, 0, DBG_DLSYM_SINGLETON)); sl.sl_imap = hlmp; if (handle == RTLD_PROBE) sl.sl_flags = LKUP_NOFALLBACK; else sl.sl_flags = LKUP_SPEC; ret = LM_LOOKUP_SYM(clmp)(&sl, &sr, &binfo, in_nfavl); } else if (handle == RTLD_NEXT) { Rt_map *nlmp; /* * If this handle is RTLD_NEXT determine whether a lazy load * from the caller might provide the next object. This mimics * the lazy loading initialization normally carried out by * lookup_sym(), however here, we must do this up-front, as * lookup_sym() will be used to inspect the next object. */ if ((sl.sl_rsymndx) && ((sip = SYMINFO(clmp)) != NULL)) { /* LINTED */ sip = (Syminfo *)((char *)sip + (sl.sl_rsymndx * SYMINENT(clmp))); if ((sip->si_flags & SYMINFO_FLG_DIRECT) && (sip->si_boundto < SYMINFO_BT_LOWRESERVE)) (void) elf_lazy_load(clmp, &sl, sip->si_boundto, name, 0, NULL, in_nfavl); /* * Clear the symbol index, so as not to confuse * lookup_sym() of the next object. */ sl.sl_rsymndx = 0; sl.sl_rsym = NULL; } /* * If the handle is RTLD_NEXT, start searching in the next link * map from the callers. Determine permissions from the * present link map. Indicate to lookup_sym() that we're on an * RTLD_NEXT request so that it will use the callers link map to * start any possible lazy dependency loading. */ sl.sl_imap = nlmp = NEXT_RT_MAP(clmp); DBG_CALL(Dbg_dl_dlsym(clmp, name, in_nfavl, (nlmp ? NAME(nlmp) : MSG_INTL(MSG_STR_NULL)), DBG_DLSYM_NEXT)); if (nlmp == NULL) return (0); sl.sl_flags = LKUP_NEXT; ret = LM_LOOKUP_SYM(clmp)(&sl, &sr, &binfo, in_nfavl); } else if (handle == RTLD_SELF) { /* * If the handle is RTLD_SELF start searching from the caller. */ DBG_CALL(Dbg_dl_dlsym(clmp, name, in_nfavl, NAME(clmp), DBG_DLSYM_SELF)); sl.sl_imap = clmp; sl.sl_flags = (LKUP_SPEC | LKUP_SELF); ret = LM_LOOKUP_SYM(clmp)(&sl, &sr, &binfo, in_nfavl); } else if (handle == RTLD_DEFAULT) { Rt_map *hlmp = LIST(clmp)->lm_head; /* * If the handle is RTLD_DEFAULT mimic the standard symbol * lookup as would be triggered by a relocation. */ DBG_CALL(Dbg_dl_dlsym(clmp, name, in_nfavl, 0, DBG_DLSYM_DEFAULT)); sl.sl_imap = hlmp; sl.sl_flags = LKUP_SPEC; ret = LM_LOOKUP_SYM(clmp)(&sl, &sr, &binfo, in_nfavl); } else if (handle == RTLD_PROBE) { Rt_map *hlmp = LIST(clmp)->lm_head; /* * If the handle is RTLD_PROBE, mimic the standard symbol * lookup as would be triggered by a relocation, however do * not fall back to a lazy loading rescan if the symbol can't be * found within the currently loaded objects. Note, a lazy * loaded dependency required by the caller might still get * loaded to satisfy this request, but no exhaustive lazy load * rescan is carried out. */ DBG_CALL(Dbg_dl_dlsym(clmp, name, in_nfavl, 0, DBG_DLSYM_PROBE)); sl.sl_imap = hlmp; sl.sl_flags = LKUP_NOFALLBACK; ret = LM_LOOKUP_SYM(clmp)(&sl, &sr, &binfo, in_nfavl); } else { Grp_hdl *ghp = (Grp_hdl *)handle; /* * Look in the shared object specified by the handle and in all * of its dependencies. */ DBG_CALL(Dbg_dl_dlsym(clmp, name, in_nfavl, NAME(ghp->gh_ownlmp), DBG_DLSYM_DEF)); ret = LM_DLSYM(clmp)(ghp, &sl, &sr, &binfo, in_nfavl); } if (ret && ((sym = sr.sr_sym) != NULL)) { Lm_list *lml = LIST(clmp); Addr addr = sym->st_value; *dlmp = sr.sr_dmap; if (!(FLAGS(*dlmp) & FLG_RT_FIXED)) addr += ADDR(*dlmp); /* * Indicate that the defining object is now used. */ if (*dlmp != clmp) FLAGS1(*dlmp) |= FL1_RT_USED; DBG_CALL(Dbg_bind_global(clmp, 0, 0, (Xword)-1, PLT_T_NONE, *dlmp, addr, sym->st_value, sr.sr_name, binfo)); if ((lml->lm_tflags | AFLAGS(clmp) | AFLAGS(*dlmp)) & LML_TFLG_AUD_SYMBIND) { uint_t sb_flags = LA_SYMB_DLSYM; /* LINTED */ uint_t symndx = (uint_t)(((Xword)sym - (Xword)SYMTAB(*dlmp)) / SYMENT(*dlmp)); addr = audit_symbind(clmp, *dlmp, sym, symndx, addr, &sb_flags); } return ((void *)addr); } return (NULL); } /* * Internal dlsym activity. Called from user level or directly for internal * symbol lookup. */ void * dlsym_intn(void *handle, const char *name, Rt_map *clmp, Rt_map **dlmp) { Rt_map *llmp = NULL; void *error; Aliste idx; Grp_desc *gdp; int in_nfavl = 0; /* * While looking for symbols it's quite possible that additional objects * get loaded from lazy loading. These objects will have been added to * the same link-map list as those objects on the handle. Remember this * list for later investigation. */ if ((handle == RTLD_NEXT) || (handle == RTLD_DEFAULT) || (handle == RTLD_SELF) || (handle == RTLD_PROBE)) llmp = LIST(clmp)->lm_tail; else { Grp_hdl *ghp = (Grp_hdl *)handle; if (ghp->gh_ownlmp) llmp = LIST(ghp->gh_ownlmp)->lm_tail; else { for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) { if ((llmp = LIST(gdp->gd_depend)->lm_tail) != NULL) break; } } } error = dlsym_core(handle, name, clmp, dlmp, &in_nfavl); /* * If the symbol could not be found it is possible that the "not-found" * AVL tree had indicated that a required file does not exist. In case * the file system has changed since this "not-found" recording was * made, retry the dlsym() with a clean "not-found" AVL tree. */ if ((error == NULL) && in_nfavl) { avl_tree_t *oavlt = nfavl; nfavl = NULL; error = dlsym_core(handle, name, clmp, dlmp, NULL); /* * If the symbol is found, then any file that was loaded will * have had its full path name registered in the FullPath AVL * tree. Remove any new "not-found" AVL information, and * restore the former AVL tree. */ nfavl_remove(nfavl); nfavl = oavlt; } if (error == NULL) { /* * Cache the error message, as Java tends to fall through this * code many times. */ if (nosym_str == NULL) nosym_str = MSG_INTL(MSG_GEN_NOSYM); eprintf(LIST(clmp), ERR_FATAL, nosym_str, name); } load_completion(llmp); return (error); } /* * Argument checking for dlsym. Only called via external entry. */ static void * dlsym_check(void *handle, const char *name, Rt_map *clmp, Rt_map **dlmp) { /* * Verify the arguments. */ if (name == NULL) { eprintf(LIST(clmp), ERR_FATAL, MSG_INTL(MSG_ARG_ILLSYM)); return (NULL); } if ((handle != RTLD_NEXT) && (handle != RTLD_DEFAULT) && (handle != RTLD_SELF) && (handle != RTLD_PROBE) && (hdl_validate((Grp_hdl *)handle) == 0)) { eprintf(LIST(clmp), ERR_FATAL, MSG_INTL(MSG_ARG_INVHNDL), EC_NATPTR(handle)); return (NULL); } return (dlsym_intn(handle, name, clmp, dlmp)); } #pragma weak _dlsym = dlsym /* * External entry for dlsym(). On success, returns the address of the specified * symbol. On error returns a null. */ void * dlsym(void *handle, const char *name) { int entry; Rt_map *clmp, *dlmp = NULL; void *addr; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); addr = dlsym_check(handle, name, clmp, &dlmp); if (entry) { if (dlmp) is_dep_init(dlmp, clmp); leave(LIST(clmp), 0); } return (addr); } /* * Core dladdr activity. */ static void dladdr_core(Rt_map *almp, void *addr, Dl_info_t *dlip, void **info, int flags) { /* * Set up generic information and any defaults. */ dlip->dli_fname = PATHNAME(almp); dlip->dli_fbase = (void *)ADDR(almp); dlip->dli_sname = NULL; dlip->dli_saddr = NULL; /* * Determine the nearest symbol to this address. */ LM_DLADDR(almp)((ulong_t)addr, almp, dlip, info, flags); } #pragma weak _dladdr = dladdr /* * External entry for dladdr(3C) and dladdr1(3C). Returns an information * structure that reflects the symbol closest to the address specified. */ int dladdr(void *addr, Dl_info_t *dlip) { int entry, ret; Rt_map *clmp, *almp; Lm_list *clml; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); clml = LIST(clmp); DBG_CALL(Dbg_dl_dladdr(clmp, addr)); /* * Use our calling technique to determine what object is associated * with the supplied address. If a caller can't be determined, * indicate the failure. */ if ((almp = _caller(addr, CL_NONE)) == NULL) { eprintf(clml, ERR_FATAL, MSG_INTL(MSG_ARG_INVADDR), EC_NATPTR(addr)); ret = 0; } else { dladdr_core(almp, addr, dlip, 0, 0); ret = 1; } if (entry) leave(clml, 0); return (ret); } #pragma weak _dladdr1 = dladdr1 int dladdr1(void *addr, Dl_info_t *dlip, void **info, int flags) { int entry, ret = 1; Rt_map *clmp, *almp; Lm_list *clml; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); clml = LIST(clmp); DBG_CALL(Dbg_dl_dladdr(clmp, addr)); /* * Validate any flags. */ if (flags) { int request; if (((request = (flags & RTLD_DL_MASK)) != RTLD_DL_SYMENT) && (request != RTLD_DL_LINKMAP)) { eprintf(clml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLFLAGS), flags); ret = 0; } else if (info == NULL) { eprintf(clml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLINFO), flags); ret = 0; } } /* * Use our calling technique to determine what object is associated * with the supplied address. If a caller can't be determined, * indicate the failure. */ if (ret) { if ((almp = _caller(addr, CL_NONE)) == NULL) { eprintf(clml, ERR_FATAL, MSG_INTL(MSG_ARG_INVADDR), EC_NATPTR(addr)); ret = 0; } else dladdr_core(almp, addr, dlip, info, flags); } if (entry) leave(clml, 0); return (ret); } /* * Core dldump activity. */ static int dldump_core(Rt_map *clmp, Rt_map *lmp, const char *ipath, const char *opath, int flags) { Lm_list *lml = LIST(clmp); Addr addr = 0; /* * Verify any arguments first. */ if ((opath == NULL) || (opath[0] == '\0') || ((lmp == NULL) && (ipath[0] == '\0'))) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLPATH)); return (1); } /* * If an input file is specified make sure its one of our dependencies * on the main link-map list. Note, this has really all evolved for * crle(), which uses libcrle.so on an alternative link-map to trigger * dumping objects from the main link-map list. If we ever want to * dump objects from alternative link-maps, this model is going to * have to be revisited. */ if (lmp == NULL) { if ((lmp = is_so_loaded(&lml_main, ipath, NULL)) == NULL) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_GEN_NOFILE), ipath); return (1); } if (FLAGS(lmp) & FLG_RT_ALTER) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_GEN_ALTER), ipath); return (1); } if (FLAGS(lmp) & FLG_RT_NODUMP) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_GEN_NODUMP), ipath); return (1); } } /* * If the object being dump'ed isn't fixed identify its mapping. */ if (!(FLAGS(lmp) & FLG_RT_FIXED)) addr = ADDR(lmp); /* * As rt_dldump() will effectively lazy load the necessary support * libraries, make sure ld.so.1 is initialized for plt relocations. */ if (elf_rtld_load() == 0) return (0); /* * Dump the required image. */ return (rt_dldump(lmp, opath, flags, addr)); } #pragma weak _dldump = dldump /* * External entry for dldump(3c). Returns 0 on success, non-zero otherwise. */ int dldump(const char *ipath, const char *opath, int flags) { int error, entry; Rt_map *clmp, *lmp; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); if (ipath) { lmp = NULL; } else { lmp = lml_main.lm_head; ipath = NAME(lmp); } DBG_CALL(Dbg_dl_dldump(clmp, ipath, opath, flags)); error = dldump_core(clmp, lmp, ipath, opath, flags); if (entry) leave(LIST(clmp), 0); return (error); } /* * get_linkmap_id() translates Lm_list * pointers to the Link_map id as used by * the rtld_db and dlmopen() interfaces. It checks to see if the Link_map is * one of the primary ones and if so returns it's special token: * LM_ID_BASE * LM_ID_LDSO * * If it's not one of the primary link_map id's it will instead returns a * pointer to the Lm_list structure which uniquely identifies the Link_map. */ Lmid_t get_linkmap_id(Lm_list *lml) { if (lml->lm_flags & LML_FLG_BASELM) return (LM_ID_BASE); if (lml->lm_flags & LML_FLG_RTLDLM) return (LM_ID_LDSO); return ((Lmid_t)lml); } /* * Set a new deferred dependency name. */ static int set_def_need(Lm_list *lml, Dyninfo *dyip, const char *name) { /* * If this dependency has already been established, then this dlinfo() * call is too late. */ if (dyip->di_info) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_DEF_DEPLOADED), dyip->di_name); return (-1); } /* * Assign the new dependency name. */ DBG_CALL(Dbg_file_deferred(lml, dyip->di_name, name)); dyip->di_flags |= FLG_DI_DEF_DONE; dyip->di_name = name; return (0); } /* * Extract information for a dlopen() handle. */ static int dlinfo_core(void *handle, int request, void *p, Rt_map *clmp) { Conv_inv_buf_t inv_buf; char *handlename; Lm_list *lml = LIST(clmp); Rt_map *lmp = NULL; /* * Determine whether a handle is provided. A handle isn't needed for * all operations, but it is validated here for the initial diagnostic. */ if (handle == RTLD_SELF) { lmp = clmp; } else { Grp_hdl *ghp = (Grp_hdl *)handle; if (hdl_validate(ghp)) lmp = ghp->gh_ownlmp; } if (lmp) { handlename = NAME(lmp); } else { (void) conv_invalid_val(&inv_buf, EC_NATPTR(handle), 0); handlename = inv_buf.buf; } DBG_CALL(Dbg_dl_dlinfo(clmp, handlename, request, p)); /* * Validate the request and return buffer. */ if ((request > RTLD_DI_MAX) || (p == NULL)) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ILLVAL)); return (-1); } /* * Return configuration cache name and address. */ if (request == RTLD_DI_CONFIGADDR) { Dl_info_t *dlip = (Dl_info_t *)p; if ((config->c_name == NULL) || (config->c_bgn == 0) || (config->c_end == 0)) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_NOCONFIG)); return (-1); } dlip->dli_fname = config->c_name; dlip->dli_fbase = (void *)config->c_bgn; return (0); } /* * Return profiled object name (used by ldprof audit library). */ if (request == RTLD_DI_PROFILENAME) { if (profile_name == NULL) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_NOPROFNAME)); return (-1); } *(const char **)p = profile_name; return (0); } if (request == RTLD_DI_PROFILEOUT) { /* * If a profile destination directory hasn't been specified * provide a default. */ if (profile_out == NULL) profile_out = MSG_ORIG(MSG_PTH_VARTMP); *(const char **)p = profile_out; return (0); } /* * Obtain or establish a termination signal. */ if (request == RTLD_DI_GETSIGNAL) { *(int *)p = killsig; return (0); } if (request == RTLD_DI_SETSIGNAL) { sigset_t set; int sig = *(int *)p; /* * Determine whether the signal is in range. */ (void) sigfillset(&set); if (sigismember(&set, sig) != 1) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_INVSIG), sig); return (-1); } killsig = sig; return (0); } /* * For any other request a link-map is required. Verify the handle. */ if (lmp == NULL) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_INVHNDL), EC_NATPTR(handle)); return (-1); } /* * Obtain the process arguments, environment and auxv. Note, as the * environment can be modified by the user (putenv(3c)), reinitialize * the environment pointer on each request. */ if (request == RTLD_DI_ARGSINFO) { Dl_argsinfo_t *aip = (Dl_argsinfo_t *)p; Lm_list *lml = LIST(lmp); *aip = argsinfo; if (lml->lm_flags & LML_FLG_ENVIRON) aip->dla_envp = *(lml->lm_environ); return (0); } /* * Return Lmid_t of the Link-Map list that the specified object is * loaded on. */ if (request == RTLD_DI_LMID) { *(Lmid_t *)p = get_linkmap_id(LIST(lmp)); return (0); } /* * Return a pointer to the Link-Map structure associated with the * specified object. */ if (request == RTLD_DI_LINKMAP) { *(Link_map **)p = (Link_map *)lmp; return (0); } /* * Return search path information, or the size of the buffer required * to store the information. */ if ((request == RTLD_DI_SERINFO) || (request == RTLD_DI_SERINFOSIZE)) { Spath_desc sd = { search_rules, NULL, 0 }; Pdesc *pdp; Dl_serinfo_t *info; Dl_serpath_t *path; char *strs; size_t size = sizeof (Dl_serinfo_t); uint_t cnt = 0; info = (Dl_serinfo_t *)p; path = &info->dls_serpath[0]; strs = (char *)&info->dls_serpath[info->dls_cnt]; /* * Traverse search path entries for this object. */ while ((pdp = get_next_dir(&sd, lmp, 0)) != NULL) { size_t _size; if (pdp->pd_pname == NULL) continue; /* * If configuration information exists, it's possible * this path has been identified as non-existent, if so * ignore it. */ if (pdp->pd_info) { Rtc_obj *dobj = (Rtc_obj *)pdp->pd_info; if (dobj->co_flags & RTC_OBJ_NOEXIST) continue; } /* * Keep track of search path count and total info size. */ if (cnt++) size += sizeof (Dl_serpath_t); _size = pdp->pd_plen + 1; size += _size; if (request == RTLD_DI_SERINFOSIZE) continue; /* * If we're filling in search path information, confirm * there's sufficient space. */ if (size > info->dls_size) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_SERSIZE), EC_OFF(info->dls_size)); return (-1); } if (cnt > info->dls_cnt) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_SERCNT), info->dls_cnt); return (-1); } /* * Append the path to the information buffer. */ (void) strcpy(strs, pdp->pd_pname); path->dls_name = strs; path->dls_flags = (pdp->pd_flags & LA_SER_MASK); strs = strs + _size; path++; } /* * If we're here to size the search buffer fill it in. */ if (request == RTLD_DI_SERINFOSIZE) { info->dls_size = size; info->dls_cnt = cnt; } return (0); } /* * Return the origin of the object associated with this link-map. * Basically return the dirname(1) of the objects fullpath. */ if (request == RTLD_DI_ORIGIN) { char *str = (char *)p; (void) strncpy(str, ORIGNAME(lmp), DIRSZ(lmp)); str += DIRSZ(lmp); *str = '\0'; return (0); } /* * Return the number of object mappings, or the mapping information for * this object. */ if (request == RTLD_DI_MMAPCNT) { uint_t *cnt = (uint_t *)p; *cnt = MMAPCNT(lmp); return (0); } if (request == RTLD_DI_MMAPS) { Dl_mapinfo_t *mip = (Dl_mapinfo_t *)p; if (mip->dlm_acnt && mip->dlm_maps) { uint_t cnt = 0; while ((cnt < mip->dlm_acnt) && (cnt < MMAPCNT(lmp))) { mip->dlm_maps[cnt] = MMAPS(lmp)[cnt]; cnt++; } mip->dlm_rcnt = cnt; } return (0); } /* * Assign a new dependency name to a deferred dependency. */ if ((request == RTLD_DI_DEFERRED) || (request == RTLD_DI_DEFERRED_SYM)) { Dl_definfo_t *dfip = (Dl_definfo_t *)p; Dyninfo *dyip; const char *dname, *rname; /* * Verify the names. */ if ((dfip->dld_refname == NULL) || (dfip->dld_depname == NULL)) { eprintf(LIST(clmp), ERR_FATAL, MSG_INTL(MSG_ARG_ILLNAME)); return (-1); } dname = dfip->dld_depname; rname = dfip->dld_refname; /* * A deferred dependency can be determined by referencing a * symbol family member that is associated to the dependency, * or by looking for the dependency by its name. */ if (request == RTLD_DI_DEFERRED_SYM) { Slookup sl; Sresult sr; uint_t binfo; Syminfo *sip; /* * Lookup the symbol in the associated object. */ SLOOKUP_INIT(sl, rname, lmp, lmp, ld_entry_cnt, elf_hash(rname), 0, 0, 0, LKUP_SYMNDX); SRESULT_INIT(sr, rname); if (sym_lookup_in_caller(clmp, &sl, &sr, &binfo) == NULL) { eprintf(LIST(clmp), ERR_FATAL, MSG_INTL(MSG_DEF_NOSYMFOUND), rname); return (-1); } /* * Use the symbols index to reference the Syminfo entry * and thus find the associated dependency. */ if (sl.sl_rsymndx && ((sip = SYMINFO(clmp)) != NULL)) { /* LINTED */ sip = (Syminfo *)((char *)sip + (sl.sl_rsymndx * SYMINENT(lmp))); if ((sip->si_flags & SYMINFO_FLG_DEFERRED) && (sip->si_boundto < SYMINFO_BT_LOWRESERVE) && ((dyip = DYNINFO(lmp)) != NULL)) { dyip += sip->si_boundto; if (!(dyip->di_flags & FLG_DI_IGNORE)) return (set_def_need(lml, dyip, dname)); } } /* * No deferred symbol found. */ eprintf(LIST(clmp), ERR_FATAL, MSG_INTL(MSG_DEF_NOSYMFOUND), rname); return (-1); } else { Dyn *dyn; /* * Using the target objects dependency information, find * the associated deferred dependency. */ for (dyn = DYN(lmp), dyip = DYNINFO(lmp); !(dyip->di_flags & FLG_DI_IGNORE); dyn++, dyip++) { const char *oname; if ((dyip->di_flags & FLG_DI_DEFERRED) == 0) continue; if (strcmp(rname, dyip->di_name) == 0) return (set_def_need(lml, dyip, dname)); /* * If this dependency name has been changed by * a previous dlinfo(), check the original * dynamic entry string. The user might be * attempting to re-change an entry using the * original name as the reference. */ if ((dyip->di_flags & FLG_DI_DEF_DONE) == 0) continue; oname = STRTAB(lmp) + dyn->d_un.d_val; if (strcmp(rname, oname) == 0) return (set_def_need(lml, dyip, dname)); } /* * No deferred dependency found. */ eprintf(lml, ERR_FATAL, MSG_INTL(MSG_DEF_NODEPFOUND), rname); return (-1); } } return (0); } #pragma weak _dlinfo = dlinfo /* * External entry for dlinfo(3C). */ int dlinfo(void *handle, int request, void *p) { int error, entry; Rt_map *clmp; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); error = dlinfo_core(handle, request, p, clmp); if (entry) leave(LIST(clmp), 0); return (error); } /* * GNU defined function to iterate through the program headers for all * currently loaded dynamic objects. The caller supplies a callback function * which is called for each object. * * entry: * callback - Callback function to call. The arguments to the callback * function are: * info - Address of dl_phdr_info structure * size - sizeof (struct dl_phdr_info) * data - Caller supplied value. * data - Value supplied by caller, which is passed to callback without * examination. * * exit: * callback is called for each dynamic ELF object in the process address * space, halting when a non-zero value is returned, or when the last * object has been processed. The return value from the last call * to callback is returned. * * note: * The Linux implementation has added additional fields to the * dl_phdr_info structure over time. The callback function is * supposed to use the size field to determine which fields are * present, and to avoid attempts to access non-existent fields. * We have added those fields that are compatible with Solaris, and * which are used by GNU C++ (g++) runtime exception handling support. * * note: * We issue a callback for every ELF object mapped into the process * address space at the time this routine is entered. These callbacks * are arbitrary functions that can do anything, including possibly * causing new objects to be mapped into the process, or unmapped. * This complicates matters: * * - Adding new objects can cause the alists to be reallocated * or for contents to move. This can happen explicitly via * dlopen(), or implicitly via lazy loading. One might consider * simply banning dlopen from a callback, but lazy loading must * be allowed, in which case there's no reason to ban dlopen(). * * - Removing objects can leave us holding references to freed * memory that must not be accessed, and can cause the list * items to move in a way that would cause us to miss reporting * one, or double report others. * * - We cannot allocate memory to build a separate data structure, * because the interface to dl_iterate_phdr() does not have a * way to communicate allocation errors back to the caller. * Even if we could, it would be difficult to do so efficiently. * * - It is possible for dl_iterate_phdr() to be called recursively * from a callback, and there is no way for us to detect or manage * this effectively, particularly as the user might use longjmp() * to skip past us on return. Hence, we must be reentrant * (stateless), further precluding the option of building a * separate data structure. * * Despite these constraints, we are able to traverse the link-map * lists safely: * * - Once interposer (preload) objects have been processed at * startup, we know that new objects are always placed at the * end of the list. Hence, if we are reading a list when that * happens, the new object will not alter the part of the list * that we've already processed. * * - The alist _TRAVERSE macros recalculate the address of the * current item from scratch on each iteration, rather than * incrementing a pointer. Hence, alist additions that occur * in mid-traverse will not cause confusion. * * There is one limitation: We cannot continue operation if an object * is removed from the process from within a callback. We detect when * this happens and return immediately with a -1 return value. * * note: * As currently implemented, if a callback causes an object to be loaded, * that object may or may not be reported by the current invocation of * dl_iterate_phdr(), based on whether or not we have already processed * the link-map list that receives it. If we want to prevent this, it * can be done efficiently by associating the current value of cnt_map * with each new Rt_map entered into the system. Then this function can * use that to detect and skip new objects that enter the system in * mid-iteration. However, the Linux documentation is ambiguous on whether * this is necessary, and it does not appear to matter in practice. * We have therefore chosen not to do so at this time. */ int dl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data) { struct dl_phdr_info info; u_longlong_t l_cnt_map = cnt_map; u_longlong_t l_cnt_unmap = cnt_unmap; Lm_list *lml, *clml; Lm_cntl *lmc; Rt_map *lmp, *clmp; Aliste idx1, idx2; Ehdr *ehdr; int ret = 0; int entry; entry = enter(0); clmp = _caller(caller(), CL_EXECDEF); clml = LIST(clmp); DBG_CALL(Dbg_dl_iphdr_enter(clmp, cnt_map, cnt_unmap)); /* Issue a callback for each ELF object in the process */ for (APLIST_TRAVERSE(dynlm_list, idx1, lml)) { for (ALIST_TRAVERSE(lml->lm_lists, idx2, lmc)) { for (lmp = lmc->lc_head; lmp; lmp = NEXT_RT_MAP(lmp)) { #if defined(_sparc) && !defined(_LP64) /* * On 32-bit sparc, the possibility exists that * this object is not ELF. */ if (THIS_IS_NOT_ELF(lmp)) continue; #endif /* Prepare the object information structure */ ehdr = (Ehdr *) ADDR(lmp); info.dlpi_addr = (ehdr->e_type == ET_EXEC) ? 0 : ADDR(lmp); info.dlpi_name = lmp->rt_pathname; info.dlpi_phdr = (Phdr *) (ADDR(lmp) + ehdr->e_phoff); info.dlpi_phnum = ehdr->e_phnum; info.dlpi_adds = cnt_map; info.dlpi_subs = cnt_unmap; /* Issue the callback */ DBG_CALL(Dbg_dl_iphdr_callback(clml, &info)); leave(clml, thr_flg_reenter); ret = (* callback)(&info, sizeof (info), data); (void) enter(thr_flg_reenter); /* Return immediately on non-zero result */ if (ret != 0) goto done; /* Adapt to object mapping changes */ if ((cnt_map == l_cnt_map) && (cnt_unmap == l_cnt_unmap)) continue; DBG_CALL(Dbg_dl_iphdr_mapchange(clml, cnt_map, cnt_unmap)); /* Stop if an object was unmapped */ if (cnt_unmap == l_cnt_unmap) { l_cnt_map = cnt_map; continue; } ret = -1; DBG_CALL(Dbg_dl_iphdr_unmap_ret(clml)); goto done; } } } done: if (entry) leave(LIST(clmp), 0); return (ret); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1988 AT&T * All Rights Reserved * * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright (c) 2012, Joyent, Inc. All rights reserved. * Copyright 2022 Oxide Computer Company */ /* * Object file dependent support for ELF objects. */ #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" #include "_inline_gen.h" #include "_inline_reloc.h" #include "msg.h" /* * Default and secure dependency search paths. */ static Spath_defn _elf_def_dirs[] = { #if defined(_ELF64) { MSG_ORIG(MSG_PTH_LIB_64), MSG_PTH_LIB_64_SIZE }, { MSG_ORIG(MSG_PTH_USRLIB_64), MSG_PTH_USRLIB_64_SIZE }, #else { MSG_ORIG(MSG_PTH_LIB), MSG_PTH_LIB_SIZE }, { MSG_ORIG(MSG_PTH_USRLIB), MSG_PTH_USRLIB_SIZE }, #endif { 0, 0 } }; static Spath_defn _elf_sec_dirs[] = { #if defined(_ELF64) { MSG_ORIG(MSG_PTH_LIBSE_64), MSG_PTH_LIBSE_64_SIZE }, { MSG_ORIG(MSG_PTH_USRLIBSE_64), MSG_PTH_USRLIBSE_64_SIZE }, #else { MSG_ORIG(MSG_PTH_LIBSE), MSG_PTH_LIBSE_SIZE }, { MSG_ORIG(MSG_PTH_USRLIBSE), MSG_PTH_USRLIBSE_SIZE }, #endif { 0, 0 } }; Alist *elf_def_dirs = NULL; Alist *elf_sec_dirs = NULL; /* * Defines for local functions. */ static void elf_dladdr(ulong_t, Rt_map *, Dl_info *, void **, int); static Addr elf_entry_point(void); static int elf_fix_name(const char *, Rt_map *, Alist **, Aliste, uint_t); static Alist **elf_get_def_dirs(void); static Alist **elf_get_sec_dirs(void); static char *elf_get_so(const char *, const char *, size_t, size_t); static int elf_needed(Lm_list *, Aliste, Rt_map *, int *); /* * Functions and data accessed through indirect pointers. */ Fct elf_fct = { elf_verify, elf_new_lmp, elf_entry_point, elf_needed, lookup_sym, elf_reloc, elf_get_def_dirs, elf_get_sec_dirs, elf_fix_name, elf_get_so, elf_dladdr, dlsym_handle }; /* * Default and secure dependency search paths. */ static Alist ** elf_get_def_dirs() { if (elf_def_dirs == NULL) set_dirs(&elf_def_dirs, _elf_def_dirs, LA_SER_DEFAULT); return (&elf_def_dirs); } static Alist ** elf_get_sec_dirs() { if (elf_sec_dirs == NULL) set_dirs(&elf_sec_dirs, _elf_sec_dirs, LA_SER_SECURE); return (&elf_sec_dirs); } /* * For ELF objects we only need perform path expansion. Latent support for * other objects may require further work */ static int elf_fix_name(const char *name, Rt_map *clmp, Alist **alpp, Aliste alni, uint_t orig) { return (expand_paths(clmp, name, alpp, alni, orig, 0)); } /* * Determine whether this object requires capabilities. */ inline static int elf_cap_check(Fdesc *fdp, Ehdr *ehdr, Rej_desc *rej) { Phdr *phdr; Cap *cap = NULL; Dyn *dyn = NULL; char *str = NULL; Addr base; uint_t cnt, dyncnt; /* * If this is a shared object, the base address of the shared object is * added to all address values defined within the object. Otherwise, if * this is an executable, all object addresses are used as is. */ if (ehdr->e_type == ET_EXEC) base = 0; else base = (Addr)ehdr; /* LINTED */ phdr = (Phdr *)((char *)ehdr + ehdr->e_phoff); for (cnt = 0; cnt < ehdr->e_phnum; cnt++, phdr++) { if (phdr->p_type == PT_DYNAMIC) { /* LINTED */ dyn = (Dyn *)((uintptr_t)phdr->p_vaddr + base); dyncnt = phdr->p_filesz / sizeof (Dyn); } else if (phdr->p_type == PT_SUNWCAP) { /* LINTED */ cap = (Cap *)((uintptr_t)phdr->p_vaddr + base); } } if (cap) { /* * From the .dynamic section, determine the associated string * table. Required for CA_SUNW_MACH and CA_SUNW_PLAT * processing. */ while (dyn && dyncnt) { if (dyn->d_tag == DT_NULL) { break; } else if (dyn->d_tag == DT_STRTAB) { str = (char *)(dyn->d_un.d_ptr + base); break; } dyn++, dyncnt--; } } /* * Establish any alternative capabilities, and validate this object * if it defines it's own capabilities information. */ return (cap_check_fdesc(fdp, cap, str, rej)); } /* * Determine if we have been given an ELF file and if so determine if the file * is compatible. Returns 1 if true, else 0 and sets the reject descriptor * with associated error information. */ Fct * elf_verify(caddr_t addr, size_t size, Fdesc *fdp, const char *name, Rej_desc *rej) { Ehdr *ehdr; char *caddr = (char *)addr; /* * Determine if we're an elf file. If not simply return, we don't set * any rejection information as this test allows use to scroll through * the objects we support (ELF). */ if (size < sizeof (Ehdr) || caddr[EI_MAG0] != ELFMAG0 || caddr[EI_MAG1] != ELFMAG1 || caddr[EI_MAG2] != ELFMAG2 || caddr[EI_MAG3] != ELFMAG3) { return (NULL); } /* * Check class and encoding. */ /* LINTED */ ehdr = (Ehdr *)addr; if (ehdr->e_ident[EI_CLASS] != M_CLASS) { rej->rej_type = SGS_REJ_CLASS; rej->rej_info = (uint_t)ehdr->e_ident[EI_CLASS]; return (NULL); } if (ehdr->e_ident[EI_DATA] != M_DATA) { rej->rej_type = SGS_REJ_DATA; rej->rej_info = (uint_t)ehdr->e_ident[EI_DATA]; return (NULL); } if ((ehdr->e_type != ET_REL) && (ehdr->e_type != ET_EXEC) && (ehdr->e_type != ET_DYN)) { rej->rej_type = SGS_REJ_TYPE; rej->rej_info = (uint_t)ehdr->e_type; return (NULL); } /* * Verify ELF version. */ if (ehdr->e_version > EV_CURRENT) { rej->rej_type = SGS_REJ_VERSION; rej->rej_info = (uint_t)ehdr->e_version; return (NULL); } /* * Verify machine specific flags. */ if (elf_mach_flags_check(rej, ehdr) == 0) return (NULL); /* * Verify any capability requirements. Note, if this object is a shared * object that is explicitly defined on the ldd(1) command line, and it * contains an incompatible capabilities requirement, then inform the * user, but continue processing. */ if (elf_cap_check(fdp, ehdr, rej) == 0) { Rt_map *lmp = lml_main.lm_head; if ((lml_main.lm_flags & LML_FLG_TRC_LDDSTUB) && lmp && (FLAGS1(lmp) & FL1_RT_LDDSTUB) && (NEXT(lmp) == NULL)) { /* LINTED */ (void) printf(MSG_INTL(ldd_warn[rej->rej_type]), name, rej->rej_str); return (&elf_fct); } return (NULL); } return (&elf_fct); } /* * The runtime linker employs lazy loading to provide the libraries needed for * debugging, preloading .o's and dldump(). As these are seldom used, the * standard startup of ld.so.1 doesn't initialize all the information necessary * to perform plt relocation on ld.so.1's link-map. The first time lazy loading * is called we get here to perform these initializations: * * - elf_needed() is called to establish any ld.so.1 dependencies. These * dependencies should all be lazy loaded, so this routine is typically a * no-op. However, we call elf_needed() for completeness, in case any * NEEDED initialization is required. * * - For intel, ld.so.1's JMPSLOT relocations need relative updates. These * are by default skipped thus delaying all relative relocation processing * on every invocation of ld.so.1. */ int elf_rtld_load() { Lm_list *lml = &lml_rtld; Rt_map *lmp = lml->lm_head; if (lml->lm_flags & LML_FLG_PLTREL) return (1); if (elf_needed(lml, ALIST_OFF_DATA, lmp, NULL) == 0) return (0); #if defined(__i386) /* * This is a kludge to give ld.so.1 a performance benefit on i386. * It's based around two factors. * * - JMPSLOT relocations (PLT's) actually need a relative relocation * applied to the GOT entry so that they can find PLT0. * * - ld.so.1 does not exercise *any* PLT's before it has made a call * to elf_lazy_load(). This is because all dynamic dependencies * are recorded as lazy dependencies. */ (void) elf_reloc_relative_count((ulong_t)JMPREL(lmp), (ulong_t)(PLTRELSZ(lmp) / RELENT(lmp)), (ulong_t)RELENT(lmp), (ulong_t)ADDR(lmp), lmp, NULL, 0); #endif lml->lm_flags |= LML_FLG_PLTREL; return (1); } /* * Lazy load an object. */ Rt_map * elf_lazy_load(Rt_map *clmp, Slookup *slp, uint_t ndx, const char *sym, uint_t flags, Grp_hdl **hdl, int *in_nfavl) { Alist *palp = NULL; Rt_map *nlmp; Dyninfo *dip = &DYNINFO(clmp)[ndx], *pdip; const char *name; Lm_list *lml = LIST(clmp); Aliste lmco; /* * If this dependency should be ignored, or has already been processed, * we're done. */ if (((nlmp = (Rt_map *)dip->di_info) != NULL) || (dip->di_flags & (FLG_DI_IGNORE | FLG_DI_LDD_DONE))) return (nlmp); /* * If we're running under ldd(1), indicate that this dependency has been * processed (see test above). It doesn't matter whether the object is * successfully loaded or not, this flag simply ensures that we don't * repeatedly attempt to load an object that has already failed to load. * To do so would create multiple failure diagnostics for the same * object under ldd(1). */ if (lml->lm_flags & LML_FLG_TRC_ENABLE) dip->di_flags |= FLG_DI_LDD_DONE; /* * Determine the initial dependency name. */ name = dip->di_name; DBG_CALL(Dbg_file_lazyload(clmp, name, sym)); /* * If this object needs to establish its own group, make sure a handle * is created. */ if (dip->di_flags & FLG_DI_GROUP) flags |= (FLG_RT_SETGROUP | FLG_RT_PUBHDL); /* * Lazy dependencies are identified as DT_NEEDED entries with a * DF_P1_LAZYLOAD flag in the previous DT_POSFLAG_1 element. The * dynamic information element that corresponds to the DT_POSFLAG_1 * entry is free, and thus used to store the present entrance * identifier. This identifier is used to prevent multiple attempts to * load a failed lazy loadable dependency within the same runtime linker * operation. However, future attempts to reload this dependency are * still possible. */ if (ndx && (pdip = dip - 1) && (pdip->di_flags & FLG_DI_POSFLAG1)) pdip->di_info = (void *)slp->sl_id; /* * Expand the requested name if necessary. */ if (elf_fix_name(name, clmp, &palp, AL_CNT_NEEDED, 0) == 0) return (NULL); /* * Establish a link-map control list for this request. */ if ((lmco = create_cntl(lml, 0)) == 0) { remove_alist(&palp, 1); return (NULL); } /* * Load the associated object. */ dip->di_info = nlmp = load_one(lml, lmco, palp, clmp, MODE(clmp), flags, hdl, in_nfavl); /* * Remove any expanded pathname infrastructure. Reduce the pending lazy * dependency count of the caller, together with the link-map lists * count of objects that still have lazy dependencies pending. */ remove_alist(&palp, 1); if (--LAZY(clmp) == 0) LIST(clmp)->lm_lazy--; /* * Finish processing the objects associated with this request, and * create an association between the caller and this dependency. */ if (nlmp && ((bind_one(clmp, nlmp, BND_NEEDED) == 0) || ((nlmp = analyze_lmc(lml, lmco, nlmp, clmp, in_nfavl)) == NULL) || (relocate_lmc(lml, lmco, clmp, nlmp, in_nfavl) == 0))) dip->di_info = nlmp = NULL; /* * If this lazyload has failed, and we've created a new link-map * control list to which this request has added objects, then remove * all the objects that have been associated to this request. */ if ((nlmp == NULL) && (lmco != ALIST_OFF_DATA)) remove_lmc(lml, clmp, lmco, name); /* * Remove any temporary link-map control list. */ if (lmco != ALIST_OFF_DATA) remove_cntl(lml, lmco); /* * If this lazy loading failed, record the fact, and bump the lazy * counts. */ if (nlmp == NULL) { dip->di_flags |= FLG_DI_LAZYFAIL; if (LAZY(clmp)++ == 0) LIST(clmp)->lm_lazy++; } return (nlmp); } /* * Return the entry point of the ELF executable. */ static Addr elf_entry_point(void) { Rt_map *lmp = lml_main.lm_head; Ehdr *ehdr = (Ehdr *)ADDR(lmp); Addr addr = (Addr)(ehdr->e_entry); if ((FLAGS(lmp) & FLG_RT_FIXED) == 0) addr += ADDR(lmp); return (addr); } /* * Determine if a dependency requires a particular version and if so verify * that the version exists in the dependency. */ int elf_verify_vers(const char *name, Rt_map *clmp, Rt_map *nlmp) { Verneed *vnd = VERNEED(clmp); int _num, num = VERNEEDNUM(clmp); char *cstrs = (char *)STRTAB(clmp); Lm_list *lml = LIST(clmp); /* * Traverse the callers version needed information and determine if any * specific versions are required from the dependency. */ DBG_CALL(Dbg_ver_need_title(LIST(clmp), NAME(clmp))); for (_num = 1; _num <= num; _num++, vnd = (Verneed *)((Xword)vnd + vnd->vn_next)) { Half cnt = vnd->vn_cnt; Vernaux *vnap; char *nstrs, *need; /* * Determine if a needed entry matches this dependency. */ need = (char *)(cstrs + vnd->vn_file); if (strcmp(name, need) != 0) continue; if ((lml->lm_flags & LML_FLG_TRC_VERBOSE) && ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0)) (void) printf(MSG_INTL(MSG_LDD_VER_FIND), name); /* * Validate that each version required actually exists in the * dependency. */ nstrs = (char *)STRTAB(nlmp); for (vnap = (Vernaux *)((Xword)vnd + vnd->vn_aux); cnt; cnt--, vnap = (Vernaux *)((Xword)vnap + vnap->vna_next)) { char *version, *define; Verdef *vdf = VERDEF(nlmp); ulong_t _num, num = VERDEFNUM(nlmp); int found = 0; /* * Skip validation of versions that are marked * INFO. This optimization is used for versions * that are inherited by another version. Verification * of the inheriting version is sufficient. * * Such versions are recorded in the object for the * benefit of VERSYM entries that refer to them. This * provides a purely diagnostic benefit. */ if (vnap->vna_flags & VER_FLG_INFO) continue; version = (char *)(cstrs + vnap->vna_name); DBG_CALL(Dbg_ver_need_entry(lml, 0, need, version)); for (_num = 1; _num <= num; _num++, vdf = (Verdef *)((Xword)vdf + vdf->vd_next)) { Verdaux *vdap; if (vnap->vna_hash != vdf->vd_hash) continue; vdap = (Verdaux *)((Xword)vdf + vdf->vd_aux); define = (char *)(nstrs + vdap->vda_name); if (strcmp(version, define) != 0) continue; found++; break; } /* * If we're being traced print out any matched version * when the verbose (-v) option is in effect. Always * print any unmatched versions. */ if (lml->lm_flags & LML_FLG_TRC_ENABLE) { /* BEGIN CSTYLED */ if (found) { if (!(lml->lm_flags & LML_FLG_TRC_VERBOSE)) continue; (void) printf(MSG_ORIG(MSG_LDD_VER_FOUND), need, version, NAME(nlmp)); } else { if (rtld_flags & RT_FL_SILENCERR) continue; (void) printf(MSG_INTL(MSG_LDD_VER_NFOUND), need, version); } /* END CSTYLED */ continue; } /* * If the version hasn't been found then this is a * candidate for a fatal error condition. Weak * version definition requirements are silently * ignored. Also, if the image inspected for a version * definition has no versioning recorded at all then * silently ignore this (this provides better backward * compatibility to old images created prior to * versioning being available). Both of these skipped * diagnostics are available under tracing (see above). */ if ((found == 0) && (num != 0) && (!(vnap->vna_flags & VER_FLG_WEAK))) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_VER_NFOUND), need, version, NAME(clmp)); return (0); } } } DBG_CALL(Dbg_ver_need_done(lml)); return (1); } /* * Search through the dynamic section for DT_NEEDED entries and perform one * of two functions. If only the first argument is specified then load the * defined shared object, otherwise add the link map representing the defined * link map the the dlopen list. */ static int elf_needed(Lm_list *lml, Aliste lmco, Rt_map *clmp, int *in_nfavl) { Alist *palp = NULL; Dyn *dyn; Dyninfo *dip; Word lmflags = lml->lm_flags; /* * A DYNINFO() structure is created during link-map generation that * parallels the DYN() information, and defines any flags that * influence a dependencies loading. */ for (dyn = DYN(clmp), dip = DYNINFO(clmp); !(dip->di_flags & FLG_DI_IGNORE); dyn++, dip++) { uint_t flags = 0, silent = 0; const char *name = dip->di_name; Rt_map *nlmp = NULL; if ((dip->di_flags & FLG_DI_NEEDED) == 0) continue; /* * Skip any deferred dependencies, unless ldd(1) has forced * their processing. By default, deferred dependencies are * only processed when an explicit binding to an individual * deferred reference is made. */ if ((dip->di_flags & FLG_DI_DEFERRED) && ((rtld_flags & RT_FL_DEFERRED) == 0)) continue; /* * NOTE, libc.so.1 can't be lazy loaded. Although a lazy * position flag won't be produced when a RTLDINFO .dynamic * entry is found (introduced with the UPM in Solaris 10), it * was possible to mark libc for lazy loading on previous * releases. To reduce the overhead of testing for this * occurrence, only carry out this check for the first object * on the link-map list (there aren't many applications built * without libc). */ if ((dip->di_flags & FLG_DI_LAZY) && (lml->lm_head == clmp) && (strcmp(name, MSG_ORIG(MSG_FIL_LIBC)) == 0)) dip->di_flags &= ~FLG_DI_LAZY; /* * Don't bring in lazy loaded objects yet unless we've been * asked to attempt to load all available objects (crle(1) sets * LD_FLAGS=loadavail). Even under RTLD_NOW we don't process * this - RTLD_NOW will cause relocation processing which in * turn might trigger lazy loading, but its possible that the * object has a lazy loaded file with no bindings (i.e., it * should never have been a dependency in the first place). */ if (dip->di_flags & FLG_DI_LAZY) { if ((lmflags & LML_FLG_LOADAVAIL) == 0) { LAZY(clmp)++; continue; } /* * Silence any error messages - see description under * elf_lookup_filtee(). */ if ((rtld_flags & RT_FL_SILENCERR) == 0) { rtld_flags |= RT_FL_SILENCERR; silent = 1; } } DBG_CALL(Dbg_file_needed(clmp, name)); /* * If we're running under ldd(1), indicate that this dependency * has been processed. It doesn't matter whether the object is * successfully loaded or not, this flag simply ensures that we * don't repeatedly attempt to load an object that has already * failed to load. To do so would create multiple failure * diagnostics for the same object under ldd(1). */ if (lml->lm_flags & LML_FLG_TRC_ENABLE) dip->di_flags |= FLG_DI_LDD_DONE; /* * Identify any group permission requirements. */ if (dip->di_flags & FLG_DI_GROUP) flags = (FLG_RT_SETGROUP | FLG_RT_PUBHDL); /* * Establish the objects name, load it and establish a binding * with the caller. */ if ((elf_fix_name(name, clmp, &palp, AL_CNT_NEEDED, 0) == 0) || ((nlmp = load_one(lml, lmco, palp, clmp, MODE(clmp), flags, 0, in_nfavl)) == NULL) || (bind_one(clmp, nlmp, BND_NEEDED) == 0)) nlmp = NULL; /* * Clean up any infrastructure, including the removal of the * error suppression state, if it had been previously set in * this routine. */ remove_alist(&palp, 0); if (silent) rtld_flags &= ~RT_FL_SILENCERR; if ((dip->di_info = (void *)nlmp) == NULL) { /* * If the object could not be mapped, continue if error * suppression is established or we're here with ldd(1). */ if ((MODE(clmp) & RTLD_CONFGEN) || (lmflags & (LML_FLG_LOADAVAIL | LML_FLG_TRC_ENABLE))) continue; else { remove_alist(&palp, 1); return (0); } } } if (LAZY(clmp)) lml->lm_lazy++; remove_alist(&palp, 1); return (1); } /* * A null symbol interpretor. Used if a filter has no associated filtees. */ /* ARGSUSED0 */ static int elf_null_find_sym(Slookup *slp, Sresult *srp, uint_t *binfo, int *in_nfavl) { return (0); } /* * Disable filtee use. */ static void elf_disable_filtee(Rt_map *lmp, Dyninfo *dip) { if ((dip->di_flags & FLG_DI_SYMFLTR) == 0) { /* * If this is an object filter, null out the reference name. */ if (OBJFLTRNDX(lmp) != FLTR_DISABLED) { REFNAME(lmp) = NULL; OBJFLTRNDX(lmp) = FLTR_DISABLED; /* * Indicate that this filtee is no longer available. */ if (dip->di_flags & FLG_DI_STDFLTR) SYMINTP(lmp) = elf_null_find_sym; } } else if (dip->di_flags & FLG_DI_STDFLTR) { /* * Indicate that this standard filtee is no longer available. */ if (SYMSFLTRCNT(lmp)) SYMSFLTRCNT(lmp)--; } else { /* * Indicate that this auxiliary filtee is no longer available. */ if (SYMAFLTRCNT(lmp)) SYMAFLTRCNT(lmp)--; } dip->di_flags &= ~MSK_DI_FILTER; } /* * Find symbol interpreter - filters. * This function is called when the symbols from a shared object should * be resolved from the shared objects filtees instead of from within itself. * * A symbol name of 0 is used to trigger filtee loading. */ static int _elf_lookup_filtee(Slookup *slp, Sresult *srp, uint_t *binfo, uint_t ndx, int *in_nfavl) { const char *name = slp->sl_name, *filtees; Rt_map *clmp = slp->sl_cmap; Rt_map *ilmp = slp->sl_imap; Pdesc *pdp; int any; Dyninfo *dip = &DYNINFO(ilmp)[ndx]; Lm_list *lml = LIST(ilmp); Aliste idx; /* * Indicate that the filter has been used. If a binding already exists * to the caller, indicate that this object is referenced. This insures * we don't generate false unreferenced diagnostics from ldd -u/U or * debugging. Don't create a binding regardless, as this filter may * have been dlopen()'ed. */ if (name && (ilmp != clmp)) { Word tracing = (LIST(clmp)->lm_flags & (LML_FLG_TRC_UNREF | LML_FLG_TRC_UNUSED)); if (tracing || DBG_ENABLED) { Bnd_desc *bdp; Aliste idx; FLAGS1(ilmp) |= FL1_RT_USED; if ((tracing & LML_FLG_TRC_UNREF) || DBG_ENABLED) { for (APLIST_TRAVERSE(CALLERS(ilmp), idx, bdp)) { if (bdp->b_caller == clmp) { bdp->b_flags |= BND_REFER; break; } } } } } /* * If this is the first call to process this filter, establish the * filtee list. If a configuration file exists, determine if any * filtee associations for this filter, and its filtee reference, are * defined. Otherwise, process the filtee reference. Any token * expansion is also completed at this point (i.e., $PLATFORM). */ filtees = dip->di_name; if (dip->di_info == NULL) { if (rtld_flags2 & RT_FL2_FLTCFG) { elf_config_flt(lml, PATHNAME(ilmp), filtees, (Alist **)&dip->di_info, AL_CNT_FILTEES); } if (dip->di_info == NULL) { DBG_CALL(Dbg_file_filter(lml, NAME(ilmp), filtees, 0)); if ((lml->lm_flags & (LML_FLG_TRC_VERBOSE | LML_FLG_TRC_SEARCH)) && ((FLAGS1(ilmp) & FL1_RT_LDDSTUB) == 0)) (void) printf(MSG_INTL(MSG_LDD_FIL_FILTER), NAME(ilmp), filtees); if (expand_paths(ilmp, filtees, (Alist **)&dip->di_info, AL_CNT_FILTEES, 0, 0) == 0) { elf_disable_filtee(ilmp, dip); return (0); } } } /* * Traverse the filtee list, dlopen()'ing any objects specified and * using their group handle to lookup the symbol. */ any = 0; for (ALIST_TRAVERSE((Alist *)dip->di_info, idx, pdp)) { int mode; Grp_hdl *ghp; Rt_map *nlmp = NULL; if (pdp->pd_plen == 0) continue; /* * Establish the mode of the filtee from the filter. As filtees * are loaded via a dlopen(), make sure that RTLD_GROUP is set * and the filtees aren't global. It would be nice to have * RTLD_FIRST used here also, but as filters got out long before * RTLD_FIRST was introduced it's a little too late now. */ mode = MODE(ilmp) | RTLD_GROUP; mode &= ~RTLD_GLOBAL; /* * Insure that any auxiliary filter can locate symbols from its * caller. */ if (dip->di_flags & FLG_DI_AUXFLTR) mode |= RTLD_PARENT; /* * Process any capability directory. Establish a new link-map * control list from which to analyze any newly added objects. */ if ((pdp->pd_info == NULL) && (pdp->pd_flags & PD_TKN_CAP)) { const char *dir = pdp->pd_pname; Aliste lmco; /* * Establish a link-map control list for this request. */ if ((lmco = create_cntl(lml, 0)) == 0) return (0); /* * Determine the capability filtees. If none can be * found, provide suitable diagnostics. */ DBG_CALL(Dbg_cap_filter(lml, dir, ilmp)); if (cap_filtees((Alist **)&dip->di_info, idx, dir, lmco, ilmp, clmp, filtees, mode, (FLG_RT_PUBHDL | FLG_RT_CAP), in_nfavl) == 0) { if ((lml->lm_flags & LML_FLG_TRC_ENABLE) && (dip->di_flags & FLG_DI_AUXFLTR) && (rtld_flags & RT_FL_WARNFLTR)) { (void) printf( MSG_INTL(MSG_LDD_CAP_NFOUND), dir); } DBG_CALL(Dbg_cap_filter(lml, dir, 0)); } /* * Re-establish the originating path name descriptor, * as the expansion of capabilities filtees may have * re-allocated the controlling Alist. Mark this * original pathname descriptor as unused so that the * descriptor isn't revisited for processing. Any real * capabilities filtees have been added as new pathname * descriptors following this descriptor. */ pdp = alist_item((Alist *)dip->di_info, idx); pdp->pd_flags &= ~PD_TKN_CAP; pdp->pd_plen = 0; /* * Now that any capability objects have been processed, * remove any temporary link-map control list. */ if (lmco != ALIST_OFF_DATA) remove_cntl(lml, lmco); } if (pdp->pd_plen == 0) continue; /* * Process an individual filtee. */ if (pdp->pd_info == NULL) { const char *filtee = pdp->pd_pname; int audit = 0; DBG_CALL(Dbg_file_filtee(lml, NAME(ilmp), filtee, 0)); ghp = NULL; /* * Determine if the reference link map is already * loaded. As an optimization compare the filtee with * our interpretor. The most common filter is * libdl.so.1, which is a filter on ld.so.1. */ #if defined(_ELF64) if (strcmp(filtee, MSG_ORIG(MSG_PTH_RTLD_64)) == 0) { #else if (strcmp(filtee, MSG_ORIG(MSG_PTH_RTLD)) == 0) { #endif uint_t hflags, rdflags, cdflags; /* * Establish any flags for the handle (Grp_hdl). * * - This is a special, public, ld.so.1 * handle. * - Only the first object on this handle * can supply symbols. * - This handle provides a filtee. * * Essentially, this handle allows a caller to * reference the dl*() family of interfaces from * ld.so.1. */ hflags = (GPH_PUBLIC | GPH_LDSO | GPH_FIRST | GPH_FILTEE); /* * Establish the flags for the referenced * dependency descriptor (Grp_desc). * * - ld.so.1 is available for dlsym(). * - ld.so.1 is available to relocate * against. * - There's no need to add an dependencies * to this handle. */ rdflags = (GPD_DLSYM | GPD_RELOC); /* * Establish the flags for this callers * dependency descriptor (Grp_desc). * * - The explicit creation of a handle * creates a descriptor for the referenced * object and the parent (caller). */ cdflags = GPD_PARENT; nlmp = lml_rtld.lm_head; if ((ghp = hdl_create(&lml_rtld, nlmp, ilmp, hflags, rdflags, cdflags)) == NULL) nlmp = NULL; /* * Establish the filter handle to prevent any * recursion. */ if (nlmp && ghp) pdp->pd_info = (void *)ghp; /* * Audit the filter/filtee established. Ignore * any return from the auditor, as we can't * allow ignore filtering to ld.so.1, otherwise * nothing is going to work. */ if (nlmp && ((lml->lm_tflags | AFLAGS(ilmp)) & LML_TFLG_AUD_OBJFILTER)) (void) audit_objfilter(ilmp, filtees, nlmp, 0); } else { Rej_desc rej = { 0 }; Fdesc fd = { 0 }; Aliste lmco; /* * Trace the inspection of this file, determine * any auditor substitution, and seed the file * descriptor with the originating name. */ if (load_trace(lml, pdp, clmp, &fd) == NULL) continue; /* * Establish a link-map control list for this * request. */ if ((lmco = create_cntl(lml, 0)) == 0) return (0); /* * Locate and load the filtee. */ if ((nlmp = load_path(lml, lmco, ilmp, mode, FLG_RT_PUBHDL, &ghp, &fd, &rej, in_nfavl)) == NULL) file_notfound(LIST(ilmp), filtee, ilmp, FLG_RT_PUBHDL, &rej); filtee = pdp->pd_pname; /* * Establish the filter handle to prevent any * recursion. */ if (nlmp && ghp) { ghp->gh_flags |= GPH_FILTEE; pdp->pd_info = (void *)ghp; FLAGS1(nlmp) |= FL1_RT_USED; } /* * Audit the filter/filtee established. A * return of 0 indicates the auditor wishes to * ignore this filtee. */ if (nlmp && ((lml->lm_tflags | FLAGS1(ilmp)) & LML_TFLG_AUD_OBJFILTER)) { if (audit_objfilter(ilmp, filtees, nlmp, 0) == 0) { audit = 1; nlmp = NULL; } } /* * Finish processing the objects associated with * this request. Create an association between * this object and the originating filter to * provide sufficient information to tear down * this filtee if necessary. */ if (nlmp && ghp && (((nlmp = analyze_lmc(lml, lmco, nlmp, clmp, in_nfavl)) == NULL) || (relocate_lmc(lml, lmco, ilmp, nlmp, in_nfavl) == 0))) nlmp = NULL; /* * If the filtee has been successfully * processed, then create an association * between the filter and filtee. This * association provides sufficient information * to tear down the filter and filtee if * necessary. */ DBG_CALL(Dbg_file_hdl_title(DBG_HDL_ADD)); if (nlmp && ghp && (hdl_add(ghp, ilmp, GPD_FILTER, NULL) == NULL)) nlmp = NULL; /* * Generate a diagnostic if the filtee couldn't * be loaded. */ if (nlmp == NULL) DBG_CALL(Dbg_file_filtee(lml, 0, filtee, audit)); /* * If this filtee loading has failed, and we've * created a new link-map control list to which * this request has added objects, then remove * all the objects that have been associated to * this request. */ if ((nlmp == NULL) && (lmco != ALIST_OFF_DATA)) remove_lmc(lml, clmp, lmco, name); /* * Remove any temporary link-map control list. */ if (lmco != ALIST_OFF_DATA) remove_cntl(lml, lmco); } /* * If the filtee couldn't be loaded, null out the * path name descriptor entry, and continue the search. * Otherwise, the group handle is retained for future * symbol searches. */ if (nlmp == NULL) { pdp->pd_info = NULL; pdp->pd_plen = 0; continue; } } ghp = (Grp_hdl *)pdp->pd_info; /* * If name is NULL, we're here to trigger filtee loading. * Skip the symbol lookup so that we'll continue looking for * additional filtees. */ if (name) { Grp_desc *gdp; int ret = 0; Aliste idx; Slookup sl = *slp; sl.sl_flags |= (LKUP_FIRST | LKUP_DLSYM); any++; /* * Look for the symbol in the handles dependencies. */ for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) { if ((gdp->gd_flags & GPD_DLSYM) == 0) continue; /* * If our parent is a dependency don't look at * it (otherwise we are in a recursive loop). * This situation can occur with auxiliary * filters if the filtee has a dependency on the * filter. This dependency isn't necessary as * auxiliary filters are opened RTLD_PARENT, but * users may still unknowingly add an explicit * dependency to the parent. */ if ((sl.sl_imap = gdp->gd_depend) == ilmp) continue; if (((ret = SYMINTP(sl.sl_imap)(&sl, srp, binfo, in_nfavl)) != 0) || (ghp->gh_flags & GPH_FIRST)) break; } /* * If a symbol has been found, indicate the binding * and return the symbol. */ if (ret) { *binfo |= DBG_BINFO_FILTEE; return (1); } } /* * If this object is tagged to terminate filtee processing we're * done. */ if (FLAGS1(ghp->gh_ownlmp) & FL1_RT_ENDFILTE) break; } /* * If we're just here to trigger filtee loading then we're done. */ if (name == NULL) return (0); /* * If no filtees have been found for a filter, clean up any path name * descriptors and disable their search completely. For auxiliary * filters we can reselect the symbol search function so that we never * enter this routine again for this object. For standard filters we * use the null symbol routine. */ if (any == 0) { remove_alist((Alist **)&(dip->di_info), 1); elf_disable_filtee(ilmp, dip); } return (0); } /* * Focal point for disabling error messages for auxiliary filters. As an * auxiliary filter allows for filtee use, but provides a fallback should a * filtee not exist (or fail to load), any errors generated as a consequence of * trying to load the filtees are typically suppressed. Setting RT_FL_SILENCERR * suppresses errors generated by eprintf(), but ensures a debug diagnostic is * produced. ldd(1) employs printf(), and here the selection of whether to * print a diagnostic in regards to auxiliary filters is a little more complex. * * - The determination of whether to produce an ldd message, or a fatal * error message is driven by LML_FLG_TRC_ENABLE. * - More detailed ldd messages may also be driven off of LML_FLG_TRC_WARN, * (ldd -d/-r), LML_FLG_TRC_VERBOSE (ldd -v), LML_FLG_TRC_SEARCH (ldd -s), * and LML_FLG_TRC_UNREF/LML_FLG_TRC_UNUSED (ldd -U/-u). * - If the calling object is lddstub, then several classes of message are * suppressed. The user isn't trying to diagnose lddstub, this is simply * a stub executable employed to preload a user specified library against. * - If RT_FL_SILENCERR is in effect then any generic ldd() messages should * be suppressed. All detailed ldd messages should still be produced. */ int elf_lookup_filtee(Slookup *slp, Sresult *srp, uint_t *binfo, uint_t ndx, int *in_nfavl) { Dyninfo *dip = &DYNINFO(slp->sl_imap)[ndx]; int ret, silent = 0; /* * Make sure this entry is still acting as a filter. We may have tried * to process this previously, and disabled it if the filtee couldn't * be processed. However, other entries may provide different filtees * that are yet to be completed. */ if (dip->di_flags == 0) return (0); /* * Indicate whether an error message is required should this filtee not * be found, based on the type of filter. */ if ((dip->di_flags & FLG_DI_AUXFLTR) && ((rtld_flags & (RT_FL_WARNFLTR | RT_FL_SILENCERR)) == 0)) { rtld_flags |= RT_FL_SILENCERR; silent = 1; } ret = _elf_lookup_filtee(slp, srp, binfo, ndx, in_nfavl); if (silent) rtld_flags &= ~RT_FL_SILENCERR; return (ret); } /* * Compute the elf hash value (as defined in the ELF access library). * The form of the hash table is: * * |--------------| * | # of buckets | * |--------------| * | # of chains | * |--------------| * | bucket[] | * |--------------| * | chain[] | * |--------------| */ ulong_t elf_hash(const char *name) { uint_t hval = 0; while (*name) { uint_t g; hval = (hval << 4) + *name++; if ((g = (hval & 0xf0000000)) != 0) hval ^= g >> 24; hval &= ~g; } return ((ulong_t)hval); } /* * Look up a symbol. The callers lookup information is passed in the Slookup * structure, and any resultant binding information is returned in the Sresult * structure. */ int elf_find_sym(Slookup *slp, Sresult *srp, uint_t *binfo, int *in_nfavl) { const char *name = slp->sl_name; Rt_map *ilmp = slp->sl_imap; ulong_t hash = slp->sl_hash; uint_t ndx, hashoff, buckets, *chainptr; Sym *sym, *symtabptr; char *strtabptr, *strtabname; uint_t flags1; Syminfo *sip; /* * If we're only here to establish a symbols index, skip the diagnostic * used to trace a symbol search. */ if ((slp->sl_flags & LKUP_SYMNDX) == 0) DBG_CALL(Dbg_syms_lookup(ilmp, name, MSG_ORIG(MSG_STR_ELF))); if (HASH(ilmp) == NULL) return (0); buckets = HASH(ilmp)[0]; /* LINTED */ hashoff = ((uint_t)hash % buckets) + 2; /* * Get the first symbol from the hash chain and initialize the string * and symbol table pointers. */ if ((ndx = HASH(ilmp)[hashoff]) == 0) return (0); chainptr = HASH(ilmp) + 2 + buckets; strtabptr = STRTAB(ilmp); symtabptr = SYMTAB(ilmp); while (ndx) { sym = symtabptr + ndx; strtabname = strtabptr + sym->st_name; /* * Compare the symbol found with the name required. If the * names don't match continue with the next hash entry. */ if ((*strtabname++ != *name) || strcmp(strtabname, &name[1])) { hashoff = ndx + buckets + 2; if ((ndx = chainptr[ndx]) != 0) continue; return (0); } /* * Symbols that are defined as hidden within an object usually * have any references from within the same object bound at * link-edit time, thus ld.so.1 is not involved. However, if * these are capabilities symbols, then references to them must * be resolved at runtime. A hidden symbol can only be bound * to by the object that defines the symbol. */ if ((sym->st_shndx != SHN_UNDEF) && (ELF_ST_VISIBILITY(sym->st_other) == STV_HIDDEN) && (slp->sl_cmap != ilmp)) return (0); /* * The Solaris ld does not put DT_VERSYM in the dynamic * section, but the GNU ld does. The GNU runtime linker * interprets the top bit of the 16-bit Versym value * (0x8000) as the "hidden" bit. If this bit is set, * the linker is supposed to act as if that symbol does * not exist. The hidden bit supports their versioning * scheme, which allows multiple incompatible functions * with the same name to exist at different versions * within an object. The Solaris linker does not support this * mechanism, or the model of interface evolution that * it allows, but we honor the hidden bit in GNU ld * produced objects in order to interoperate with them. */ if (VERSYM(ilmp) && (VERSYM(ilmp)[ndx] & 0x8000)) { DBG_CALL(Dbg_syms_ignore_gnuver(ilmp, name, ndx, VERSYM(ilmp)[ndx])); return (0); } /* * If we're only here to establish a symbol's index, we're done. */ if (slp->sl_flags & LKUP_SYMNDX) { srp->sr_dmap = ilmp; srp->sr_sym = sym; return (1); } /* * If we find a match and the symbol is defined, capture the * symbol pointer and the link map in which it was found. */ if (sym->st_shndx != SHN_UNDEF) { srp->sr_dmap = ilmp; srp->sr_sym = sym; *binfo |= DBG_BINFO_FOUND; if ((FLAGS(ilmp) & FLG_RT_OBJINTPO) || ((FLAGS(ilmp) & FLG_RT_SYMINTPO) && is_sym_interposer(ilmp, sym))) *binfo |= DBG_BINFO_INTERPOSE; break; /* * If we find a match and the symbol is undefined, the * symbol type is a function, and the value of the symbol * is non zero, then this is a special case. This allows * the resolution of a function address to the plt[] entry. * See SPARC ABI, Dynamic Linking, Function Addresses for * more details. */ } else if ((slp->sl_flags & LKUP_SPEC) && (FLAGS(ilmp) & FLG_RT_ISMAIN) && (sym->st_value != 0) && (ELF_ST_TYPE(sym->st_info) == STT_FUNC)) { srp->sr_dmap = ilmp; srp->sr_sym = sym; *binfo |= (DBG_BINFO_FOUND | DBG_BINFO_PLTADDR); if ((FLAGS(ilmp) & FLG_RT_OBJINTPO) || ((FLAGS(ilmp) & FLG_RT_SYMINTPO) && is_sym_interposer(ilmp, sym))) *binfo |= DBG_BINFO_INTERPOSE; return (1); } /* * Undefined symbol. */ return (0); } /* * We've found a match. Determine if the defining object contains * symbol binding information. */ if ((sip = SYMINFO(ilmp)) != NULL) sip += ndx; /* * If this definition is a singleton, and we haven't followed a default * symbol search knowing that we're looking for a singleton (presumably * because the symbol definition has been changed since the referring * object was built), then reject this binding so that the caller can * fall back to a standard symbol search. */ if ((ELF_ST_VISIBILITY(sym->st_other) == STV_SINGLETON) && (((slp->sl_flags & LKUP_STANDARD) == 0) || (((slp->sl_flags & LKUP_SINGLETON) == 0) && (LIST(ilmp)->lm_flags & LML_FLG_GROUPSEXIST)))) { DBG_CALL(Dbg_bind_reject(slp->sl_cmap, ilmp, name, DBG_BNDREJ_SINGLE)); *binfo |= BINFO_REJSINGLE; *binfo &= ~DBG_BINFO_MSK; return (0); } /* * If this is a direct binding request, but the symbol definition has * disabled directly binding to it (presumably because the symbol * definition has been changed since the referring object was built), * reject this binding so that the caller can fall back to a standard * symbol search. */ if (sip && (slp->sl_flags & LKUP_DIRECT) && (sip->si_flags & SYMINFO_FLG_NOEXTDIRECT)) { DBG_CALL(Dbg_bind_reject(slp->sl_cmap, ilmp, name, DBG_BNDREJ_DIRECT)); *binfo |= BINFO_REJDIRECT; *binfo &= ~DBG_BINFO_MSK; return (0); } /* * If this is a binding request within an RTLD_GROUP family, and the * symbol has disabled directly binding to it, reject this binding so * that the caller can fall back to a standard symbol search. * * Effectively, an RTLD_GROUP family achieves what can now be * established with direct bindings. However, various symbols have * been tagged as inappropriate for direct binding to (ie. libc:malloc). * * A symbol marked as no-direct cannot be used within a group without * first ensuring that the symbol has not been interposed upon outside * of the group. A common example occurs when users implement their own * version of malloc() in the executable. Such a malloc() interposes on * the libc:malloc, and this interposition must be honored within the * group as well. * * Following any rejection, LKUP_WORLD is established as a means of * overriding this test as we return to a standard search. */ if (sip && (sip->si_flags & SYMINFO_FLG_NOEXTDIRECT) && ((MODE(slp->sl_cmap) & (RTLD_GROUP | RTLD_WORLD)) == RTLD_GROUP) && ((slp->sl_flags & LKUP_WORLD) == 0)) { DBG_CALL(Dbg_bind_reject(slp->sl_cmap, ilmp, name, DBG_BNDREJ_GROUP)); *binfo |= BINFO_REJGROUP; *binfo &= ~DBG_BINFO_MSK; return (0); } /* * If this symbol is associated with capabilities, then each of the * capabilities instances needs to be compared against the system * capabilities. The best instance will be chosen to satisfy this * binding. */ if (CAP(ilmp) && CAPINFO(ilmp) && ELF_C_GROUP(CAPINFO(ilmp)[ndx]) && (cap_match(srp, ndx, symtabptr, strtabptr) == 0)) return (0); /* * Determine whether this object is acting as a filter. */ if (((flags1 = FLAGS1(ilmp)) & MSK_RT_FILTER) == 0) return (1); /* * Determine if this object offers per-symbol filtering, and if so, * whether this symbol references a filtee. */ if (sip && (flags1 & (FL1_RT_SYMSFLTR | FL1_RT_SYMAFLTR))) { /* * If this is a standard filter reference, and no standard * filtees remain to be inspected, we're done. If this is an * auxiliary filter reference, and no auxiliary filtees remain, * we'll fall through in case any object filtering is available. */ if ((sip->si_flags & SYMINFO_FLG_FILTER) && (SYMSFLTRCNT(ilmp) == 0)) return (0); if ((sip->si_flags & SYMINFO_FLG_FILTER) || ((sip->si_flags & SYMINFO_FLG_AUXILIARY) && SYMAFLTRCNT(ilmp))) { Sresult sr; /* * Initialize a local symbol result descriptor, using * the original symbol name. */ SRESULT_INIT(sr, slp->sl_name); /* * This symbol has an associated filtee. Lookup the * symbol in the filtee, and if it is found return it. * If the symbol doesn't exist, and this is a standard * filter, return an error, otherwise fall through to * catch any object filtering that may be available. */ if (elf_lookup_filtee(slp, &sr, binfo, sip->si_boundto, in_nfavl)) { *srp = sr; return (1); } if (sip->si_flags & SYMINFO_FLG_FILTER) return (0); } } /* * Determine if this object provides global filtering. */ if (flags1 & (FL1_RT_OBJSFLTR | FL1_RT_OBJAFLTR)) { if (OBJFLTRNDX(ilmp) != FLTR_DISABLED) { Sresult sr; /* * Initialize a local symbol result descriptor, using * the original symbol name. */ SRESULT_INIT(sr, slp->sl_name); /* * This object has an associated filtee. Lookup the * symbol in the filtee, and if it is found return it. * If the symbol doesn't exist, and this is a standard * filter, return and error, otherwise return the symbol * within the filter itself. */ if (elf_lookup_filtee(slp, &sr, binfo, OBJFLTRNDX(ilmp), in_nfavl)) { *srp = sr; return (1); } } if (flags1 & FL1_RT_OBJSFLTR) return (0); } return (1); } /* * Create a new Rt_map structure for an ELF object and initialize * all values. */ Rt_map * elf_new_lmp(Lm_list *lml, Aliste lmco, Fdesc *fdp, Addr addr, size_t msize, void *odyn, Rt_map *clmp, int *in_nfavl) { const char *name = fdp->fd_nname; Rt_map *lmp; Ehdr *ehdr = (Ehdr *)addr; Phdr *phdr, *tphdr = NULL, *dphdr = NULL, *uphdr = NULL; Dyn *dyn = (Dyn *)odyn; Cap *cap = NULL; int ndx; Addr base, fltr = 0, audit = 0, cfile = 0, crle = 0; Xword rpath = 0; size_t lmsz, rtsz, epsz, dynsz = 0; uint_t dyncnt = 0; DBG_CALL(Dbg_file_elf(lml, name, addr, msize, lml->lm_lmidstr, lmco)); /* * If this is a shared object, the base address of the shared object is * added to all address values defined within the object. Otherwise, if * this is an executable, all object addresses are used as is. */ if (ehdr->e_type == ET_EXEC) base = 0; else base = addr; /* * Traverse the program header table, picking off required items. This * traversal also provides for the sizing of the PT_DYNAMIC section. */ phdr = (Phdr *)((uintptr_t)ehdr + ehdr->e_phoff); for (ndx = 0; ndx < (int)ehdr->e_phnum; ndx++, phdr = (Phdr *)((uintptr_t)phdr + ehdr->e_phentsize)) { switch (phdr->p_type) { case PT_DYNAMIC: dphdr = phdr; dyn = (Dyn *)((uintptr_t)phdr->p_vaddr + base); break; case PT_TLS: tphdr = phdr; break; case PT_SUNWCAP: cap = (Cap *)((uintptr_t)phdr->p_vaddr + base); break; case PT_SUNW_UNWIND: case PT_SUNW_EH_FRAME: uphdr = phdr; break; default: break; } } /* * Determine the number of PT_DYNAMIC entries for the DYNINFO() * allocation. Sadly, this is a little larger than we really need, * as there are typically padding DT_NULL entries. However, adding * this data to the initial link-map allocation is a win. */ if (dyn) { dyncnt = dphdr->p_filesz / sizeof (Dyn); dynsz = dyncnt * sizeof (Dyninfo); } /* * Allocate space for the link-map, private elf information, and * DYNINFO() data. Once these are allocated and initialized, * remove_so(0, lmp) can be used to tear down the link-map allocation * should any failures occur. */ rtsz = S_DROUND(sizeof (Rt_map)); epsz = S_DROUND(sizeof (Rt_elfp)); lmsz = rtsz + epsz + dynsz; if ((lmp = calloc(1, lmsz)) == NULL) return (NULL); ELFPRV(lmp) = (void *)((uintptr_t)lmp + rtsz); DYNINFO(lmp) = (Dyninfo *)((uintptr_t)lmp + rtsz + epsz); LMSIZE(lmp) = lmsz; /* * All fields not filled in were set to 0 by calloc. */ NAME(lmp) = (char *)name; ADDR(lmp) = addr; MSIZE(lmp) = msize; SYMINTP(lmp) = elf_find_sym; FCT(lmp) = &elf_fct; LIST(lmp) = lml; OBJFLTRNDX(lmp) = FLTR_DISABLED; SORTVAL(lmp) = -1; DYN(lmp) = dyn; DYNINFOCNT(lmp) = dyncnt; PTUNWIND(lmp) = uphdr; if (ehdr->e_type == ET_EXEC) FLAGS(lmp) |= FLG_RT_FIXED; /* * Fill in rest of the link map entries with information from the file's * dynamic structure. */ if (dyn) { Dyninfo *dip; uint_t dynndx; Xword pltpadsz = 0; Rti_desc *rti; Dyn *pdyn; Word lmtflags = lml->lm_tflags; int ignore = 0; /* * Note, we use DT_NULL to terminate processing, and the * dynamic entry count as a fall back. Normally, a DT_NULL * entry marks the end of the dynamic section. Any non-NULL * items following the first DT_NULL are silently ignored. * This situation should only occur through use of elfedit(1) * or a similar tool. */ for (dynndx = 0, pdyn = NULL, dip = DYNINFO(lmp); dynndx < dyncnt; dynndx++, pdyn = dyn++, dip++) { if (ignore) { dip->di_flags |= FLG_DI_IGNORE; continue; } switch ((Xword)dyn->d_tag) { case DT_NULL: dip->di_flags |= ignore = FLG_DI_IGNORE; break; case DT_POSFLAG_1: dip->di_flags |= FLG_DI_POSFLAG1; break; case DT_NEEDED: case DT_USED: dip->di_flags |= FLG_DI_NEEDED; /* BEGIN CSTYLED */ if (pdyn && (pdyn->d_tag == DT_POSFLAG_1)) { /* * Identify any non-deferred lazy load for * future processing, unless LD_NOLAZYLOAD * has been set. */ if ((pdyn->d_un.d_val & DF_P1_LAZYLOAD) && ((lmtflags & LML_TFLG_NOLAZYLD) == 0)) dip->di_flags |= FLG_DI_LAZY; /* * Identify any group permission * requirements. */ if (pdyn->d_un.d_val & DF_P1_GROUPPERM) dip->di_flags |= FLG_DI_GROUP; /* * Identify any deferred dependencies. */ if (pdyn->d_un.d_val & DF_P1_DEFERRED) dip->di_flags |= FLG_DI_DEFERRED; } /* END CSTYLED */ break; case DT_SYMTAB: SYMTAB(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_SUNW_SYMTAB: SUNWSYMTAB(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_SUNW_SYMSZ: SUNWSYMSZ(lmp) = dyn->d_un.d_val; break; case DT_STRTAB: STRTAB(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_SYMENT: SYMENT(lmp) = dyn->d_un.d_val; break; case DT_FEATURE_1: if (dyn->d_un.d_val & DTF_1_CONFEXP) crle = 1; break; case DT_MOVESZ: MOVESZ(lmp) = dyn->d_un.d_val; FLAGS(lmp) |= FLG_RT_MOVE; break; case DT_MOVEENT: MOVEENT(lmp) = dyn->d_un.d_val; break; case DT_MOVETAB: MOVETAB(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_REL: case DT_RELA: /* * At this time, ld.so. can only handle one * type of relocation per object. */ REL(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_RELSZ: case DT_RELASZ: RELSZ(lmp) = dyn->d_un.d_val; break; case DT_RELENT: case DT_RELAENT: RELENT(lmp) = dyn->d_un.d_val; break; case DT_RELCOUNT: case DT_RELACOUNT: RELACOUNT(lmp) = (uint_t)dyn->d_un.d_val; break; case DT_HASH: HASH(lmp) = (uint_t *)(dyn->d_un.d_ptr + base); break; case DT_PLTGOT: PLTGOT(lmp) = (uint_t *)(dyn->d_un.d_ptr + base); break; case DT_PLTRELSZ: PLTRELSZ(lmp) = dyn->d_un.d_val; break; case DT_JMPREL: JMPREL(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_INIT: if (dyn->d_un.d_ptr != 0) INIT(lmp) = (void (*)())(dyn->d_un.d_ptr + base); break; case DT_FINI: if (dyn->d_un.d_ptr != 0) FINI(lmp) = (void (*)())(dyn->d_un.d_ptr + base); break; case DT_INIT_ARRAY: INITARRAY(lmp) = (Addr *)(dyn->d_un.d_ptr + base); break; case DT_INIT_ARRAYSZ: INITARRAYSZ(lmp) = (uint_t)dyn->d_un.d_val; break; case DT_FINI_ARRAY: FINIARRAY(lmp) = (Addr *)(dyn->d_un.d_ptr + base); break; case DT_FINI_ARRAYSZ: FINIARRAYSZ(lmp) = (uint_t)dyn->d_un.d_val; break; case DT_PREINIT_ARRAY: PREINITARRAY(lmp) = (Addr *)(dyn->d_un.d_ptr + base); break; case DT_PREINIT_ARRAYSZ: PREINITARRAYSZ(lmp) = (uint_t)dyn->d_un.d_val; break; case DT_RPATH: case DT_RUNPATH: rpath = dyn->d_un.d_val; break; case DT_FILTER: dip->di_flags |= FLG_DI_STDFLTR; fltr = dyn->d_un.d_val; OBJFLTRNDX(lmp) = dynndx; FLAGS1(lmp) |= FL1_RT_OBJSFLTR; break; case DT_AUXILIARY: dip->di_flags |= FLG_DI_AUXFLTR; if (!(rtld_flags & RT_FL_NOAUXFLTR)) { fltr = dyn->d_un.d_val; OBJFLTRNDX(lmp) = dynndx; } FLAGS1(lmp) |= FL1_RT_OBJAFLTR; break; case DT_SUNW_FILTER: dip->di_flags |= (FLG_DI_STDFLTR | FLG_DI_SYMFLTR); SYMSFLTRCNT(lmp)++; FLAGS1(lmp) |= FL1_RT_SYMSFLTR; break; case DT_SUNW_AUXILIARY: dip->di_flags |= (FLG_DI_AUXFLTR | FLG_DI_SYMFLTR); if (!(rtld_flags & RT_FL_NOAUXFLTR)) { SYMAFLTRCNT(lmp)++; } FLAGS1(lmp) |= FL1_RT_SYMAFLTR; break; case DT_DEPAUDIT: if (!(rtld_flags & RT_FL_NOAUDIT)) { audit = dyn->d_un.d_val; FLAGS1(lmp) |= FL1_RT_DEPAUD; } break; case DT_CONFIG: cfile = dyn->d_un.d_val; break; case DT_DEBUG: /* * DT_DEBUG entries are only created in * dynamic objects that require an interpretor * (ie. all dynamic executables and some shared * objects), and provide for a hand-shake with * old debuggers. This entry is initialized to * zero by the link-editor. If a debugger is * monitoring us, and has updated this entry, * set the debugger monitor flag, and finish * initializing the debugging structure. See * setup(). Also, switch off any configuration * object use as most debuggers can't handle * fixed dynamic executables as dependencies. */ if (dyn->d_un.d_ptr) rtld_flags |= (RT_FL_DEBUGGER | RT_FL_NOOBJALT); dyn->d_un.d_ptr = (Addr)&r_debug; break; case DT_VERNEED: VERNEED(lmp) = (Verneed *)(dyn->d_un.d_ptr + base); break; case DT_VERNEEDNUM: /* LINTED */ VERNEEDNUM(lmp) = (int)dyn->d_un.d_val; break; case DT_VERDEF: VERDEF(lmp) = (Verdef *)(dyn->d_un.d_ptr + base); break; case DT_VERDEFNUM: /* LINTED */ VERDEFNUM(lmp) = (int)dyn->d_un.d_val; break; case DT_VERSYM: /* * The Solaris ld does not produce DT_VERSYM, * but the GNU ld does, in order to support * their style of versioning, which differs * from ours in some ways, while using the * same data structures. The presence of * DT_VERSYM therefore means that GNU * versioning rules apply to the given file. * If DT_VERSYM is not present, then Solaris * versioning rules apply. */ VERSYM(lmp) = (Versym *)(dyn->d_un.d_ptr + base); break; case DT_BIND_NOW: if ((dyn->d_un.d_val & DF_BIND_NOW) && ((rtld_flags2 & RT_FL2_BINDLAZY) == 0)) { MODE(lmp) |= RTLD_NOW; MODE(lmp) &= ~RTLD_LAZY; } break; case DT_SYMBOLIC: /* * GNU ld uses DT_SYMBOLIC (tag 16) for * -Bsymbolic. Handle it the same as * DF_SYMBOLIC within DT_FLAGS. */ FLAGS1(lmp) |= FL1_RT_SYMBOLIC; break; case DT_FLAGS: FLAGS1(lmp) |= FL1_RT_DTFLAGS; if (dyn->d_un.d_val & DF_SYMBOLIC) FLAGS1(lmp) |= FL1_RT_SYMBOLIC; if ((dyn->d_un.d_val & DF_BIND_NOW) && ((rtld_flags2 & RT_FL2_BINDLAZY) == 0)) { MODE(lmp) |= RTLD_NOW; MODE(lmp) &= ~RTLD_LAZY; } /* * Capture any static TLS use, and enforce that * this object be non-deletable. */ if (dyn->d_un.d_val & DF_STATIC_TLS) { FLAGS1(lmp) |= FL1_RT_TLSSTAT; MODE(lmp) |= RTLD_NODELETE; } break; case DT_FLAGS_1: if (dyn->d_un.d_val & DF_1_DISPRELPND) FLAGS1(lmp) |= FL1_RT_DISPREL; if (dyn->d_un.d_val & DF_1_GROUP) FLAGS(lmp) |= (FLG_RT_SETGROUP | FLG_RT_PUBHDL); if ((dyn->d_un.d_val & DF_1_NOW) && ((rtld_flags2 & RT_FL2_BINDLAZY) == 0)) { MODE(lmp) |= RTLD_NOW; MODE(lmp) &= ~RTLD_LAZY; } if (dyn->d_un.d_val & DF_1_NODELETE) MODE(lmp) |= RTLD_NODELETE; if (dyn->d_un.d_val & DF_1_INITFIRST) FLAGS(lmp) |= FLG_RT_INITFRST; if (dyn->d_un.d_val & DF_1_NOOPEN) FLAGS(lmp) |= FLG_RT_NOOPEN; if (dyn->d_un.d_val & DF_1_LOADFLTR) FLAGS(lmp) |= FLG_RT_LOADFLTR; if (dyn->d_un.d_val & DF_1_NODUMP) FLAGS(lmp) |= FLG_RT_NODUMP; if (dyn->d_un.d_val & DF_1_CONFALT) crle = 1; if (dyn->d_un.d_val & DF_1_DIRECT) FLAGS1(lmp) |= FL1_RT_DIRECT; if (dyn->d_un.d_val & DF_1_NODEFLIB) FLAGS1(lmp) |= FL1_RT_NODEFLIB; if (dyn->d_un.d_val & DF_1_ENDFILTEE) FLAGS1(lmp) |= FL1_RT_ENDFILTE; if (dyn->d_un.d_val & DF_1_TRANS) FLAGS(lmp) |= FLG_RT_TRANS; /* * Global auditing is only meaningful when * specified by the initiating object of the * process - typically the dynamic executable. * If this is the initiating object, its link- * map will not yet have been added to the * link-map list, and consequently the link-map * list is empty. (see setup()). */ if (dyn->d_un.d_val & DF_1_GLOBAUDIT) { if (lml_main.lm_head == NULL) FLAGS1(lmp) |= FL1_RT_GLOBAUD; else DBG_CALL(Dbg_audit_ignore(lmp)); } /* * If this object identifies itself as an * interposer, but relocation processing has * already started, then demote it. It's too * late to guarantee complete interposition. */ /* BEGIN CSTYLED */ if (dyn->d_un.d_val & (DF_1_INTERPOSE | DF_1_SYMINTPOSE)) { if (lml->lm_flags & LML_FLG_STARTREL) { DBG_CALL(Dbg_util_intoolate(lmp)); if (lml->lm_flags & LML_FLG_TRC_ENABLE) (void) printf( MSG_INTL(MSG_LDD_REL_ERR2), NAME(lmp)); } else if (dyn->d_un.d_val & DF_1_INTERPOSE) FLAGS(lmp) |= FLG_RT_OBJINTPO; else FLAGS(lmp) |= FLG_RT_SYMINTPO; } /* END CSTYLED */ break; case DT_SYMINFO: SYMINFO(lmp) = (Syminfo *)(dyn->d_un.d_ptr + base); break; case DT_SYMINENT: SYMINENT(lmp) = dyn->d_un.d_val; break; case DT_PLTPAD: PLTPAD(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_PLTPADSZ: pltpadsz = dyn->d_un.d_val; break; case DT_SUNW_RTLDINF: /* * Maintain a list of RTLDINFO structures. * Typically, libc is the only supplier, and * only one structure is provided. However, * multiple suppliers and multiple structures * are supported. For example, one structure * may provide thread_init, and another * structure may provide atexit reservations. */ if ((rti = alist_append(&lml->lm_rti, NULL, sizeof (Rti_desc), AL_CNT_RTLDINFO)) == NULL) { remove_so(0, lmp, clmp); return (NULL); } rti->rti_lmp = lmp; rti->rti_info = (void *)(dyn->d_un.d_ptr + base); break; case DT_SUNW_SORTENT: SUNWSORTENT(lmp) = dyn->d_un.d_val; break; case DT_SUNW_SYMSORT: SUNWSYMSORT(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_SUNW_SYMSORTSZ: SUNWSYMSORTSZ(lmp) = dyn->d_un.d_val; break; case DT_DEPRECATED_SPARC_REGISTER: case M_DT_REGISTER: dip->di_flags |= FLG_DI_REGISTER; FLAGS(lmp) |= FLG_RT_REGSYMS; break; case DT_SUNW_CAP: CAP(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_SUNW_CAPINFO: CAPINFO(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_SUNW_CAPCHAIN: CAPCHAIN(lmp) = (void *)(dyn->d_un.d_ptr + base); break; case DT_SUNW_CAPCHAINENT: CAPCHAINENT(lmp) = dyn->d_un.d_val; break; case DT_SUNW_CAPCHAINSZ: CAPCHAINSZ(lmp) = dyn->d_un.d_val; break; } } /* * Update any Dyninfo string pointers now that STRTAB() is * known. */ for (dynndx = 0, dyn = DYN(lmp), dip = DYNINFO(lmp); !(dip->di_flags & FLG_DI_IGNORE); dyn++, dip++) { switch ((Xword)dyn->d_tag) { case DT_NEEDED: case DT_USED: case DT_FILTER: case DT_AUXILIARY: case DT_SUNW_FILTER: case DT_SUNW_AUXILIARY: dip->di_name = STRTAB(lmp) + dyn->d_un.d_val; break; } } /* * Assign any padding. */ if (PLTPAD(lmp)) { if (pltpadsz == (Xword)0) PLTPAD(lmp) = NULL; else PLTPADEND(lmp) = (void *)((Addr)PLTPAD(lmp) + pltpadsz); } } /* * A dynsym contains only global functions. We want to have * a version of it that also includes local functions, so that * dladdr() will be able to report names for local functions * when used to generate a stack trace for a stripped file. * This version of the dynsym is provided via DT_SUNW_SYMTAB. * * In producing DT_SUNW_SYMTAB, ld uses a non-obvious trick * in order to avoid having to have two copies of the global * symbols held in DT_SYMTAB: The local symbols are placed in * a separate section than the globals in the dynsym, but the * linker conspires to put the data for these two sections adjacent * to each other. DT_SUNW_SYMTAB points at the top of the local * symbols, and DT_SUNW_SYMSZ is the combined length of both tables. * * If the two sections are not adjacent, then something went wrong * at link time. We use ASSERT to kill the process if this is * a debug build. In a production build, we will silently ignore * the presence of the .ldynsym and proceed. We can detect this * situation by checking to see that DT_SYMTAB lies in * the range given by DT_SUNW_SYMTAB/DT_SUNW_SYMSZ. */ if ((SUNWSYMTAB(lmp) != NULL) && (((char *)SYMTAB(lmp) <= (char *)SUNWSYMTAB(lmp)) || (((char *)SYMTAB(lmp) >= (SUNWSYMSZ(lmp) + (char *)SUNWSYMTAB(lmp)))))) { ASSERT(0); SUNWSYMTAB(lmp) = NULL; SUNWSYMSZ(lmp) = 0; } /* * If configuration file use hasn't been disabled, and a configuration * file hasn't already been set via an environment variable, see if any * application specific configuration file is specified. An LD_CONFIG * setting is used first, but if this image was generated via crle(1) * then a default configuration file is a fall-back. */ if ((!(rtld_flags & RT_FL_NOCFG)) && (config->c_name == NULL)) { if (cfile) config->c_name = (const char *)(cfile + (char *)STRTAB(lmp)); else if (crle) rtld_flags |= RT_FL_CONFAPP; } if (rpath) RPATH(lmp) = (char *)(rpath + (char *)STRTAB(lmp)); if (fltr) REFNAME(lmp) = (char *)(fltr + (char *)STRTAB(lmp)); /* * For Intel ABI compatibility. It's possible that a JMPREL can be * specified without any other relocations (e.g. a dynamic executable * normally only contains .plt relocations). If this is the case then * no REL, RELSZ or RELENT will have been created. For us to be able * to traverse the .plt relocations under LD_BIND_NOW we need to know * the RELENT for these relocations. Refer to elf_reloc() for more * details. */ if (!RELENT(lmp) && JMPREL(lmp)) RELENT(lmp) = sizeof (M_RELOC); /* * Establish any per-object auditing. If we're establishing main's * link-map its too early to go searching for audit objects so just * hold the object name for later (see setup()). */ if (audit) { char *cp = audit + (char *)STRTAB(lmp); if (*cp) { if (((AUDITORS(lmp) = calloc(1, sizeof (Audit_desc))) == NULL) || ((AUDITORS(lmp)->ad_name = strdup(cp)) == NULL)) { remove_so(0, lmp, clmp); return (NULL); } if (lml_main.lm_head) { if (audit_setup(lmp, AUDITORS(lmp), 0, in_nfavl) == 0) { remove_so(0, lmp, clmp); return (NULL); } AFLAGS(lmp) |= AUDITORS(lmp)->ad_flags; lml->lm_flags |= LML_FLG_LOCAUDIT; } } } if (tphdr && (tls_assign(lml, lmp, tphdr) == 0)) { remove_so(0, lmp, clmp); return (NULL); } /* * A capabilities section should be identified by a DT_SUNW_CAP entry, * and if non-empty object capabilities are included, a PT_SUNWCAP * header should reference the section. Make sure CAP() is set * regardless. */ if ((CAP(lmp) == NULL) && cap) CAP(lmp) = cap; /* * Make sure any capabilities information or chain can be handled. */ if (CAPINFO(lmp) && (CAPINFO(lmp)[0] > CAPINFO_CURRENT)) CAPINFO(lmp) = NULL; if (CAPCHAIN(lmp) && (CAPCHAIN(lmp)[0] > CAPCHAIN_CURRENT)) CAPCHAIN(lmp) = NULL; /* * As part of processing dependencies, a file descriptor is populated * with capabilities information following validation. */ if (fdp->fd_flags & FLG_FD_ALTCHECK) { FLAGS1(lmp) |= FL1_RT_ALTCHECK; CAPSET(lmp) = fdp->fd_scapset; if (fdp->fd_flags & FLG_FD_ALTCAP) FLAGS1(lmp) |= FL1_RT_ALTCAP; } else if ((cap = CAP(lmp)) != NULL) { /* * Processing of the executable and ld.so.1 does not involve a * file descriptor as exec() did all the work, so capture the * capabilities for these cases. */ while (cap->c_tag != CA_SUNW_NULL) { switch (cap->c_tag) { case CA_SUNW_HW_1: CAPSET(lmp).sc_hw_1 = cap->c_un.c_val; break; case CA_SUNW_SF_1: CAPSET(lmp).sc_sf_1 = cap->c_un.c_val; break; case CA_SUNW_HW_2: CAPSET(lmp).sc_hw_2 = cap->c_un.c_val; break; case CA_SUNW_PLAT: CAPSET(lmp).sc_plat = STRTAB(lmp) + cap->c_un.c_ptr; break; case CA_SUNW_MACH: CAPSET(lmp).sc_mach = STRTAB(lmp) + cap->c_un.c_ptr; break; case CA_SUNW_HW_3: CAPSET(lmp).sc_hw_3 = cap->c_un.c_val; break; } cap++; } } /* * If a capabilities chain table exists, duplicate it. The chain table * is inspected for each initial call to a capabilities family lead * symbol. From this chain, each family member is inspected to * determine the 'best' family member. The chain table is then updated * so that the best member is immediately selected for any further * family searches. */ if (CAPCHAIN(lmp)) { Capchain *capchain; if ((capchain = calloc(1, CAPCHAINSZ(lmp))) == NULL) return (NULL); (void) memcpy(capchain, CAPCHAIN(lmp), CAPCHAINSZ(lmp)); CAPCHAIN(lmp) = capchain; } /* * Add the mapped object to the end of the link map list. */ lm_append(lml, lmco, lmp); /* * Start the system loading in the ELF information we'll be processing. */ if (REL(lmp)) { (void) madvise((void *)ADDR(lmp), (uintptr_t)REL(lmp) + (uintptr_t)RELSZ(lmp) - (uintptr_t)ADDR(lmp), MADV_WILLNEED); } return (lmp); } /* * Build full pathname of shared object from given directory name and filename. */ static char * elf_get_so(const char *dir, const char *file, size_t dlen, size_t flen) { static char pname[PATH_MAX]; (void) strncpy(pname, dir, dlen); pname[dlen++] = '/'; (void) strncpy(&pname[dlen], file, flen + 1); return (pname); } /* * The copy relocation is recorded in a copy structure which will be applied * after all other relocations are carried out. This provides for copying data * that must be relocated itself (ie. pointers in shared objects). This * structure also provides a means of binding RTLD_GROUP dependencies to any * copy relocations that have been taken from any group members. * * If the size of the .bss area available for the copy information is not the * same as the source of the data inform the user if we're under ldd(1) control * (this checking was only established in 5.3, so by only issuing an error via * ldd(1) we maintain the standard set by previous releases). */ int elf_copy_reloc(char *name, Sym *rsym, Rt_map *rlmp, void *radd, Sym *dsym, Rt_map *dlmp, const void *dadd) { Rel_copy rc; Lm_list *lml = LIST(rlmp); rc.r_name = name; rc.r_rsym = rsym; /* the new reference symbol and its */ rc.r_rlmp = rlmp; /* associated link-map */ rc.r_dlmp = dlmp; /* the defining link-map */ rc.r_dsym = dsym; /* the original definition */ rc.r_radd = radd; rc.r_dadd = dadd; if (rsym->st_size > dsym->st_size) rc.r_size = (size_t)dsym->st_size; else rc.r_size = (size_t)rsym->st_size; if (alist_append(©_R(dlmp), &rc, sizeof (Rel_copy), AL_CNT_COPYREL) == NULL) { if (!(lml->lm_flags & LML_FLG_TRC_WARN)) return (0); else return (1); } if (!(FLAGS1(dlmp) & FL1_RT_COPYTOOK)) { if (aplist_append(©_S(rlmp), dlmp, AL_CNT_COPYREL) == NULL) { if (!(lml->lm_flags & LML_FLG_TRC_WARN)) return (0); else return (1); } FLAGS1(dlmp) |= FL1_RT_COPYTOOK; } /* * If we are tracing (ldd), warn the user if * 1) the size from the reference symbol differs from the * copy definition. We can only copy as much data as the * reference (dynamic executables) entry allows. * 2) the copy definition has STV_PROTECTED visibility. */ if (lml->lm_flags & LML_FLG_TRC_WARN) { if (rsym->st_size != dsym->st_size) { (void) printf(MSG_INTL(MSG_LDD_CPY_SIZDIF), _conv_reloc_type(M_R_COPY), demangle(name), NAME(rlmp), EC_XWORD(rsym->st_size), NAME(dlmp), EC_XWORD(dsym->st_size)); if (rsym->st_size > dsym->st_size) (void) printf(MSG_INTL(MSG_LDD_CPY_INSDATA), NAME(dlmp)); else (void) printf(MSG_INTL(MSG_LDD_CPY_DATRUNC), NAME(rlmp)); } if (ELF_ST_VISIBILITY(dsym->st_other) == STV_PROTECTED) { (void) printf(MSG_INTL(MSG_LDD_CPY_PROT), _conv_reloc_type(M_R_COPY), demangle(name), NAME(dlmp)); } } DBG_CALL(Dbg_reloc_apply_val(lml, ELF_DBG_RTLD, (Xword)radd, (Xword)rc.r_size)); return (1); } /* * Determine the symbol location of an address within a link-map. Look for * the nearest symbol (whose value is less than or equal to the required * address). This is the object specific part of dladdr(). */ static void elf_dladdr(ulong_t addr, Rt_map *lmp, Dl_info *dlip, void **info, int flags) { ulong_t ndx, cnt, base, _value; Sym *sym, *_sym = NULL; const char *str; int _flags; uint_t *dynaddr_ndx; uint_t dynaddr_n = 0; ulong_t value; /* * If SUNWSYMTAB() is non-NULL, then it sees a special version of * the dynsym that starts with any local function symbols that exist in * the library and then moves to the data held in SYMTAB(). In this * case, SUNWSYMSZ tells us how long the symbol table is. The * availability of local function symbols will enhance the results * we can provide. * * If SUNWSYMTAB() is non-NULL, then there might also be a * SUNWSYMSORT() vector associated with it. SUNWSYMSORT() contains * an array of indices into SUNWSYMTAB, sorted by increasing * address. We can use this to do an O(log N) search instead of a * brute force search. * * If SUNWSYMTAB() is NULL, then SYMTAB() references a dynsym that * contains only global symbols. In that case, the length of * the symbol table comes from the nchain field of the related * symbol lookup hash table. */ str = STRTAB(lmp); if (SUNWSYMSZ(lmp) == 0) { sym = SYMTAB(lmp); /* * If we don't have a .hash table there are no symbols * to look at. */ if (HASH(lmp) == NULL) return; cnt = HASH(lmp)[1]; } else { sym = SUNWSYMTAB(lmp); cnt = SUNWSYMSZ(lmp) / SYMENT(lmp); dynaddr_ndx = SUNWSYMSORT(lmp); if (dynaddr_ndx != NULL) dynaddr_n = SUNWSYMSORTSZ(lmp) / SUNWSORTENT(lmp); } if (FLAGS(lmp) & FLG_RT_FIXED) base = 0; else base = ADDR(lmp); if (dynaddr_n > 0) { /* Binary search */ long low = 0, low_bnd; long high = dynaddr_n - 1, high_bnd; long mid; Sym *mid_sym; /* * Note that SUNWSYMSORT only contains symbols types that * supply memory addresses, so there's no need to check and * filter out any other types. */ low_bnd = low; high_bnd = high; while (low <= high) { mid = (low + high) / 2; mid_sym = &sym[dynaddr_ndx[mid]]; value = mid_sym->st_value + base; if (addr < value) { if ((sym[dynaddr_ndx[high]].st_value + base) >= addr) high_bnd = high; high = mid - 1; } else if (addr > value) { if ((sym[dynaddr_ndx[low]].st_value + base) <= addr) low_bnd = low; low = mid + 1; } else { _sym = mid_sym; _value = value; break; } } /* * If the above didn't find it exactly, then we must * return the closest symbol with a value that doesn't * exceed the one we are looking for. If that symbol exists, * it will lie in the range bounded by low_bnd and * high_bnd. This is a linear search, but a short one. */ if (_sym == NULL) { for (mid = low_bnd; mid <= high_bnd; mid++) { mid_sym = &sym[dynaddr_ndx[mid]]; value = mid_sym->st_value + base; if (addr >= value) { _sym = mid_sym; _value = value; } else { break; } } } } else { /* Linear search */ for (_value = 0, sym++, ndx = 1; ndx < cnt; ndx++, sym++) { /* * Skip expected symbol types that are not functions * or data: * - A symbol table starts with an undefined symbol * in slot 0. If we are using SUNWSYMTAB(), * there will be a second undefined symbol * right before the globals. * - The local part of SUNWSYMTAB() contains a * series of function symbols. Each section * starts with an initial STT_FILE symbol. */ if ((sym->st_shndx == SHN_UNDEF) || (ELF_ST_TYPE(sym->st_info) == STT_FILE)) continue; value = sym->st_value + base; if (value > addr) continue; if (value < _value) continue; _sym = sym; _value = value; /* * Note, because we accept local and global symbols * we could find a section symbol that matches the * associated address, which means that the symbol * name will be null. In this case continue the * search in case we can find a global symbol of * the same value. */ if ((value == addr) && (ELF_ST_TYPE(sym->st_info) != STT_SECTION)) break; } } _flags = flags & RTLD_DL_MASK; if (_sym) { if (_flags == RTLD_DL_SYMENT) *info = (void *)_sym; else if (_flags == RTLD_DL_LINKMAP) *info = (void *)lmp; dlip->dli_sname = str + _sym->st_name; dlip->dli_saddr = (void *)_value; } else { /* * addr lies between the beginning of the mapped segment and * the first global symbol. We have no symbol to return * and the caller requires one. We use _START_, the base * address of the mapping. */ if (_flags == RTLD_DL_SYMENT) { /* * An actual symbol struct is needed, so we * construct one for _START_. To do this in a * fully accurate way requires a different symbol * for each mapped segment. This requires the * use of dynamic memory and a mutex. That's too much * plumbing for a fringe case of limited importance. * * Fortunately, we can simplify: * - Only the st_size and st_info fields are useful * outside of the linker internals. The others * reference things that outside code cannot see, * and can be set to 0. * - It's just a label and there is no size * to report. So, the size should be 0. * This means that only st_info needs a non-zero * (constant) value. A static struct will suffice. * It must be const (readonly) so the caller can't * change its meaning for subsequent callers. */ static const Sym fsym = { 0, 0, 0, ELF_ST_INFO(STB_LOCAL, STT_OBJECT) }; *info = (void *) &fsym; } dlip->dli_sname = MSG_ORIG(MSG_SYM_START); dlip->dli_saddr = (void *) ADDR(lmp); } } /* * This routine is called as a last fall-back to search for a symbol from a * standard relocation or dlsym(). To maintain lazy loadings goal of reducing * the number of objects mapped, any symbol search is first carried out using * the objects that already exist in the process (either on a link-map list or * handle). If a symbol can't be found, and lazy dependencies are still * pending, this routine loads the dependencies in an attempt to locate the * symbol. */ int elf_lazy_find_sym(Slookup *slp, Sresult *srp, uint_t *binfo, int *in_nfavl) { static APlist *alist = NULL; Aliste idx1; Rt_map *lmp1, *lmp = slp->sl_imap, *clmp = slp->sl_cmap; const char *name = slp->sl_name; Slookup sl1 = *slp; Lm_list *lml; Lm_cntl *lmc; /* * It's quite possible we've been here before to process objects, * therefore reinitialize our dynamic list. */ if (alist) aplist_reset(alist); /* * Discard any relocation index from further symbol searches. This * index has already been used to trigger any necessary lazy-loads, * and it might be because one of these lazy loads has failed that * we're performing this fallback. By removing the relocation index * we don't try and perform the same failed lazy loading activity again. */ sl1.sl_rsymndx = 0; /* * Determine the callers link-map list so that we can monitor whether * new objects have been added. */ lml = LIST(clmp); lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, CNTL(clmp)); /* * Generate a local list of new objects to process. This list can grow * as each object supplies its own lazy dependencies. */ if (aplist_append(&alist, lmp, AL_CNT_LAZYFIND) == NULL) return (0); for (APLIST_TRAVERSE(alist, idx1, lmp1)) { uint_t dynndx; Dyninfo *dip, *pdip; /* * Loop through the lazy DT_NEEDED entries examining each object * for the required symbol. If the symbol is not found, the * object is in turn added to the local alist, so that the * objects lazy DT_NEEDED entries can be examined. */ lmp = lmp1; for (dynndx = 0, dip = DYNINFO(lmp), pdip = NULL; !(dip->di_flags & FLG_DI_IGNORE); dynndx++, pdip = dip++) { Grp_hdl *ghp; Grp_desc *gdp; Rt_map *nlmp, *llmp; Slookup sl2; Sresult sr; Aliste idx2; if (((dip->di_flags & FLG_DI_LAZY) == 0) || dip->di_info) continue; /* * If this object has already failed to lazy load, and * we're still processing the same runtime linker * operation that produced the failure, don't bother * to try and load the object again. */ if ((dip->di_flags & FLG_DI_LAZYFAIL) && pdip && (pdip->di_flags & FLG_DI_POSFLAG1)) { if (pdip->di_info == (void *)ld_entry_cnt) continue; dip->di_flags &= ~FLG_DI_LAZYFAIL; pdip->di_info = NULL; } /* * Determine the last link-map presently on the callers * link-map control list. */ llmp = lmc->lc_tail; /* * Try loading this lazy dependency. If the object * can't be loaded, consider this non-fatal and continue * the search. Lazy loaded dependencies need not exist * and their loading should only turn out to be fatal * if they are required to satisfy a relocation. * * A successful lazy load can mean one of two things: * * - new objects have been loaded, in which case the * objects will have been analyzed, relocated, and * finally moved to the callers control list. * - the objects are already loaded, and this lazy * load has simply associated the referenced object * with it's lazy dependencies. * * If new objects are loaded, look in these objects * first. Note, a new object can be the object being * referenced by this lazy load, however we can also * descend into multiple lazy loads as we relocate this * reference. * * If the symbol hasn't been found, use the referenced * objects handle, as it might have dependencies on * objects that are already loaded. Note that existing * objects might have already been searched and skipped * as non-available to this caller. However, a lazy * load might have caused the promotion of modes, or * added this object to the family of the caller. In * either case, the handle associated with the object * is then used to carry out the symbol search. */ if ((nlmp = elf_lazy_load(lmp, &sl1, dynndx, name, FLG_RT_PRIHDL, &ghp, in_nfavl)) == NULL) continue; if (NEXT_RT_MAP(llmp)) { /* * Look in any new objects. */ sl1.sl_imap = NEXT_RT_MAP(llmp); sl1.sl_flags &= ~LKUP_STDRELOC; /* * Initialize a local symbol result descriptor, * using the original symbol name. */ SRESULT_INIT(sr, slp->sl_name); if (lookup_sym(&sl1, &sr, binfo, in_nfavl)) { *srp = sr; return (1); } } /* * Use the objects handle to inspect the family of * objects associated with the handle. Note, there's * a possibility of overlap with the above search, * should a lazy load bring in new objects and * reference existing objects. */ sl2 = sl1; for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { if ((gdp->gd_depend != NEXT_RT_MAP(llmp)) && (gdp->gd_flags & GPD_DLSYM)) { sl2.sl_imap = gdp->gd_depend; sl2.sl_flags |= LKUP_FIRST; /* * Initialize a local symbol result * descriptor, using the original * symbol name. */ SRESULT_INIT(sr, slp->sl_name); if (lookup_sym(&sl2, &sr, binfo, in_nfavl)) { *srp = sr; return (1); } } } /* * Some dlsym() operations are already traversing a * link-map (dlopen(0)), and thus there's no need to * save them on the dynamic dependency list. */ if (slp->sl_flags & LKUP_NODESCENT) continue; if (aplist_test(&alist, nlmp, AL_CNT_LAZYFIND) == 0) return (0); } } return (0); } /* * Warning message for bad r_offset. */ void elf_reloc_bad(Rt_map *lmp, void *rel, uchar_t rtype, ulong_t roffset, ulong_t rsymndx) { const char *name = NULL; Lm_list *lml = LIST(lmp); int trace; if ((lml->lm_flags & LML_FLG_TRC_ENABLE) && (((rtld_flags & RT_FL_SILENCERR) == 0) || (lml->lm_flags & LML_FLG_TRC_VERBOSE))) trace = 1; else trace = 0; if ((trace == 0) && (DBG_ENABLED == 0)) return; if (rsymndx) { Sym *symref = (Sym *)((ulong_t)SYMTAB(lmp) + (rsymndx * SYMENT(lmp))); if (ELF_ST_BIND(symref->st_info) != STB_LOCAL) name = (char *)(STRTAB(lmp) + symref->st_name); } if (name == NULL) name = MSG_INTL(MSG_STR_UNKNOWN); if (trace) { const char *rstr; rstr = _conv_reloc_type((uint_t)rtype); (void) printf(MSG_INTL(MSG_LDD_REL_ERR1), rstr, name, EC_ADDR(roffset)); return; } Dbg_reloc_error(lml, ELF_DBG_RTLD, M_MACH, M_REL_SHT_TYPE, rel, name); } /* * Resolve a static TLS relocation. */ long elf_static_tls(Rt_map *lmp, Sym *sym, void *rel, uchar_t rtype, char *name, ulong_t roffset, long value) { Lm_list *lml = LIST(lmp); /* * Relocations against a static TLS block have limited support once * process initialization has completed. Any error condition should be * discovered by testing for DF_STATIC_TLS as part of loading an object, * however individual relocations are tested in case the dynamic flag * had not been set when this object was built. */ if (PTTLS(lmp) == NULL) { DBG_CALL(Dbg_reloc_in(lml, ELF_DBG_RTLD, M_MACH, M_REL_SHT_TYPE, rel, NULL, 0, name)); eprintf(lml, ERR_FATAL, MSG_INTL(MSG_REL_BADTLS), _conv_reloc_type((uint_t)rtype), NAME(lmp), name ? demangle(name) : MSG_INTL(MSG_STR_UNKNOWN)); return (0); } /* * If no static TLS has been set aside for this object, determine if * any can be obtained. Enforce that any object using static TLS is * non-deletable. */ if (TLSSTATOFF(lmp) == 0) { FLAGS1(lmp) |= FL1_RT_TLSSTAT; MODE(lmp) |= RTLD_NODELETE; if (tls_assign(lml, lmp, PTTLS(lmp)) == 0) { DBG_CALL(Dbg_reloc_in(lml, ELF_DBG_RTLD, M_MACH, M_REL_SHT_TYPE, rel, NULL, 0, name)); eprintf(lml, ERR_FATAL, MSG_INTL(MSG_REL_BADTLS), _conv_reloc_type((uint_t)rtype), NAME(lmp), name ? demangle(name) : MSG_INTL(MSG_STR_UNKNOWN)); return (0); } } /* * Typically, a static TLS offset is maintained as a symbols value. * For local symbols that are not apart of the dynamic symbol table, * the TLS relocation points to a section symbol, and the static TLS * offset was deposited in the associated GOT table. Make sure the GOT * is cleared, so that the value isn't reused in do_reloc(). */ if (ELF_ST_BIND(sym->st_info) == STB_LOCAL) { if ((ELF_ST_TYPE(sym->st_info) == STT_SECTION)) { value = *(long *)roffset; *(long *)roffset = 0; } else { value = sym->st_value; } } return (-(TLSSTATOFF(lmp) - value)); } /* * If the symbol is not found and the reference was not to a weak symbol, report * an error. Weak references may be unresolved. */ int elf_reloc_error(Rt_map *lmp, const char *name, void *rel, uint_t binfo) { Lm_list *lml = LIST(lmp); /* * Under crle(1), relocation failures are ignored. */ if (lml->lm_flags & LML_FLG_IGNRELERR) return (1); /* * Under ldd(1), unresolved references are reported. However, if the * original reference is EXTERN or PARENT these references are ignored * unless ldd's -p option is in effect. */ if (lml->lm_flags & LML_FLG_TRC_WARN) { if (((binfo & DBG_BINFO_REF_MSK) == 0) || ((lml->lm_flags & LML_FLG_TRC_NOPAREXT) != 0)) { (void) printf(MSG_INTL(MSG_LDD_SYM_NFOUND), demangle(name), NAME(lmp)); } return (1); } /* * Otherwise, the unresolved references is fatal. */ DBG_CALL(Dbg_reloc_in(lml, ELF_DBG_RTLD, M_MACH, M_REL_SHT_TYPE, rel, NULL, 0, name)); eprintf(lml, ERR_FATAL, MSG_INTL(MSG_REL_NOSYM), NAME(lmp), demangle(name)); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Garrett D'Amore * Copyright (c) 2017, Joyent, Inc. * Copyright 2025 Oxide Computer Company */ /* * Implementation of all external interfaces between ld.so.1 and libc. * * This file started as a set of routines that provided synchronization and * locking operations using calls to libthread. libthread has merged with libc * under the Unified Process Model (UPM), and things have gotten a lot simpler. * This file continues to establish and redirect various events within ld.so.1 * to interfaces within libc. * * Until libc is loaded and relocated, any external interfaces are captured * locally. Each link-map list maintains its own set of external vectors, as * each link-map list typically provides its own libc. Although this per-link- * map list vectoring provides a degree of flexibility, there is a protocol * expected when calling various libc interfaces. * * i. Any new alternative link-map list should call CI_THRINIT, and then call * CI_TLS_MODADD to register any TLS for each object of that link-map list * (this item is labeled i. as auditors can be the first objects loaded, * and they exist on their own lik-map list). * * ii. For the primary link-map list, CI_TLS_STATMOD must be called first to * register any static TLS. This routine is called regardless of there * being any TLS, as this routine also establishes the link-map list as the * primary list and fixes the association of uberdata). CI_THRINIT should * then be called. * * iii. Any objects added to an existing link-map list (primary or alternative) * should call CI_TLS_MODADD to register any additional TLS. * * These events are established by: * * i. Typically, libc is loaded as part of the primary dependencies of any * link-map list (since the Unified Process Model (UPM), libc can't be * lazily loaded). To minimize the possibility of loading and registering * objects, and then tearing them down (because of a relocation error), * external vectors are established as part of load_completion(). This * routine is called on completion of any operation that can cause objects * to be loaded. This point of control insures the objects have been fully * analyzed and relocated, and moved to their controlling link-map list. * The external vectors are established prior to any .inits being fired. * * ii. Calls to CI_THRINIT, and CI_TLS_MODADD also occur as part of * load_completion(). CI_THRINIT is only called once for each link-map * control list. * * iii. Calls to CI_TLS_STATMOD, and CI_THRINIT occur for the primary link-map * list in the final stages of setup(). * * The interfaces provide by libc can be divided into two families. The first * family consists of those interfaces that should be called from the link-map * list. It's possible that these interfaces convey state concerning the * link-map list they are part of: * * CI_ATEXIT * CI TLS_MODADD * CI_TLS_MODREM * CI_TLS_STATMOD * CI_THRINIT * * The second family are global in nature, that is, the link-map list from * which they are called provides no state information. In fact, for * CI_BIND_GUARD, the calling link-map isn't even known. The link-map can only * be deduced after ld.so.1's global lock has been obtained. Therefore, the * following interfaces are also maintained as global: * * CI_LCMESSAGES * CI_BIND_GUARD * CI_BIND_CLEAR * CI_THR_SELF * * Note, it is possible that these global interfaces are obtained from an * alternative link-map list that gets torn down because of a processing * failure (unlikely, because the link-map list components must be analyzed * and relocated prior to load_completion(), but perhaps the tear down is still * a possibility). Thus the global interfaces may have to be replaced. Once * the interfaces have been obtained from the primary link-map, they can * remain fixed, as the primary link-map isn't going to go anywhere. * * The last wrinkle in the puzzle is what happens if an alternative link-map * is loaded with no libc dependency? In this case, the alternative objects * can not call CI_THRINIT, can not be allowed to use TLS, and will not receive * any atexit processing. * * The history of these external interfaces is defined by their version: * * TI_VERSION == 1 * Under this model libthread provided rw_rwlock/rw_unlock, through which * all rt_mutex_lock/rt_mutex_unlock calls were vectored. * Under libc/libthread these interfaces provided _sigon/_sigoff (unlike * lwp/libthread that provided signal blocking via bind_guard/bind_clear). * * TI_VERSION == 2 * Under this model only libthreads bind_guard/bind_clear and thr_self * interfaces were used. Both libthreads blocked signals under the * bind_guard/bind_clear interfaces. Lower level locking is derived * from internally bound _lwp_ interfaces. This removes recursive * problems encountered when obtaining locking interfaces from libthread. * The use of mutexes over reader/writer locks also enables the use of * condition variables for controlling thread concurrency (allows access * to objects only after their .init has completed). * * NOTE, the TI_VERSION indicated the ti_interface version number, where the * ti_interface was a large vector of functions passed to both libc (to override * the thread stub interfaces) and ld.so.1. ld.so.1 used only a small subset of * these interfaces. * * CI_VERSION == 1 * Introduced with CI_VERSION & CI_ATEXIT * * CI_VERSION == 2 (Solaris 8 update 2). * Added support for CI_LCMESSAGES * * CI_VERSION == 3 (Solaris 9). * Added the following versions to the CI table: * * CI_BIND_GUARD, CI_BIND_CLEAR, CI_THR_SELF * CI_TLS_MODADD, CI_TLS_MOD_REMOVE, CI_TLS_STATMOD * * This version introduced the DT_SUNW_RTLDINFO structure as a mechanism * to handshake with ld.so.1. * * CI_VERSION == 4 (Solaris 10). * Added the CI_THRINIT handshake as part of the libc/libthread unified * process model. libc now initializes the current thread pointer from * this interface (and no longer relies on the INITFIRST flag - which * others have started to camp out on). * * CI_VERSION == 5 (Solaris 11). * Use of "protected" references within libc, so that symbols are * pre-bound, and don't require ld.so.1 binding. This implementation * protects libc's critical regions from being vectored to auditors. * * CI_VERSION == 6 (Solaris 11). * Added the CI_CRITICAL handshake, to allow "mem*" family to be reexposed * as "global", and thus be redirected to auxiliary filters. * * Release summary: * * Solaris 8 CI_ATEXIT via _ld_libc() * TI_* via _ld_concurrency() * * Solaris 9 CI_ATEXIT and CI_LCMESSAGES via _ld_libc() * CI_* via RTLDINFO and _ld_libc() - new libthread * TI_* via _ld_concurrency() - old libthread * * Solaris 10 CI_ATEXIT and CI_LCMESSAGES via _ld_libc() * CI_* via RTLDINFO and _ld_libc() - new libthread */ #include #include #include #include #include #include #include #include #include #include #include #include #include "_elf.h" #include "_rtld.h" /* * This interface provides the unified process model communication between * ld.so.1 and libc. This interface can be called a number of times: * * - Initially, this interface is called to process RTLDINFO. This data * structure is typically provided by libc, and contains the address of * libc interfaces that must be called to initialize threads information. * * - _ld_libc(), this interface can also be called by libc at process * initialization, after libc has been loaded and relocated, but before * control has been passed to any user code (.init's or main()). This * call provides additional libc interface information that ld.so.1 must * call during process execution. * * - _ld_libc() can also be called by libc during process execution to * re-establish interfaces such as the locale. */ static void get_lcinterface(Rt_map *lmp, Lc_interface *funcs) { int threaded = 0, entry = 0, tag; Lm_list *lml; Lc_desc *lcp; if ((lmp == NULL) || (funcs == NULL)) return; /* * Once the process is active, ensure we grab a lock. */ if (rtld_flags & RT_FL_APPLIC) entry = enter(0); lml = LIST(lmp); lcp = &lml->lm_lcs[0]; DBG_CALL(Dbg_util_nl(lml, DBG_NL_STD)); for (tag = funcs->ci_tag; tag; tag = (++funcs)->ci_tag) { char *gptr; char *lptr = funcs->ci_un.ci_ptr; DBG_CALL(Dbg_util_lcinterface(lmp, tag, lptr)); if (tag >= CI_MAX) continue; /* * Maintain all interfaces on a per-link-map basis. Note, for * most interfaces, only the first interface is used for any * link-map list. This prevents accidents with developers who * manage to load two different versions of libc. */ if ((lcp[tag].lc_lmp) && (tag != CI_LCMESSAGES) && (tag != CI_VERSION)) { DBG_CALL(Dbg_unused_lcinterface(lmp, lcp[tag].lc_lmp, tag)); continue; } lcp[tag].lc_un.lc_ptr = lptr; lcp[tag].lc_lmp = lmp; gptr = glcs[tag].lc_un.lc_ptr; /* * Process any interfaces that must be maintained on a global * basis. */ switch (tag) { case CI_ATEXIT: break; case CI_LCMESSAGES: /* * At startup, ld.so.1 can establish a locale from one * of the locale family of environment variables (see * ld_str_env() and readenv_user()). During process * execution the locale can also be changed by the user. * This interface is called from libc should the locale * be modified. Presently, only one global locale is * maintained for all link-map lists, and only objects * on the primrary link-map may change this locale. */ if ((lml->lm_flags & LML_FLG_BASELM) && ((gptr == NULL) || (strcmp(gptr, lptr) != 0))) { /* * If we've obtained a message locale (typically * supplied via libc's setlocale()), then * register the locale for use in dgettext() so * as to reestablish the locale for ld.so.1's * messages. */ if (gptr) { free((void *)gptr); rtld_flags |= RT_FL_NEWLOCALE; } glcs[tag].lc_un.lc_ptr = strdup(lptr); /* * Clear any cached messages. */ bzero(err_strs, sizeof (err_strs)); nosym_str = NULL; } break; case CI_BIND_GUARD: case CI_BIND_CLEAR: case CI_THR_SELF: case CI_CRITICAL: /* * If the global vector is unset, or this is the primary * link-map, set the global vector. */ if ((gptr == NULL) || (lml->lm_flags & LML_FLG_BASELM)) glcs[tag].lc_un.lc_ptr = lptr; /* FALLTHROUGH */ case CI_TLS_MODADD: case CI_TLS_MODREM: case CI_TLS_STATMOD: case CI_THRINIT: threaded++; break; case CI_VERSION: if ((rtld_flags2 & RT_FL2_RTLDSEEN) == 0) { Aliste idx; Lm_list *lml2; int version; rtld_flags2 |= RT_FL2_RTLDSEEN; version = funcs->ci_un.ci_val; #if defined(CI_V_FIVE) if (version >= CI_V_FIVE) { thr_flg_nolock = THR_FLG_NOLOCK; thr_flg_reenter = THR_FLG_REENTER; } #endif if (version < CI_V_FOUR) break; rtld_flags2 |= RT_FL2_UNIFPROC; /* * We might have seen an auditor which is not * dependent on libc. Such an auditor's link * map list has LML_FLG_HOLDLOCK set. This * lock needs to be dropped. Refer to * audit_setup() in audit.c. */ if ((rtld_flags2 & RT_FL2_HASAUDIT) == 0) break; /* * Yes, we did. Take care of them. */ for (APLIST_TRAVERSE(dynlm_list, idx, lml2)) { Rt_map *map = (Rt_map *)lml2->lm_head; if (FLAGS(map) & FLG_RT_AUDIT) { lml2->lm_flags &= ~LML_FLG_HOLDLOCK; } } } break; default: break; } } if (threaded) { /* * If a version of libc gives us only a subset of the TLS * interfaces, it's confused and we discard the whole lot. */ if (((lcp[CI_TLS_MODADD].lc_un.lc_func != NULL) && (lcp[CI_TLS_MODREM].lc_un.lc_func != NULL) && (lcp[CI_TLS_STATMOD].lc_un.lc_func != NULL)) == 0) { lcp[CI_TLS_MODADD].lc_un.lc_func = NULL; lcp[CI_TLS_MODREM].lc_un.lc_func = NULL; lcp[CI_TLS_STATMOD].lc_un.lc_func = NULL; } /* * Indicate that we're now thread capable. */ if ((lml->lm_flags & LML_FLG_RTLDLM) == 0) rtld_flags |= RT_FL_THREADS; } if (entry) leave(lml, 0); } /* * At this point we know we have a set of objects that have been fully analyzed * and relocated. Prior to the next major step of running .init sections (ie. * running user code), retrieve any RTLDINFO interfaces. */ int rt_get_extern(Lm_list *lml, Rt_map *lmp) { if (lml->lm_rti) { Aliste idx; Rti_desc *rti; for (ALIST_TRAVERSE(lml->lm_rti, idx, rti)) get_lcinterface(rti->rti_lmp, rti->rti_info); free(lml->lm_rti); lml->lm_rti = 0; } /* * Perform some sanity checks. If we have TLS requirements we better * have the associated external interfaces. */ if (lml->lm_tls && (lml->lm_lcs[CI_TLS_STATMOD].lc_un.lc_func == NULL)) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_TLS_NOSUPPORT), NAME(lmp)); return (0); } return (1); } /* * Provide an interface for libc to communicate additional interface * information. */ void _ld_libc(void *ptr) { get_lcinterface(_caller(caller(), CL_EXECDEF), (Lc_interface *)ptr); } static int bindmask = 0; int rt_bind_guard(int flags) { int (*fptr)(int); int bindflag; if ((fptr = glcs[CI_BIND_GUARD].lc_un.lc_func) != NULL) { return ((*fptr)(flags)); } else { bindflag = (flags & THR_FLG_RTLD); if ((bindflag & bindmask) == 0) { bindmask |= bindflag; return (1); } return (0); } } int rt_bind_clear(int flags) { int (*fptr)(int); int bindflag; if ((fptr = glcs[CI_BIND_CLEAR].lc_un.lc_func) != NULL) { return ((*fptr)(flags)); } else { bindflag = (flags & THR_FLG_RTLD); if (bindflag == 0) return (bindmask); else { bindmask &= ~bindflag; return (0); } } } /* * Make sure threads have been initialized. This interface is called once for * each link-map list. */ void rt_thr_init(Lm_list *lml) { int (*fptr)(void); if ((fptr = lml->lm_lcs[CI_THRINIT].lc_un.lc_func) != NULL) { lml->lm_lcs[CI_THRINIT].lc_un.lc_func = NULL; leave(lml, thr_flg_reenter); (void) (*fptr)(); (void) enter(thr_flg_reenter); /* * If this is an alternative link-map list, and this is the * first call to initialize threads, don't let the destination * libc be deleted. It is possible that an auditors complete * initialization fails, but there is presently no main link-map * list. As this libc has established the thread pointer, don't * delete this libc, otherwise the initialization of libc on the * main link-map can be compromised during its threads * initialization. */ if (((lml->lm_flags & LML_FLG_BASELM) == 0) && ((rtld_flags2 & RT_FL2_PLMSETUP) == 0)) MODE(lml->lm_lcs[CI_THRINIT].lc_lmp) |= RTLD_NODELETE; } } thread_t rt_thr_self() { thread_t (*fptr)(void); if ((fptr = (thread_t (*)())glcs[CI_THR_SELF].lc_un.lc_func) != NULL) return ((*fptr)()); return (1); } int rt_mutex_lock(Rt_lock *mp) { return (_lwp_mutex_lock((lwp_mutex_t *)mp)); } int rt_mutex_unlock(Rt_lock *mp) { return (_lwp_mutex_unlock((lwp_mutex_t *)mp)); } /* * Test whether we're in a libc critical region. Certain function references, * like the "mem*" family, might require binding. Although these functions can * safely bind to auxiliary filtees, they should not be captured by auditors. */ int rt_critical() { int (*fptr)(void); if ((fptr = glcs[CI_CRITICAL].lc_un.lc_func) != NULL) return ((*fptr)()); return (0); } /* * Mutex interfaces to resolve references from any objects extracted from * libc_pic.a. Note, as ld.so.1 is essentially single threaded these can be * noops. */ #pragma weak lmutex_lock = mutex_lock /* ARGSUSED */ int mutex_lock(mutex_t *mp) { return (0); } #pragma weak lmutex_unlock = mutex_unlock /* ARGSUSED */ int mutex_unlock(mutex_t *mp) { return (0); } /* ARGSUSED */ int mutex_init(mutex_t *mp, int type, void *arg) { return (0); } /* ARGSUSED */ int mutex_destroy(mutex_t *mp) { return (0); } /* * This is needed to satisfy sysconf() (case _SC_THREAD_STACK_MIN) */ size_t thr_min_stack() { return (sizeof (uintptr_t) * 1024); } /* * Local str[n]casecmp() interfaces for the dynamic linker, * to avoid problems when linking with libc_pic.a */ int strcasecmp(const char *s1, const char *s2) { extern int ascii_strcasecmp(const char *, const char *); return (ascii_strcasecmp(s1, s2)); } int strncasecmp(const char *s1, const char *s2, size_t n) { extern int ascii_strncasecmp(const char *, const char *, size_t); return (ascii_strncasecmp(s1, s2, n)); } /* * The following functions are cancellation points in libc. * They are called from other functions in libc that we extract * and use directly. We don't do cancellation while we are in * the dynamic linker, so we redefine these to call the primitive, * non-cancellation interfaces. */ int close(int fildes) { extern int __close(int); return (__close(fildes)); } int fcntl(int fildes, int cmd, ...) { extern int __fcntl(int, int, ...); intptr_t arg, arg1 = 0; va_list ap; va_start(ap, cmd); switch (cmd) { case F_DUP3FD: arg = va_arg(ap, int); arg1 = va_arg(ap, int); break; default: arg = va_arg(ap, intptr_t); break; } va_end(ap); return (__fcntl(fildes, cmd, arg, arg1)); } int open(const char *path, int oflag, ...) { extern int __open(const char *, int, mode_t); mode_t mode; va_list ap; va_start(ap, oflag); mode = va_arg(ap, mode_t); va_end(ap); return (__open(path, oflag, mode)); } int openat(int fd, const char *path, int oflag, ...) { extern int __openat(int, const char *, int, mode_t); mode_t mode; va_list ap; va_start(ap, oflag); mode = va_arg(ap, mode_t); va_end(ap); return (__openat(fd, path, oflag, mode)); } ssize_t read(int fd, void *buf, size_t size) { extern ssize_t __read(int, void *, size_t); return (__read(fd, buf, size)); } ssize_t write(int fd, const void *buf, size_t size) { extern ssize_t __write(int, const void *, size_t); return (__write(fd, buf, size)); } /* * ASCII versions of ctype character classification functions. This avoids * pulling in the entire locale framework that is in libc. */ int isdigit(int c) { return ((c >= '0' && c <= '9') ? 1 : 0); } int isupper(int c) { return ((c >= 'A' && c <= 'Z') ? 1 : 0); } int islower(int c) { return ((c >= 'a' && c <= 'z') ? 1 : 0); } int isspace(int c) { return (((c == ' ') || (c == '\t') || (c == '\r') || (c == '\n') || (c == '\v') || (c == '\f')) ? 1 : 0); } int isxdigit(int c) { return ((isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) ? 1 : 0); } int isalpha(int c) { return ((isupper(c) || islower(c)) ? 1 : 0); } int isalnum(int c) { return ((isalpha(c) || isdigit(c)) ? 1 : 0); } #if defined(__i386) || defined(__amd64) /* * Instead of utilizing the comm page for clock_gettime and gettimeofday, rtld * uses the raw syscall instead. Doing so decreases the surface of symbols * needed from libc for a modest performance cost. */ extern int __clock_gettime_sys(clockid_t, struct timespec *); int __clock_gettime(clockid_t clock_id, struct timespec *tp) { return (__clock_gettime_sys(clock_id, tp)); } int gettimeofday(struct timeval *tv, void *tz) { if (tv != NULL) { /* * Perform the same logic as the libc gettimeofday() when it * lacks comm page support: Make the clock_gettime syscall and * divide out the tv_usec field as required. */ (void) __clock_gettime_sys(CLOCK_REALTIME, (timespec_t *)tv); tv->tv_usec /= 1000; } return (0); } #endif /* defined(__i386) || defined(__amd64) */ /* * In a similar vein to the is* functions above, we also have to define our own * version of strerror, as it is implemented in terms of the locale aware * strerror_l, and we'd rather not have the full set of libc symbols used here. */ char * strerror(int errnum) { if (errnum < _sys_num_nerr && errnum >= 0) { return (dgettext("SUNW_OST_OSLIB", &_sys_nerrs[_sys_nindex[errnum]])); } errno = EINVAL; return (dgettext("SUNW_OST_OSLIB", "Unknown error")); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1988 AT&T * All Rights Reserved * * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2022 Oxide Computer Company */ #include #include #include #include #include #include #include "_rtld.h" /* * Declarations of global variables used in ld.so. */ Rt_lock rtldlock; int thr_flg_nolock = 0; int thr_flg_reenter = 0; /* * Major link-map lists. */ Lm_list lml_main = { 0 }; /* the `main's link map list */ Lm_list lml_rtld = { 0 }; /* rtld's link map list */ /* * Entrance count. Each time ld.so.1 is entered following initial process * setup, this count is bumped. This value serves to identify the present * ld.so.1 operation. * * An ld.so.1 operation can result in many symbol lookup requests (i.e., loading * objects and relocating all symbolic bindings). This count is used to protect * against attempting to re-load a failed lazy load within a single call to * ld.so.1, while allowing such attempts across calls. Should a lazy load fail, * the present operation identifier is saved in the current symbol lookup data * block (Slookup). Should a lazy load fall back operation be triggered, the * identifier in the symbol lookup block is compared to the current ld.so.1 * entry count, and if the two are equal the fall back is skipped. * * With this count, there is a danger of wrap-around, although as an unsigned * 32-bit value, it is highly unlikely that any application could usefully make * 4.3 giga-calls into ld.so.1. The worst that can occur is that a fall back * lazy load isn't triggered. However, most lazy loads that fail typically * continue to fail unless the user takes corrective action (adds the necessary * (fixed) dependencies to the system). */ ulong_t ld_entry_cnt = 1; /* * BEGIN: Exposed to rtld_db, don't change without a coordinated handshake with * librtld_db (remembering that librtld_db must be able to read old as well as * current core files). */ APlist *dynlm_list = NULL; /* dynamic list of link-maps */ /* * END: Exposed to rtld_db */ Reglist *reglist = NULL; /* list of register symbols */ /* * Set of integers to track how many of what type of PLT's have been bound. * This is only really interesting for SPARC since ia32 has only one PLT. */ uint32_t pltcnt21d = 0; uint32_t pltcnt24d = 0; uint32_t pltcntu32 = 0; uint32_t pltcntu44 = 0; uint32_t pltcntfull = 0; uint32_t pltcntfar = 0; /* * AVL tree pointers. */ avl_tree_t *capavl = NULL; /* capabilities files */ avl_tree_t *nfavl = NULL; /* not-found path names */ avl_tree_t *spavl = NULL; /* secure path names */ /* * Various other global data. */ uint_t rtld_flags = 0; uint_t rtld_flags2 = 0; Lc_desc glcs[CI_MAX]; /* global external interfaces */ const char *procname = NULL; const char *rtldname = MSG_ORIG(MSG_FIL_RTLD); char *lasterr = NULL; /* string describing last error */ /* cleared by each dlerror() */ Interp *interp = NULL; /* ELF interpreter info */ APlist *hdl_alp[HDLIST_SZ+2]; /* dlopen() handle list */ size_t syspagsz = 0; /* system page size */ ulong_t at_flags = 0; /* machine specific file flags */ Uts_desc *uts = NULL; /* utsname descriptor */ Isa_desc *isa = NULL; /* isalist descriptor */ uint_t audit_argcnt = 64; /* no. of stack args to copy (default */ /* is all) */ Audit_desc *auditors = NULL; /* global auditors (LD_AUDIT) */ APlist *aud_preinit = NULL; /* list of objects defining local */ APlist *aud_activity = NULL; /* preinit and activity auditors */ const char *rpl_audit = NULL; /* replaceable LD_AUDIT string */ const char *rpl_debug = NULL; /* replaceable LD_DEBUG string */ const char *rpl_ldflags = NULL; /* replaceable LD_FLAGS string */ const char *rpl_libpath = NULL; /* replaceable LD_LIBRARY_PATH string */ Alist *rpl_libdirs = NULL; /* and associated Pdesc list */ const char *rpl_preload = NULL; /* replaceable LD_PRELOAD string */ const char *prm_audit = NULL; /* permanent LD_AUDIT string */ const char *prm_debug = NULL; /* permanent LD_DEBUG string */ const char *prm_ldflags = NULL; /* permanent LD_FLAGS string */ const char *prm_libpath = NULL; /* permanent LD_LIBRARY_PATH string */ Alist *prm_libdirs = NULL; /* and associated Pdesc list */ const char *prm_preload = NULL; /* permanent LD_PRELOAD string */ uint_t env_info = 0; /* information regarding environment */ /* variables */ int killsig = SIGKILL; /* signal sent on fatal exit */ APlist *free_alp = NULL; /* defragmentation list */ /* * Capabilities are provided by the system. However, users can define an * alternative set of system capabilities, where they can add, subtract, or * override the system capabilities for testing purposes. Furthermore, these * alternative capabilities can be specified such that they only apply to * specified files rather than to all objects. * * The org_scapset is relied upon by the amd64 version of elf_rtbndr to * determine whether or not AVX registers are present in the system. */ static Syscapset scapset = { 0 }; Syscapset *org_scapset = &scapset; /* original system and */ Syscapset *alt_scapset = &scapset; /* alternative system */ /* capabilities */ const char *rpl_hwcap = NULL; /* replaceable hwcap str */ const char *rpl_sfcap = NULL; /* replaceable sfcap str */ const char *rpl_machcap = NULL; /* replaceable machcap str */ const char *rpl_platcap = NULL; /* replaceable platcap str */ const char *rpl_cap_files = NULL; /* associated files */ const char *prm_hwcap = NULL; /* permanent hwcap str */ const char *prm_sfcap = NULL; /* permanent sfcap str */ const char *prm_machcap = NULL; /* permanent machcap str */ const char *prm_platcap = NULL; /* permanent platcap str */ const char *prm_cap_files = NULL; /* associated files */ /* * Note, the debugging descriptor interposes on the default definition provided * by liblddbg. This is required as ld.so.1 must only have outstanding relative * relocations. */ static Dbg_desc _dbg_desc = {0, 0, 0}; Dbg_desc *dbg_desc = &_dbg_desc; /* debugging descriptor */ const char *dbg_file = NULL; /* debugging directed to file */ #pragma weak environ = _environ /* environ for PLT tracing - we */ char **_environ = NULL; /* supply the pair to satisfy any */ /* libc requirements (hwmuldiv) */ const char *profile_name = NULL; /* object being profiled */ const char *profile_out = NULL; /* profile output file */ const char *profile_lib = NULL; /* audit library to perform profile */ uchar_t search_rules[] = { /* dependency search rules */ RPLENV, /* replaceable LD_LIBRARY_PATH */ PRMENV, /* permanent LD_LIBRARY_PATH */ RUNPATH, /* callers runpath */ DEFAULT, /* default library path */ 0 }; Dl_argsinfo argsinfo = { 0 }; /* process argument, environment and */ /* auxv information. */ /* * Frequently used messages are cached here to reduce _dgettext() overhead and * also provide for resetting should the locale change (see _ld_libc()). */ const char *err_strs[ERR_NUM] = { NULL }; const char *nosym_str = NULL; /* * Rejection error message tables. */ const Msg ldd_reject[SGS_REJ_NUM] = { MSG_STR_EMPTY, MSG_LDD_REJ_MACH, /* MSG_INTL(MSG_LDD_REJ_MACH) */ MSG_LDD_REJ_CLASS, /* MSG_INTL(MSG_LDD_REJ_CLASS) */ MSG_LDD_REJ_DATA, /* MSG_INTL(MSG_LDD_REJ_DATA) */ MSG_LDD_REJ_TYPE, /* MSG_INTL(MSG_LDD_REJ_TYPE) */ MSG_LDD_REJ_BADFLAG, /* MSG_INTL(MSG_LDD_REJ_BADFLAG) */ MSG_LDD_REJ_MISFLAG, /* MSG_INTL(MSG_LDD_REJ_MISFLAG) */ MSG_LDD_REJ_VERSION, /* MSG_INTL(MSG_LDD_REJ_VERSION) */ MSG_LDD_REJ_HAL, /* MSG_INTL(MSG_LDD_REJ_HAL) */ MSG_LDD_REJ_US3, /* MSG_INTL(MSG_LDD_REJ_US3) */ MSG_LDD_REJ_STR, /* MSG_INTL(MSG_LDD_REJ_STR) */ MSG_LDD_REJ_UNKFILE, /* MSG_INTL(MSG_LDD_REJ_UNKFILE) */ MSG_LDD_REJ_UNKCAP, /* MSG_INTL(MSG_LDD_REJ_UNKCAP) */ MSG_LDD_REJ_HWCAP_1, /* MSG_INTL(MSG_LDD_REJ_HWCAP_1) */ MSG_LDD_REJ_SFCAP_1, /* MSG_INTL(MSG_LDD_REJ_SFCAP_1) */ MSG_LDD_REJ_MACHCAP, /* MSG_INTL(MSG_LDD_REJ_MACHCAP) */ MSG_LDD_REJ_PLATCAP, /* MSG_INTL(MSG_LDD_REJ_PLATCAP) */ MSG_LDD_REJ_HWCAP_2, /* MSG_INTL(MSG_LDD_REJ_HWCAP_2) */ MSG_LDD_REJ_ARCHIVE, /* MSG_INTL(MSG_LDD_REJ_ARCHIVE) */ MSG_LDD_REJ_KMOD, /* MSG_INTL(MSG_LDD_REJ_KMOD) */ MSG_LDD_REJ_HWCAP_3 /* MSG_INTL(MSG_LDD_REJ_HWCAP_3) */ }; #if SGS_REJ_NUM != (SGS_REJ_HWCAP_3 + 1) #error SGS_REJ_NUM has changed #endif const Msg err_reject[SGS_REJ_NUM] = { MSG_STR_EMPTY, MSG_ERR_REJ_MACH, /* MSG_INTL(MSG_ERR_REJ_MACH) */ MSG_ERR_REJ_CLASS, /* MSG_INTL(MSG_ERR_REJ_CLASS) */ MSG_ERR_REJ_DATA, /* MSG_INTL(MSG_ERR_REJ_DATA) */ MSG_ERR_REJ_TYPE, /* MSG_INTL(MSG_ERR_REJ_TYPE) */ MSG_ERR_REJ_BADFLAG, /* MSG_INTL(MSG_ERR_REJ_BADFLAG) */ MSG_ERR_REJ_MISFLAG, /* MSG_INTL(MSG_ERR_REJ_MISFLAG) */ MSG_ERR_REJ_VERSION, /* MSG_INTL(MSG_ERR_REJ_VERSION) */ MSG_ERR_REJ_HAL, /* MSG_INTL(MSG_ERR_REJ_HAL) */ MSG_ERR_REJ_US3, /* MSG_INTL(MSG_ERR_REJ_US3) */ MSG_ERR_REJ_STR, /* MSG_INTL(MSG_ERR_REJ_STR) */ MSG_ERR_REJ_UNKFILE, /* MSG_INTL(MSG_ERR_REJ_UNKFILE) */ MSG_ERR_REJ_UNKCAP, /* MSG_INTL(MSG_ERR_REJ_UNKCAP) */ MSG_ERR_REJ_HWCAP_1, /* MSG_INTL(MSG_ERR_REJ_HWCAP_1) */ MSG_ERR_REJ_SFCAP_1, /* MSG_INTL(MSG_ERR_REJ_SFCAP_1) */ MSG_ERR_REJ_MACHCAP, /* MSG_INTL(MSG_ERR_REJ_MACHCAP) */ MSG_ERR_REJ_PLATCAP, /* MSG_INTL(MSG_ERR_REJ_PLATCAP) */ MSG_ERR_REJ_HWCAP_2, /* MSG_INTL(MSG_ERR_REJ_HWCAP_2) */ MSG_ERR_REJ_ARCHIVE, /* MSG_INTL(MSG_ERR_REJ_ARCHIVE) */ MSG_ERR_REJ_KMOD, /* MSG_INTL(MSG_ERR_REJ_KMOD) */ MSG_ERR_REJ_HWCAP_3 /* MSG_INTL(MSG_ERR_REJ_HWCAP_3) */ }; #if SGS_REJ_NUM != (SGS_REJ_HWCAP_3 + 1) #error SGS_REJ_NUM has changed #endif const Msg ldd_warn[SGS_REJ_NUM] = { MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_LDD_WARN_UNKCAP, /* MSG_INTL(MSG_LDD_WARN_UNKCAP) */ MSG_LDD_WARN_HWCAP_1, /* MSG_INTL(MSG_LDD_WARN_HWCAP_1) */ MSG_LDD_WARN_SFCAP_1, /* MSG_INTL(MSG_LDD_WARN_SFCAP_1) */ MSG_LDD_WARN_MACHCAP, /* MSG_INTL(MSG_LDD_WARN_MACHCAP) */ MSG_LDD_WARN_PLATCAP, /* MSG_INTL(MSG_LDD_WARN_PLATCAP) */ MSG_LDD_WARN_HWCAP_2, /* MSG_INTL(MSG_LDD_WARN_HWCAP_2) */ MSG_STR_EMPTY, MSG_STR_EMPTY, MSG_LDD_WARN_HWCAP_3 /* MSG_INTL(MSG_LDD_WARN_HWCAP_3) */ }; #if SGS_REJ_NUM != (SGS_REJ_HWCAP_3 + 1) #error SGS_REJ_NUM has changed #endif /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Messaging support. To minimize ld.so.1's overhead, messaging support isn't * enabled until we need to contruct a message - Note that we don't rely on the * application to signify whether messaging is applicable, as many message * conditions (such as relocations) are generated before the application gains * control. * * This code implements a very trimmed down version of the capabilities found * via setlocale(3C), textdomain(3C) and gettext(3C). Dragging in the original * routines from libc/libintl isn't possible as they cause all i18n support to * be included which is far too expensive for ld.so.1. */ #include #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "msg.h" /* * A message object file (as generated by msgfmt(1)) consists of a message * header, followed by a message list, followed by the msgid strings and then * the msgstr strings. None of this is defined in any OSNET available headers * so we have our own local definitions :-( */ typedef struct { int hdr_midlst; /* middle message no. */ int hdr_lstcnt; /* total no. of message in the file */ int hdr_msgidsz; /* size of msgids (in bytes) */ int hdr_msgstrsz; /* size of msgstrs (in bytes) */ int hdr_lstsz; /* size of message list (in bytes) */ } Msghdr; typedef struct { int lst_less; int lst_more; int lst_idoff; int lst_stroff; } Msglst; #define LEAFINDICATOR -99 #define OLD_MSG_STRUCT_SIZE 20 #define NEW_MSG_STRUCT_SIZE (sizeof (Msglst)) /* * Define a local structure for maintaining the domains we care about. */ typedef struct { const char *dom_name; const Msghdr *dom_msghdr; size_t dom_msgsz; } Domain; /* * Perform a binary search of a message file (described by the Msghdr) for a * msgid (string). Given a match return the associated msgstr, otherwise * return the original msgid. */ static const char * msgid_to_msgstr(const Msghdr *msghdr, const char *msgid) { const Msglst *list, *_list; const char *ids, *strs, *_msgid; int off, var; /* * Establish pointers to the message list (we actually start the search * in the middle of this list (hdr->midlst), the msgid strings (ids) * and the msgstr strings (strs). */ list = (const Msglst *)&msghdr[1]; ids = (const char *)&list[msghdr->hdr_lstcnt]; strs = (const char *)&ids[msghdr->hdr_msgidsz]; off = msghdr->hdr_midlst; for (;;) { _list = list + off; _msgid = ids + _list->lst_idoff; if ((var = strcmp(_msgid, msgid)) == 0) return (strs + _list->lst_stroff); if (var < 0) { if ((off = _list->lst_less) == LEAFINDICATOR) return (msgid); } else { if ((off = _list->lst_more) == LEAFINDICATOR) return (msgid); } } /* NOTREACHED */ return (NULL); /* keep gcc happy */ } /* * Open a message file. Following the model of setlocale(3c) we obtain the * message file for the specified locale. Normally this is: * * /usr/lib/locale/`locale'/LC_MESSAGES/`domain'.mo * * The locale was determined during initial environment processing (see * readenv()), which was determined from an LC_ALL, LC_MESSAGES or LANG * setting. If no locale has been specified, or any file processing errors * occur, internationalization is basically disabled. */ static void open_mofile(Domain * dom) { const char *domain = dom->dom_name; char path[PATH_MAX]; int fd; rtld_stat_t status; const Msghdr *msghdr; int count; size_t size_tot, size_old, size_new; dom->dom_msghdr = (Msghdr *)-1; (void) snprintf(path, PATH_MAX, MSG_ORIG(MSG_FMT_MSGFILE), glcs[CI_LCMESSAGES].lc_un.lc_ptr, domain); if ((fd = open(path, O_RDONLY, 0)) == -1) return; if ((rtld_fstat(fd, &status) == -1) || (status.st_size < sizeof (Msghdr))) { (void) close(fd); return; } /* LINTED */ if ((msghdr = (Msghdr *)mmap(0, status.st_size, PROT_READ, MAP_SHARED, fd, 0)) == (Msghdr *)-1) { (void) close(fd); return; } (void) close(fd); /* checks if opened file is msg file */ count = msghdr->hdr_lstcnt; if (((count - 1) / 2) != msghdr->hdr_midlst) { (void) munmap((caddr_t)msghdr, status.st_size); return; } size_tot = msghdr->hdr_lstsz; size_old = OLD_MSG_STRUCT_SIZE * count; size_new = (int)NEW_MSG_STRUCT_SIZE * count; if ((size_tot != size_old) && (size_tot != size_new)) { (void) munmap((caddr_t)msghdr, status.st_size); return; } size_tot = msghdr->hdr_msgidsz + msghdr->hdr_msgstrsz + (int)sizeof (Msghdr); if ((size_tot + size_old < status.st_size) && (size_tot + size_new < status.st_size)) { (void) munmap((caddr_t)msghdr, status.st_size); return; } /* * We have a good message file, initialize the Domain information. */ dom->dom_msghdr = msghdr; dom->dom_msgsz = status.st_size; } /* * Two interfaces are established to support our internationalization. * gettext(3C) calls originate from all link-editor libraries, and thus the * SUNW_OST_SGS domain is assumed. dgettext() calls originate from * dependencies such as libelf and libc. * * Presently we support two domains (libc's strerror() uses SUNW_OST_OSLIB). * If ld.so.1's dependencies evolve to require more then the `domain' array * maintained below can be enlarged or made more dynamic in nature. */ char * dgettext(const char *domain, const char *msgid) { static int domaincnt = 0; static Domain *domains; Domain *_domain; int cnt; if (glcs[CI_LCMESSAGES].lc_un.lc_val == 0) return ((char *)msgid); /* * Determine if we've initialized any domains yet. */ if (domaincnt == 0) { if ((domains = calloc(2, sizeof (Domain))) == NULL) return ((char *)msgid); domains[0].dom_name = MSG_ORIG(MSG_SUNW_OST_SGS); domains[1].dom_name = MSG_ORIG(MSG_SUNW_OST_OSLIB); domaincnt = 2; } /* * If this is a new locale make sure we clean up any old ones. */ if (rtld_flags & RT_FL_NEWLOCALE) { cnt = 0; for (_domain = domains; cnt < domaincnt; _domain++, cnt++) { if (_domain->dom_msghdr == 0) continue; if (_domain->dom_msghdr != (Msghdr *)-1) (void) munmap((caddr_t)_domain->dom_msghdr, _domain->dom_msgsz); _domain->dom_msghdr = 0; } rtld_flags &= ~RT_FL_NEWLOCALE; } /* * Determine which domain we need. */ for (cnt = 0, _domain = domains; cnt < domaincnt; _domain++, cnt++) { if (_domain->dom_name == domain) break; if (strcmp(_domain->dom_name, domain) == 0) break; } if (cnt == domaincnt) return ((char *)msgid); /* * Determine if the domain has been initialized yet. */ if (_domain->dom_msghdr == 0) open_mofile(_domain); if (_domain->dom_msghdr == (Msghdr *)-1) return ((char *)msgid); return ((char *)msgid_to_msgstr(_domain->dom_msghdr, msgid)); } /* * This satisfies any dependencies of code dragged in from libc, as we don't * want libc's gettext implementation in ld.so.1. This routine may not be * referenced, in which case -zignore will discard it. */ char * gettext(const char *msgid) { return ((char *)dgettext(MSG_ORIG(MSG_SUNW_OST_SGS), msgid)); } /* * The sgsmsg.1l use requires the following interface. */ const char * _rtld_msg(Msg mid) { return ((char *)dgettext(MSG_ORIG(MSG_SUNW_OST_SGS), MSG_ORIG(mid))); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 1988 AT&T * All Rights Reserved */ /* * Simplified version of malloc(), calloc() and free(), to be linked with * utilities that use [s]brk() and do not define their own version of the * routines. * The algorithm maps /dev/zero to get extra memory space. * Each call to mmap() creates a page. The pages are linked in a list. * Each page is divided in blocks. There is at least one block in a page. * New memory chunks are allocated on a first-fit basis. * Freed blocks are joined in larger blocks. Free pages are unmapped. */ #include #include #include #include #include #include "_rtld.h" #include "msg.h" struct block { size_t size; /* Space available for user */ struct page *page; /* Backwards reference to page */ int status; struct block *next; void * memstart[1]; }; struct page { size_t size; /* Total page size (incl. header) */ struct page *next; struct block block[1]; }; #define FREE 0 #define BUSY 1 #define HDR_BLOCK (sizeof (struct block) - sizeof (void *)) #define HDR_PAGE (sizeof (struct page) - sizeof (void *)) static struct page *memstart; #if DEBUG /* * When built for debugging, scribble a pattern over newly allocated and * freed memory. */ #define NEWMEM 0 #define FREMEM 1 /* LINTED */ const ulong_t patterns[] = { (ulong_t)0xbaddcafebaddcafeULL, (ulong_t)0xdeadbeefdeadbeefULL }; static void scribble(ulong_t *membgn, int pattern, size_t size) { size_t memsize = size / sizeof (ulong_t); while (memsize--) { if (pattern == FREMEM) ASSERT(*membgn != patterns[pattern]); *membgn++ = patterns[pattern]; } } #endif /* * Defragmentation */ void defrag() { struct page *page; Aliste idx; for (APLIST_TRAVERSE(free_alp, idx, page)) { struct block *block; for (block = page->block; block; block = block->next) { struct block *block2; if (block->status == BUSY) continue; for (block2 = block->next; block2 && block2->status == FREE; block2 = block2->next) { block->next = block2->next; block->size += block2->size + HDR_BLOCK; } } /* * If a page becomes free, leave it, and save the unmapping * expense, as we'll probably come back and reclaim the page * for later malloc activity. * * Free the defrag index. */ aplist_delete(free_alp, &idx); } } static void split(struct block *block, size_t size) { if (block->size > size + sizeof (struct block)) { struct block *newblock; /* LINTED */ newblock = (struct block *) ((char *)block + HDR_BLOCK + size); newblock->next = block->next; block->next = newblock; newblock->status = FREE; newblock->page = block->page; newblock->size = block->size - size - HDR_BLOCK; block->size = size; } } #include /* * Replace both malloc() and lmalloc() (libc's private memory allocator). * They are both private here. */ #pragma weak lmalloc = malloc void * malloc(size_t size) { struct block *block; struct page *page; size = S_DROUND(size); /* * Try to locate necessary space */ for (page = memstart; page; page = page->next) { for (block = page->block; block; block = block->next) { if ((block->status == FREE) && (block->size >= size)) goto found; } } found: /* * Need to allocate a new page */ if (!page) { size_t totsize = size + HDR_PAGE; size_t totpage = S_ROUND(totsize, syspagsz); if ((page = dz_map(0, 0, totpage, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE)) == MAP_FAILED) return (0); page->next = memstart; memstart = page; page->size = totpage; block = page->block; block->next = 0; block->status = FREE; block->size = totpage - HDR_PAGE; block->page = page; } split(block, size); #if DEBUG scribble((ulong_t *)&block->memstart, NEWMEM, block->size); #endif block->status = BUSY; return (&block->memstart); } void * calloc(size_t num, size_t size) { void * mp; size_t total; if (num == 0 || size == 0) { total = 0; } else { total = num * size; /* check for overflow */ if ((total / num) != size) { errno = ENOMEM; return (NULL); } } if ((mp = malloc(total)) == NULL) return (NULL); (void) memset(mp, 0, total); return (mp); } void * realloc(void *ptr, size_t size) { struct block *block; size_t osize; void * newptr; if (ptr == NULL) return (malloc(size)); /* LINTED */ block = (struct block *)((char *)ptr - HDR_BLOCK); size = S_DROUND(size); osize = block->size; /* * Join block with next one if it is free */ if (block->next && block->next->status == FREE) { block->size += block->next->size + HDR_BLOCK; block->next = block->next->next; } if (size <= block->size) { split(block, size); #if DEBUG if (block->size > osize) scribble((ulong_t *)((char *)ptr + osize), NEWMEM, (block->size - osize)); #endif return (ptr); } if ((newptr = malloc(size)) == NULL) return (NULL); (void) memcpy(newptr, ptr, osize); block->status = FREE; /* * Add the free block to the free APlist for later defragmentation. * However, this addition can only be achieved if there is room on the * free APlist. The APlist can't be allowed to grow, as the growth * requires a realloc(), which would recurse back here, resulting in an * infinite loop. If the free APlist is full, defrag() now. This * defragmentation might not be able to collapse any free space, but * the free APlist will be cleared as part of the processing, ensuring * room for the addition. */ if (free_alp && (aplist_nitems(free_alp) >= aplist_arritems(free_alp))) defrag(); (void) aplist_test(&free_alp, block->page, AL_CNT_FREELIST); return (newptr); } /* * Replace both free() and lfree() (libc's private memory allocator). * They are both private here. */ void free(void *ptr) { struct block *block; if (ptr == NULL) return; /* LINTED */ block = (struct block *)((char *)ptr - HDR_BLOCK); block->status = FREE; #if DEBUG scribble((ulong_t *)&block->memstart, FREMEM, block->size); #endif (void) aplist_test(&free_alp, block->page, AL_CNT_FREELIST); } /* ARGSUSED1 */ void lfree(void *ptr, size_t size) { free(ptr); } /* * We can use any memory after ld.so.1's .bss up until the next page boundary * as allocatable memory. */ void addfree(void *ptr, size_t bytes) { struct block *block; struct page *page; if (bytes <= sizeof (struct page)) return; page = ptr; page->next = memstart; memstart = page; page->size = bytes; block = page->block; block->next = 0; block->status = FREE; block->size = bytes - HDR_PAGE; block->page = page; } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. # # Note: This mapfile is used to place r_debug as the # very first data item inside of the run-time linker. # This is required for the ld.so.1 <--> rtld_db bootstraping # for debuggers. # # The issue with this is that different compilers may not agree on the name of # the section this data will be placed in. So we must have a mapfile for # each, though at present GCC is the only compiler. # $mapfile_version 2 LOAD_SEGMENT data { FLAGS = READ WRITE EXECUTE; # .data.rel sections from pics/debugdata.o go to data segment ASSIGN_SECTION dbg_data { IS_NAME = .data.rel; FILE_PATH = pics/debugdata.o; }; # Put .data.rel sections from pics/debugdata.o ahead of any other # .data.rel input sections in the .data.rel output section. IS_ORDER = dbg_data; # Put .data.rel output section at top of data segment OS_ORDER = .data.rel; }; # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved. # # Generic interface definition for usr/src/cmd/sgs/rtld. # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 # # All symbols in ld.so.1 are private as no-one should bind to these directly. # SYMBOL_VERSION SUNWprivate_1.3 { protected: dladdr; # Standard dlopen(3x) family dladdr1; dldump; dlclose; dlerror; dlinfo; dlopen; dlmopen; dlsym; _dladdr; # these should not exist _dladdr1; _dldump; _dlclose; _dlerror; _dlinfo; _dlopen; _dlmopen; _dlsym; _ld_libc; # provides libc initialization _elf_rtbndr; # dbx expects to find these elf_rtbndr; _rt_boot; # Provides basic adb symbol offsets rtld_db_dlactivity; # Required to support librtld_db rtld_db_preinit; rtld_db_postinit; r_debug { ASSERT = { TYPE = OBJECT; $if _x86 && _ELF32 SIZE = 44; $elif _x86 && _ELF64 SIZE = 80; $elif _sparc && _ELF32 SIZE = 44; $elif _sparc && _ELF64 SIZE = 80; $else $error Unknown platform $endif }; }; elf_plt_write; is_so_loaded; lml_main { ASSERT = { TYPE = OBJECT; $if _ELF32 SIZE = addrsize[41]; $elif _ELF64 SIZE = addrsize[38]; $else $error Unknown platform $endif }; }; lookup_sym; alist_append; # librtld support ld_entry_cnt { ASSERT = { TYPE = OBJECT; SIZE = addrsize; }; }; # Diagnostic support dbg_desc { ASSERT = { TYPE = OBJECT; SIZE = addrsize; }; }; dbg_print; eprintf; # Error message printing veprintf; dgettext; # Messaging support strerror; calloc; # Memory management (mapmalloc). free; malloc; realloc; # PLT tracing getenv() support. _environ { ASSERT = { TYPE = OBJECT; SIZE = addrsize; }; }; environ { ASSERT = { BINDING = WEAK; ALIAS = _environ; }; }; memcpy; # Some routines are useful for support snprintf; # libraries such as liblddbg. sprintf; # Note that some of these functions strcat; # (like sprintf) may have reduced strcmp; # functionality over libc, as a simpler strcpy; # implementation is provided in ld.so.1. strlen; strrchr; strtok_r; ___errno; qsort; dl_iterate_phdr; $if _x86 && _ELF64 # amd64 ABI exception unwinding _dlamd64getunwind; dlamd64getunwind; $endif # doXX_reloc_rtld and relocXX_table are required to # support librtld.so. $if _ELF32 do32_reloc_rtld; reloc32_table { ASSERT = { TYPE = OBJECT; $if _x86 SIZE = addrsize[132]; $elif _sparc SIZE = addrsize[264]; $else $error Unknown architecture $endif }; }; $elif _ELF64 do64_reloc_rtld; reloc64_table { ASSERT = { TYPE = OBJECT; $if _x86 SIZE = addrsize[86]; $elif _sparc SIZE = addrsize[176]; $else $error Unknown architecture $endif }; }; $else $error unknown ELFCLASS $endif local: *; }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Object file dependent support for ELF objects. */ #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" #include "_inline_gen.h" #include "msg.h" /* * For backward compatibility copy relocation processing, it can be necessary to * determine if a copy destination is also the recipient of a move record. For * these instances, the move record addresses are retained for is_move_data(). */ static APlist *alp = NULL; /* * Warning message for bad move target. */ void elf_move_bad(Lm_list *lml, Rt_map *lmp, Sym *sym, ulong_t num, Addr addr) { const char *name; int trace; trace = (lml->lm_flags & LML_FLG_TRC_ENABLE) && (((rtld_flags & RT_FL_SILENCERR) == 0) || (lml->lm_flags & (LML_FLG_TRC_VERBOSE | LML_FLG_TRC_WARN))); if ((trace == 0) && (DBG_ENABLED == 0)) return; if (ELF_ST_BIND(sym->st_info) != STB_LOCAL) name = (const char *)(STRTAB(lmp) + sym->st_name); else name = MSG_INTL(MSG_STR_UNKNOWN); if (trace) (void) printf(MSG_INTL(MSG_LDD_MOVE_ERR), EC_XWORD(num), name, EC_ADDR(addr)); else DBG_CALL(Dbg_move_bad(lml, num, name, addr)); } /* * Move data. Apply sparse initialization to data in zeroed bss. */ int move_data(Rt_map *lmp, APlist **textrel) { Lm_list *lml = LIST(lmp); Move *mv = MOVETAB(lmp); ulong_t num, mvnum = MOVESZ(lmp) / MOVEENT(lmp); int moves; /* * If these records are against the executable, and the executable was * built prior to Solaris 8, keep track of the move record symbol. See * comment in analyze.c:lookup_sym_interpose() in regards Solaris 8 * objects and DT_FLAGS. */ moves = (lmp == lml->lm_head) && ((FLAGS1(lmp) & FL1_RT_DTFLAGS) == 0); DBG_CALL(Dbg_move_data(lmp)); for (num = 0; num < mvnum; num++, mv++) { mmapobj_result_t *mpp; Addr addr, taddr; Half rep, repno, stride; Sym *sym; if ((sym = (Sym *)SYMTAB(lmp) + ELF_M_SYM(mv->m_info)) == 0) continue; stride = mv->m_stride + 1; addr = sym->st_value; /* * Determine the move data target, and verify the address is * writable. */ if ((FLAGS(lmp) & FLG_RT_FIXED) == 0) addr += ADDR(lmp); taddr = addr + mv->m_poffset; if ((mpp = find_segment((caddr_t)taddr, lmp)) == NULL) { elf_move_bad(lml, lmp, sym, num, taddr); continue; } if (((mpp->mr_prot & PROT_WRITE) == 0) && ((set_prot(lmp, mpp, 1) == 0) || (aplist_append(textrel, mpp, AL_CNT_TEXTREL) == NULL))) return (0); DBG_CALL(Dbg_move_entry2(lml, mv, sym->st_name, (const char *)(sym->st_name + STRTAB(lmp)))); for (rep = 0, repno = 0; rep < mv->m_repeat; rep++) { DBG_CALL(Dbg_move_expand(lml, mv, taddr)); switch (ELF_M_SIZE(mv->m_info)) { case 1: *((char *)taddr) = (char)mv->m_value; taddr += stride; repno++; break; case 2: /* LINTED */ *((Half *)taddr) = (Half)mv->m_value; taddr += 2 * stride; repno++; break; case 4: /* LINTED */ *((Word *)taddr) = (Word)mv->m_value; taddr += 4 * stride; repno++; break; case 8: /* LINTED */ *((unsigned long long *)taddr) = mv->m_value; taddr += 8 * stride; repno++; break; default: eprintf(lml, ERR_NONE, MSG_INTL(MSG_MOVE_ERR1)); break; } } /* * If any move records have been applied to this symbol, retain * the symbol address if required for backward compatibility * copy relocation processing. */ if (moves && repno && (aplist_append(&alp, (void *)addr, AL_CNT_MOVES) == NULL)) return (0); } /* * Binaries built in the early 1990's prior to Solaris 8, using the ild * incremental linker are known to have zero filled move sections * (presumably place holders for new, incoming move sections). If no * move records have been processed, remove the move identifier to * optimize the amount of backward compatibility copy relocation * processing that is needed. */ if (moves && (alp == NULL)) FLAGS(lmp) &= ~FLG_RT_MOVE; return (1); } /* * Determine whether an address is the recipient of a move record. * Returns 1 if the address matches a move symbol, 0 otherwise. */ int is_move_data(caddr_t addr) { caddr_t maddr; Aliste idx; for (APLIST_TRAVERSE(alp, idx, maddr)) { if (addr == maddr) return (1); } return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Object file dependent suport for ELF objects. */ #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" static Rt_map *olmp = NULL; static Alist *mpalp = NULL; static Ehdr dehdr = { { ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, M_CLASS, M_DATA }, 0, M_MACH, EV_CURRENT }; /* * Process a relocatable object. The static object link map pointer is used as * a flag to determine whether a concatenation is already in progress (ie. an * LD_PRELOAD may specify a list of objects). The link map returned simply * specifies an `object' flag which the caller can interpret and thus call * elf_obj_fini() to complete the concatenation. */ static Rt_map * elf_obj_init(Lm_list *lml, Aliste lmco, const char *oname) { Ofl_desc *ofl; const char *name; /* * Allocate the name of this object, as the original name may be * associated with a data buffer that can be reused to load the * dependencies needed to processes this object. */ if ((name = stravl_insert(oname, 0, 0, 0)) == NULL) return (NULL); /* * Initialize an output file descriptor and the entrance criteria. */ if ((ofl = calloc(1, sizeof (Ofl_desc))) == NULL) return (NULL); ofl->ofl_dehdr = &dehdr; ofl->ofl_flags = (FLG_OF_DYNAMIC | FLG_OF_SHAROBJ | FLG_OF_STRIP); ofl->ofl_flags1 = (FLG_OF1_RELDYN | FLG_OF1_TEXTOFF | FLG_OF1_MEMORY); ofl->ofl_lml = lml; /* * As ent_setup() will effectively lazy load the necessary support * libraries, make sure ld.so.1 is initialized for plt relocations. * Then configure libld.so to process objects of the desired target * type (this is the first call to libld.so, which will effectively * lazyload it). */ if ((elf_rtld_load() == 0) || (ld_init_target(lml, M_MACH) != 0)) { free(ofl); return (NULL); } /* * Obtain a generic set of entrance criteria, and generate a link map * place holder and use the ELFPRV() element to maintain the output * file descriptor. */ if ((ld_ent_setup(ofl, syspagsz) == S_ERROR) || ((olmp = calloc(1, sizeof (*olmp))) == NULL)) { free(ofl); return (NULL); } DBG_CALL(Dbg_file_elf(lml, name, 0, 0, lml->lm_lmidstr, lmco)); FLAGS(olmp) |= FLG_RT_OBJECT; ELFPRV(olmp) = (void *)ofl; /* * Initialize string tables. */ if (ld_init_strings(ofl) == S_ERROR) { free(ofl); free(olmp); olmp = NULL; return (NULL); } /* * Assign the output file name to be the initial object that got us * here. This name is being used for diagnostic purposes only as we * don't actually generate an output file unless debugging is enabled. */ ofl->ofl_name = name; NAME(olmp) = (char *)name; LIST(olmp) = lml; lm_append(lml, lmco, olmp); return (olmp); } /* * Define a structure to retain the mapping information of the original * relocatable object. Typically, mmapobj(2) maps a relocatable object into one * mapping. However, if padding has been enabled by a debugger, then additional * padding segments may have been added. elf_obj_file() needs to know which * segment is the relocatable objects data, and retain the initial segment and * the associated segment number for unmapping this object later (see * elf_obj_fini()). Note, even if padding is enabled, the final shared object * that is created by the link-editor for this relocatable object will have no * associated padding, as ld(1) has no capabilities to provide padding. */ typedef struct { mmapobj_result_t *md_mpp; uint_t md_mnum; } Mmap_desc; /* * Initial processing of a relocatable object. If this is the first object * encountered we need to initialize some structures, then simply call the * link-edit functionality to provide the initial processing of the file (ie. * reads in sections and symbols, performs symbol resolution if more that one * object file have been specified, and assigns input sections to output * sections). */ Rt_map * elf_obj_file(Lm_list *lml, Aliste lmco, Rt_map *clmp, const char *name, mmapobj_result_t *hmpp, mmapobj_result_t *mpp, uint_t mnum) { Rej_desc rej; Mmap_desc md; /* * If this is the first relocatable object (LD_PRELOAD could provide a * list of objects), initialize an input file descriptor and a link map. */ if ((olmp == NULL) && ((olmp = elf_obj_init(lml, lmco, name)) == NULL)) return (NULL); DBG_CALL(Dbg_util_nl(lml, DBG_NL_STD)); /* * Keep track of the input image, as this must be free'd after all ELF * processing is completed. */ md.md_mpp = mpp; md.md_mnum = mnum; if (alist_append(&mpalp, &md, sizeof (Mmap_desc), AL_CNT_MPOBJS) == NULL) { remove_so(lml, olmp, clmp); return (NULL); } /* * Pass the object mapping to the link-editor to commence processing the * file. */ if (ld_process_mem(name, name, hmpp->mr_addr, hmpp->mr_msize, (Ofl_desc *)ELFPRV(olmp), &rej) == (Ifl_desc *)S_ERROR) { remove_so(lml, olmp, clmp); return (NULL); } return (olmp); } /* * Ensure any platform or machine capability names are valid. */ inline static int check_plat_names(Syscapset *scapset, Alist *caps, Rej_desc *rej) { Capstr *capstr; Aliste idx; for (ALIST_TRAVERSE(caps, idx, capstr)) { if (platcap_check(scapset, capstr->cs_str, rej) == 1) return (1); } return (0); } inline static int check_mach_names(Syscapset *scapset, Alist *caps, Rej_desc *rej) { Capstr *capstr; Aliste idx; for (ALIST_TRAVERSE(caps, idx, capstr)) { if (machcap_check(scapset, capstr->cs_str, rej) == 1) return (1); } return (0); } /* * Finish relocatable object processing. Having already initially processed one * or more objects, complete the generation of a shared object image by calling * the appropriate link-edit functionality (refer to sgs/ld/common/main.c). */ Rt_map * elf_obj_fini(Lm_list *lml, Rt_map *lmp, Rt_map *clmp, int *in_nfavl) { Ofl_desc *ofl = (Ofl_desc *)ELFPRV(lmp); Rt_map *nlmp, *tlmp; Ehdr *ehdr; Phdr *phdr; mmapobj_result_t *mpp, *hmpp; uint_t phnum; int mnum; Lm_cntl *lmc; Aliste idx1; Mmap_desc *mdp; Fdesc fd = { 0 }; Grp_hdl *ghp; Rej_desc rej = { 0 }; Syscapset *scapset; elfcap_mask_t omsk; Alist *oalp; DBG_CALL(Dbg_util_nl(lml, DBG_NL_STD)); if (ld_reloc_init(ofl) == S_ERROR) return (NULL); if (ld_sym_validate(ofl) == S_ERROR) return (NULL); /* * At this point, all input section processing is complete. If any * capabilities have been established, ensure that they are appropriate * for this system. */ if (pnavl_recorded(&capavl, ofl->ofl_name, 0, NULL)) scapset = alt_scapset; else scapset = org_scapset; if ((((omsk = ofl->ofl_ocapset.oc_hw_1.cm_val) != 0) && (hwcap1_check(scapset, omsk, &rej) == 0)) || (((omsk = ofl->ofl_ocapset.oc_sf_1.cm_val) != 0) && (sfcap1_check(scapset, omsk, &rej) == 0)) || (((omsk = ofl->ofl_ocapset.oc_hw_2.cm_val) != 0) && (hwcap2_check(scapset, omsk, &rej) == 0)) || (((oalp = ofl->ofl_ocapset.oc_plat.cl_val) != NULL) && (check_plat_names(scapset, oalp, &rej) == 0)) || (((oalp = ofl->ofl_ocapset.oc_mach.cl_val) != NULL) && (check_mach_names(scapset, oalp, &rej) == 0))) { if ((lml_main.lm_flags & LML_FLG_TRC_LDDSTUB) && lmp && (FLAGS1(lmp) & FL1_RT_LDDSTUB) && (NEXT(lmp) == NULL)) { /* LINTED */ (void) printf(MSG_INTL(ldd_reject[rej.rej_type]), ofl->ofl_name, rej.rej_str); } return (NULL); } /* * Finish creating the output file. */ if (ld_make_sections(ofl) == S_ERROR) return (NULL); if (ld_create_outfile(ofl) == S_ERROR) return (NULL); if (ld_update_outfile(ofl) == S_ERROR) return (NULL); if (ld_reloc_process(ofl) == S_ERROR) return (NULL); /* * At this point we have a memory image of the shared object. The link * editor would normally simply write this to the required output file. * If we're debugging generate a standard temporary output file. */ DBG_CALL(Dbg_file_output(ofl)); /* * Allocate a mapping array to retain mapped segment information. */ ehdr = ofl->ofl_nehdr; phdr = ofl->ofl_phdr; if ((mpp = hmpp = calloc(ehdr->e_phnum, sizeof (mmapobj_result_t))) == NULL) return (NULL); for (mnum = 0, phnum = 0; phnum < ehdr->e_phnum; phnum++) { if (phdr[phnum].p_type != PT_LOAD) continue; mpp[mnum].mr_addr = (caddr_t)((uintptr_t)phdr[phnum].p_vaddr + (uintptr_t)ehdr); mpp[mnum].mr_msize = phdr[phnum].p_memsz; mpp[mnum].mr_fsize = phdr[phnum].p_filesz; mpp[mnum].mr_prot = (PROT_READ | PROT_WRITE | PROT_EXEC); mnum++; } /* * Generate a new link map representing the memory image created. */ fd.fd_nname = ofl->ofl_name; if ((nlmp = elf_new_lmp(lml, CNTL(olmp), &fd, (Addr)hmpp->mr_addr, ofl->ofl_size, NULL, clmp, in_nfavl)) == NULL) return (NULL); MMAPS(nlmp) = hmpp; MMAPCNT(nlmp) = mnum; PADSTART(nlmp) = (ulong_t)hmpp->mr_addr; PADIMLEN(nlmp) = mpp->mr_addr + mpp->mr_msize - hmpp->mr_addr; /* * Replace the original (temporary) link map with the new link map. */ /* LINTED */ lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, CNTL(nlmp)); lml->lm_obj--; if ((tlmp = PREV_RT_MAP(nlmp)) == olmp) tlmp = nlmp; if (PREV(olmp)) { NEXT(PREV_RT_MAP(olmp)) = (Link_map *)nlmp; PREV(nlmp) = PREV(olmp); } else { PREV(nlmp) = NULL; lmc->lc_head = nlmp; if (CNTL(nlmp) == ALIST_OFF_DATA) lml->lm_head = nlmp; } if (NEXT(olmp) != (Link_map *)nlmp) { NEXT(nlmp) = NEXT(olmp); PREV(NEXT_RT_MAP(olmp)) = (Link_map *)nlmp; } NEXT(tlmp) = NULL; lmc->lc_tail = tlmp; if (CNTL(nlmp) == ALIST_OFF_DATA) lml->lm_tail = tlmp; HANDLES(nlmp) = HANDLES(olmp); GROUPS(nlmp) = GROUPS(olmp); STDEV(nlmp) = STDEV(olmp); STINO(nlmp) = STINO(olmp); FLAGS(nlmp) |= ((FLAGS(olmp) & ~FLG_RT_OBJECT) | FLG_RT_IMGALLOC); FLAGS1(nlmp) |= FLAGS1(olmp); MODE(nlmp) |= MODE(olmp); NAME(nlmp) = NAME(olmp); /* * Reassign any original handles to the new link-map. */ for (APLIST_TRAVERSE(HANDLES(nlmp), idx1, ghp)) { Grp_desc *gdp; Aliste idx2; ghp->gh_ownlmp = nlmp; for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { if (gdp->gd_depend == olmp) { gdp->gd_depend = nlmp; break; } } } ld_ofl_cleanup(ofl); free(ELFPRV(olmp)); free(olmp); olmp = 0; /* * Unmap the original relocatable object. */ for (ALIST_TRAVERSE(mpalp, idx1, mdp)) { unmap_obj(mdp->md_mpp, mdp->md_mnum); free(mdp->md_mpp); } free(mpalp); mpalp = NULL; /* * Now that we've allocated our permanent link map structure, expand the * PATHNAME() and insert this path name into the FullPathNode AVL tree. */ (void) fullpath(nlmp, 0); if (fpavl_insert(lml, nlmp, PATHNAME(nlmp), 0) == 0) return (NULL); /* * If we're being audited tell the audit library of the file we've just * opened. */ if ((lml->lm_tflags | AFLAGS(nlmp)) & LML_TFLG_AUD_MASK) { if (audit_objopen(nlmp, nlmp) == 0) return (NULL); } return (nlmp); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright (c) 1988 AT&T * All Rights Reserved */ /* * Copyright (c) 2012, Joyent, Inc. All rights reserved. */ /* * PATH setup and search directory functions. */ #include #include #include #include #include #include #include #include "_rtld.h" #include "msg.h" /* * Default and secure dependency search path initialization. */ void set_dirs(Alist **alpp, Spath_defn *sdp, uint_t flags) { while (sdp->sd_name) { Pdesc *pdp; if ((pdp = alist_append(alpp, NULL, sizeof (Pdesc), AL_CNT_SPATH)) == NULL) return; pdp->pd_pname = (char *)sdp->sd_name; pdp->pd_plen = sdp->sd_len; pdp->pd_flags = flags; sdp++; } } static void print_default_dirs(Lm_list *lml, Alist *alp, int search) { uint_t flags = 0; int num = 0; Aliste idx; Pdesc *pdp; if (search) (void) printf(MSG_INTL(MSG_LDD_PTH_BGNDFL)); for (ALIST_TRAVERSE(alp, idx, pdp)) { flags = pdp->pd_flags; if (search) { const char *fmt; if (num++) fmt = MSG_ORIG(MSG_LDD_FMT_PATHN); else fmt = MSG_ORIG(MSG_LDD_FMT_PATH1); (void) printf(fmt, pdp->pd_pname); } else DBG_CALL(Dbg_libs_path(lml, pdp->pd_pname, pdp->pd_flags, config->c_name)); } if (search) { if (flags & LA_SER_CONFIG) (void) printf(MSG_INTL(MSG_LDD_PTH_ENDDFLC), config->c_name); else (void) printf(MSG_INTL(MSG_LDD_PTH_ENDDFL)); } } /* * Given a search rule type, return a list of directories to search according * to the specified rule. */ static Alist ** get_dir_list(uchar_t rules, Rt_map *lmp, uint_t flags) { Alist **dalpp = NULL; Lm_list *lml = LIST(lmp); int search; /* * Determine whether ldd -s is in effect - ignore when we're searching * for audit libraries as these will be added to their own link-map. */ if ((lml->lm_flags & LML_FLG_TRC_SEARCH) && ((FLAGS1(lmp) & FL1_RT_LDDSTUB) == 0) && ((flags & FLG_RT_AUDIT) == 0)) search = 1; else search = 0; switch (rules) { case RPLENV: /* * Initialize the replaceable environment variable * (LD_LIBRARY_PATH) search path list. Note, we always call * Dbg_libs_path() so that every library lookup diagnostic can * be preceded with the appropriate search path information. */ if (rpl_libpath) { uint_t mode = (LA_SER_LIBPATH | PD_FLG_UNIQUE); /* * Note, this path may have originated from the users * environment or from a configuration file. */ if (env_info & ENV_INF_PATHCFG) mode |= LA_SER_CONFIG; DBG_CALL(Dbg_libs_path(lml, rpl_libpath, mode, config->c_name)); /* * For ldd(1) -s, indicate the search paths that'll * be used. If this is a secure application then some * search paths may be ignored, therefore reset the * rpl_libdirs pointer each time so that the * diagnostics related to these unsecure directories * will be output for each image loaded. */ if (search) { const char *fmt; if (env_info & ENV_INF_PATHCFG) fmt = MSG_INTL(MSG_LDD_PTH_LIBPATHC); else fmt = MSG_INTL(MSG_LDD_PTH_LIBPATH); (void) printf(fmt, rpl_libpath, config->c_name); } if (rpl_libdirs && (rtld_flags & RT_FL_SECURE) && (search || DBG_ENABLED)) remove_alist(&rpl_libdirs, 1); if (rpl_libdirs == NULL) { /* * If this is a secure application we need to * be selective over what directories we use. */ (void) expand_paths(lmp, rpl_libpath, &rpl_libdirs, AL_CNT_SEARCH, mode, PD_TKN_CAP); } dalpp = &rpl_libdirs; } break; case PRMENV: /* * Initialize the permanent (LD_LIBRARY_PATH) search path list. * This can only originate from a configuration file. To be * consistent with the debugging display of DEFENV (above), * always call Dbg_libs_path(). */ if (prm_libpath) { uint_t mode = (LA_SER_LIBPATH | LA_SER_CONFIG | PD_FLG_UNIQUE); DBG_CALL(Dbg_libs_path(lml, prm_libpath, mode, config->c_name)); /* * For ldd(1) -s, indicate the search paths that'll * be used. If this is a secure application then some * search paths may be ignored, therefore reset the * prm_libdirs pointer each time so that the * diagnostics related to these unsecure directories * will be output for each image loaded. */ if (search) (void) printf(MSG_INTL(MSG_LDD_PTH_LIBPATHC), prm_libpath, config->c_name); if (prm_libdirs && (rtld_flags & RT_FL_SECURE) && (search || DBG_ENABLED)) remove_alist(&prm_libdirs, 1); if (prm_libdirs == NULL) { /* * If this is a secure application we need to * be selective over what directories we use. */ (void) expand_paths(lmp, prm_libpath, &prm_libdirs, AL_CNT_SEARCH, mode, PD_TKN_CAP); } dalpp = &prm_libdirs; } break; case RUNPATH: /* * Initialize the runpath search path list. To be consistent * with the debugging display of DEFENV (above), always call * Dbg_libs_path(). */ if (RPATH(lmp)) { DBG_CALL(Dbg_libs_path(lml, RPATH(lmp), LA_SER_RUNPATH, NAME(lmp))); /* * For ldd(1) -s, indicate the search paths that'll * be used. If this is a secure application then some * search paths may be ignored, therefore reset the * runlist pointer each time so that the diagnostics * related to these unsecure directories will be * output for each image loaded. */ if (search) (void) printf(MSG_INTL(MSG_LDD_PTH_RUNPATH), RPATH(lmp), NAME(lmp)); if (RLIST(lmp) && (rtld_flags & RT_FL_SECURE) && (search || DBG_ENABLED)) remove_alist(&RLIST(lmp), 1); if (RLIST(lmp) == NULL) { /* * If this is a secure application we need to * be selective over what directories we use. */ (void) expand_paths(lmp, RPATH(lmp), &RLIST(lmp), AL_CNT_SEARCH, LA_SER_RUNPATH, PD_TKN_CAP); } dalpp = &RLIST(lmp); } break; case DEFAULT: /* * If we have been requested to load an audit library through a * DT_DEPAUDIT entry, then we treat this the same way that we * handle a library that has been specified via a DT_NEEDED * entry -- we check the default directories and not the * secure directories. */ if ((FLAGS1(lmp) & FL1_RT_NODEFLIB) == 0) { if ((rtld_flags & RT_FL_SECURE) && ((flags & FLG_RT_PRELOAD) || ((flags & FLG_RT_AUDIT) && !(FLAGS1(lmp) & FL1_RT_DEPAUD)))) dalpp = LM_SECURE_DIRS(lmp)(); else dalpp = LM_DEFAULT_DIRS(lmp)(); } /* * For ldd(1) -s, indicate the default paths that'll be used. */ if (dalpp && (search || DBG_ENABLED)) print_default_dirs(lml, *dalpp, search); break; default: break; } return (dalpp); } /* * Get the next directory in the search rules path. The search path "cookie" * provided by the caller (sdp) maintains the state of a search in progress. * * Typically, a search consists of a series of rules that govern the order of * a search (ie. LD_LIBRARY_PATH, followed by RPATHS, followed by defaults). * Each rule can establish a corresponding series of path names, which are * maintained as an Alist. The index within this Alist determines the present * search directory. */ Pdesc * get_next_dir(Spath_desc *sdp, Rt_map *lmp, uint_t flags) { /* * Make sure there are still rules to process. */ while (*sdp->sp_rule) { Alist *alp; /* * If an Alist for this rule already exists, use if, otherwise * obtain an Alist for this rule. Providing the Alist has * content, and the present Alist index is less than the number * of Alist members, return the associated path name descriptor. */ if ((sdp->sp_dalpp || ((sdp->sp_dalpp = get_dir_list(*sdp->sp_rule, lmp, flags)) != NULL)) && ((alp = *sdp->sp_dalpp) != NULL) && (alist_nitems(alp) > sdp->sp_idx)) { return (alist_item(alp, sdp->sp_idx++)); } /* * If no Alist for this rule exists, or if this is the last * element of this Alist, reset the Alist pointer and index, * and prepare for the next rule. */ sdp->sp_rule++; sdp->sp_dalpp = NULL; sdp->sp_idx = 0; } /* * All rules and search paths have been exhausted. */ return (NULL); } /* * Process a directory (runpath) or filename (needed or filter) string looking * for tokens to expand. Allocate a new buffer for the string. */ uint_t expand(char **name, size_t *len, char **list, uint_t orig, uint_t omit, Rt_map *lmp) { char _name[PATH_MAX]; char *token = NULL, *oname, *ename, *optr, *_optr, *nptr, *_list; size_t olen = 0, nlen = 0, _len; int isaflag = 0; uint_t flags = 0; Lm_list *lml = LIST(lmp); optr = _optr = oname = ename = *name; ename += *len; nptr = _name; while ((olen < *len) && (nlen < PATH_MAX)) { uint_t _flags; if ((*optr != '$') || ((olen - *len) == 1)) { /* * When expanding paths while a configuration file * exists that contains directory information, determine * whether the path contains "./". If so, we'll resolve * the path later to remove these relative entries. */ if ((rtld_flags & RT_FL_DIRCFG) && (orig & LA_SER_MASK) && (*optr == '/') && (optr != oname) && (*(optr - 1) == '.')) flags |= TKN_DOTSLASH; olen++, optr++; continue; } /* * Copy any string we've presently passed over to the new * buffer. */ if ((_len = (optr - _optr)) != 0) { if ((nlen += _len) < PATH_MAX) { (void) strncpy(nptr, _optr, _len); nptr = nptr + _len; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } /* * Skip the token delimiter and determine if a reserved token * match is found. */ olen++, optr++; _flags = 0; token = 0; if (strncmp(optr, MSG_ORIG(MSG_TKN_ORIGIN), MSG_TKN_ORIGIN_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_ORIGIN); /* * $ORIGIN expansion is required. Determine this * objects basename. Expansion of $ORIGIN is allowed * for secure applications but must be checked by the * caller to insure the expanded path matches a * registered secure name. */ if (((omit & PD_TKN_ORIGIN) == 0) && (((_len = DIRSZ(lmp)) != 0) || ((_len = fullpath(lmp, 0)) != 0))) { if ((nlen += _len) < PATH_MAX) { (void) strncpy(nptr, ORIGNAME(lmp), _len); nptr = nptr +_len; olen += MSG_TKN_ORIGIN_SIZE; optr += MSG_TKN_ORIGIN_SIZE; _flags |= PD_TKN_ORIGIN; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_PLATFORM), MSG_TKN_PLATFORM_SIZE) == 0) { Syscapset *scapset; if (FLAGS1(lmp) & FL1_RT_ALTCAP) scapset = alt_scapset; else scapset = org_scapset; token = (char *)MSG_ORIG(MSG_TKN_PLATFORM); /* * $PLATFORM expansion required. */ if (((omit & PD_TKN_PLATFORM) == 0) && ((scapset->sc_plat == NULL) && (scapset->sc_platsz == 0))) platform_name(scapset); if (((omit & PD_TKN_PLATFORM) == 0) && scapset->sc_plat) { nlen += scapset->sc_platsz; if (nlen < PATH_MAX) { (void) strncpy(nptr, scapset->sc_plat, scapset->sc_platsz); nptr = nptr + scapset->sc_platsz; olen += MSG_TKN_PLATFORM_SIZE; optr += MSG_TKN_PLATFORM_SIZE; _flags |= PD_TKN_PLATFORM; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_MACHINE), MSG_TKN_MACHINE_SIZE) == 0) { Syscapset *scapset; if (FLAGS1(lmp) & FL1_RT_ALTCAP) scapset = alt_scapset; else scapset = org_scapset; token = (char *)MSG_ORIG(MSG_TKN_MACHINE); /* * $MACHINE expansion required. */ if (((omit & PD_TKN_MACHINE) == 0) && ((scapset->sc_mach == NULL) && (scapset->sc_machsz == 0))) machine_name(scapset); if (((omit & PD_TKN_MACHINE) == 0) && scapset->sc_mach) { nlen += scapset->sc_machsz; if (nlen < PATH_MAX) { (void) strncpy(nptr, scapset->sc_mach, scapset->sc_machsz); nptr = nptr + scapset->sc_machsz; olen += MSG_TKN_MACHINE_SIZE; optr += MSG_TKN_MACHINE_SIZE; _flags |= PD_TKN_MACHINE; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_OSNAME), MSG_TKN_OSNAME_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_OSNAME); /* * $OSNAME expansion required. This is established * from the sysname[] returned by uname(2). */ if (((omit & PD_TKN_OSNAME) == 0) && (uts == NULL)) uts = conv_uts(); if (((omit & PD_TKN_OSNAME) == 0) && (uts && uts->uts_osnamesz)) { if ((nlen += uts->uts_osnamesz) < PATH_MAX) { (void) strncpy(nptr, uts->uts_osname, uts->uts_osnamesz); nptr = nptr + uts->uts_osnamesz; olen += MSG_TKN_OSNAME_SIZE; optr += MSG_TKN_OSNAME_SIZE; _flags |= PD_TKN_OSNAME; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_OSREL), MSG_TKN_OSREL_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_OSREL); /* * $OSREL expansion required. This is established * from the release[] returned by uname(2). */ if (((omit & PD_TKN_OSREL) == 0) && (uts == 0)) uts = conv_uts(); if (((omit & PD_TKN_OSREL) == 0) && (uts && uts->uts_osrelsz)) { if ((nlen += uts->uts_osrelsz) < PATH_MAX) { (void) strncpy(nptr, uts->uts_osrel, uts->uts_osrelsz); nptr = nptr + uts->uts_osrelsz; olen += MSG_TKN_OSREL_SIZE; optr += MSG_TKN_OSREL_SIZE; _flags |= PD_TKN_OSREL; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if ((strncmp(optr, MSG_ORIG(MSG_TKN_ISALIST), MSG_TKN_ISALIST_SIZE) == 0)) { int ok; token = (char *)MSG_ORIG(MSG_TKN_ISALIST); /* * $ISALIST expansion required. When accompanied with * a list pointer, this routine updates that pointer * with the new list of potential candidates. Without * this list pointer, only the first expansion is * provided. NOTE, that two $ISLIST expansions within * the same path aren't supported. */ if ((omit & PD_TKN_ISALIST) || isaflag++) ok = 0; else ok = 1; if (ok && (isa == NULL)) isa = conv_isalist(); if (ok && isa && isa->isa_listsz) { size_t no, mlen, tlen, hlen = olen - 1; char *lptr; Isa_opt *opt = isa->isa_opt; if ((nlen += opt->isa_namesz) < PATH_MAX) { (void) strncpy(nptr, opt->isa_name, opt->isa_namesz); nptr = nptr + opt->isa_namesz; olen += MSG_TKN_ISALIST_SIZE; optr += MSG_TKN_ISALIST_SIZE; _flags |= PD_TKN_ISALIST; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } if (list) { tlen = *len - olen; mlen = ((hlen + tlen) * (isa->isa_optno - 1)) + isa->isa_listsz - opt->isa_namesz + strlen(*list); if ((_list = lptr = malloc(mlen)) == NULL) return (0); for (no = 1, opt++; no < isa->isa_optno; no++, opt++) { (void) strncpy(lptr, *name, hlen); lptr = lptr + hlen; (void) strncpy(lptr, opt->isa_name, opt->isa_namesz); lptr = lptr + opt->isa_namesz; (void) strncpy(lptr, optr, tlen); lptr = lptr + tlen; *lptr++ = ':'; } if (**list) (void) strcpy(lptr, *list); else *--lptr = '\0'; } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_CAPABILITY), MSG_TKN_CAPABILITY_SIZE) == 0) { char *bptr = nptr - 1; char *eptr = optr + MSG_TKN_CAPABILITY_SIZE; token = (char *)MSG_ORIG(MSG_TKN_CAPABILITY); /* * $CAPABILITY expansion required. Expansion is only * allowed for non-simple path names (must contain a * '/'), with the token itself being the last element * of the path. Therefore, all we need do is test the * existence of the string "/$CAPABILITY\0". */ if (((omit & PD_TKN_CAP) == 0) && ((bptr > _name) && (*bptr == '/') && ((*eptr == '\0') || (*eptr == ':')))) { /* * Decrement the present pointer so that the * directories trailing "/" gets nuked later. */ nptr--, nlen--; olen += MSG_TKN_CAPABILITY_SIZE; optr += MSG_TKN_CAPABILITY_SIZE; _flags |= PD_TKN_CAP; } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_HWCAP), MSG_TKN_HWCAP_SIZE) == 0) { char *bptr = nptr - 1; char *eptr = optr + MSG_TKN_HWCAP_SIZE; token = (char *)MSG_ORIG(MSG_TKN_HWCAP); /* * $HWCAP expansion required. This token has been * superseeded by $CAPABILITY. For compatibility with * older environments, only expand this token when hard- * ware capability information is available. This * expansion is only allowed for non-simple path names * (must contain a '/'), with the token itself being the * last element of the path. Therefore, all we need do * is test the existence of the string "/$HWCAP\0". */ if (((omit & PD_TKN_CAP) == 0) && (rtld_flags2 & RT_FL2_HWCAP) && ((bptr > _name) && (*bptr == '/') && ((*eptr == '\0') || (*eptr == ':')))) { /* * Decrement the present pointer so that the * directories trailing "/" gets nuked later. */ nptr--, nlen--; olen += MSG_TKN_HWCAP_SIZE; optr += MSG_TKN_HWCAP_SIZE; _flags |= PD_TKN_CAP; } } else { /* * If reserved token was not found, copy the * character. */ *nptr++ = '$'; nlen++; } /* * If a reserved token was found, and could not be expanded, * diagnose the error condition. */ if (token) { if (_flags) flags |= _flags; else { char buf[PATH_MAX], *str; /* * Note, the original string we're expanding * might contain a number of ':' separated * paths. Isolate the path we're processing to * provide a more precise error diagnostic. */ str = strchr(oname, ':'); if (str != NULL) { size_t slen = str - oname; (void) strncpy(buf, oname, slen); buf[slen] = '\0'; str = buf; } else str = oname; eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND2), NAME(lmp), str, token); return (0); } } _optr = optr; } /* * First make sure the current length is shorter than PATH_MAX. We may * arrive here if the given path contains '$' characters which are not * the lead of a reserved token. */ if (nlen >= PATH_MAX) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } /* * If any ISALIST processing has occurred not only do we return the * expanded node we're presently working on, but we can also update the * remaining list so that it is effectively prepended with this node * expanded to all remaining ISALIST options. Note that we can only * handle one ISALIST per node. For more than one ISALIST to be * processed we'd need a better algorithm than above to replace the * newly generated list. Whether we want to encourage the number of * path name permutations this would provide is another question. So, * for now if more than one ISALIST is encountered we return the * original node untouched. */ if (isa && isaflag) { if (isaflag == 1) { if (list) *list = _list; } else { flags &= ~PD_TKN_ISALIST; if ((nptr = (char *)stravl_insert(*name, 0, (*len + 1), 1)) == NULL) return (0); *name = nptr; return (TKN_NONE); } } /* * Copy any remaining string. Terminate the new string with a null as * this string can be displayed via debugging diagnostics. */ if ((_len = (optr - _optr)) != 0) { if ((nlen += _len) < PATH_MAX) { (void) strncpy(nptr, _optr, _len); nptr = nptr + _len; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } *nptr = '\0'; /* * A path that has been expanded is typically used to create full * path names for objects that will be opened. The final path name is * resolved to simplify it, and set the stage for possible $ORIGIN * processing. Therefore, it's usually unnecessary to resolve the path * at this point. However, if a configuration file, containing * directory information is in use, then we might need to lookup this * path in the configuration file. To keep the number of path name * resolutions to a minimum, only resolve paths that contain "./". The * use of "$ORIGIN/../lib" will probably only match a configuration file * entry after resolution. */ if (list && (rtld_flags & RT_FL_DIRCFG) && (flags & TKN_DOTSLASH)) { int len; if ((len = resolvepath(_name, _name, (PATH_MAX - 1))) >= 0) { nlen = (size_t)len; _name[nlen] = '\0'; flags |= PD_TKN_RESOLVED; } } /* * Allocate a new string if necessary. * * If any form of token expansion, or string resolution has occurred, * the storage must be allocated for the new string. * * If we're processing a substring, for example, any string besides the * last string within a search path "A:B:C", then this substring needs * to be isolated with a null terminator. However, if this search path * was created from a previous ISALIST expansion, then all strings must * be allocated, as the isalist expansion will be freed after expansion * processing. */ if ((nptr = (char *)stravl_insert(_name, 0, (nlen + 1), 1)) == NULL) return (0); *name = nptr; *len = nlen; return (flags ? flags : TKN_NONE); } /* * Determine whether a path name is secure. */ int is_path_secure(char *opath, Rt_map *clmp, uint_t info, uint_t flags) { Alist **salpp; Aliste idx; char buffer[PATH_MAX], *npath = NULL; Lm_list *lml = LIST(clmp); Pdesc *pdp; /* * If a path name originates from a configuration file, use it. The use * of a configuration file is already validated for secure applications, * so if we're using a configuration file, we must be able to use all * that it defines. */ if (info & LA_SER_CONFIG) return (1); if ((info & LA_SER_MASK) == 0) { char *str; /* * If the path name specifies a file (rather than a directory), * peel off the file before making the comparison. */ str = strrchr(opath, '/'); /* * Carry out some initial security checks. * * . a simple file name (one containing no "/") is fine, as * this file name will be combined with search paths to * determine the complete path. Note, a secure application * may provide a configuration file, and this can only be * a full path name (PN_FLG_FULLPATH). * . a full path (one starting with "/") is fine, provided * this path name isn't a preload/audit path. * . provided $ORIGIN expansion has not been employed, the * above categories of path are deemed secure. */ if ((((str == 0) && ((info & PD_FLG_FULLPATH) == 0)) || ((*opath == '/') && (str != opath) && ((info & PD_FLG_EXTLOAD) == 0))) && ((flags & PD_TKN_ORIGIN) == 0)) return (1); /* * Determine the directory name of the present path. */ if (str) { if (str == opath) npath = (char *)MSG_ORIG(MSG_STR_SLASH); else { size_t size; if ((size = str - opath) >= PATH_MAX) return (0); (void) strncpy(buffer, opath, size); buffer[size] = '\0'; npath = buffer; } /* * If $ORIGIN processing has been employed, then allow * any directory that has already been used to satisfy * other dependencies, to be used. */ if ((flags & PD_TKN_ORIGIN) && pnavl_recorded(&spavl, npath, 0, NULL)) { DBG_CALL(Dbg_libs_insecure(lml, npath, 1)); return (1); } } } else { /* * A search path, i.e., RPATH, configuration file path, etc. is * used as is. Exceptions to this are: * * . LD_LIBRARY_PATH. * . any $ORIGIN expansion, unless used by a setuid ld.so.1 * to find its own dependencies, or the path name has * already been used to find other dependencies. * . any relative path. */ if (((info & LA_SER_LIBPATH) == 0) && (*opath == '/') && ((flags & PD_TKN_ORIGIN) == 0)) return (1); /* * If $ORIGIN processing is requested, allow a setuid ld.so.1 * to use this path for its own dependencies. Allow the * application to use this path name only if the path name has * already been used to locate other dependencies. */ if (flags & PD_TKN_ORIGIN) { if ((lml->lm_flags & LML_FLG_RTLDLM) && is_rtld_setuid()) return (1); else if (pnavl_recorded(&spavl, opath, 0, NULL)) { DBG_CALL(Dbg_libs_insecure(lml, opath, 1)); return (1); } } npath = (char *)opath; } /* * Determine whether the present directory is trusted. */ if (npath) { salpp = LM_SECURE_DIRS(LIST(clmp)->lm_head)(); for (ALIST_TRAVERSE(*salpp, idx, pdp)) { if (strcmp(npath, pdp->pd_pname) == 0) return (1); } } /* * The path is insecure, so depending on the caller, provide a * diagnostic. Preloaded, or audit libraries generate a warning, as * the process will run without them. */ if (info & PD_FLG_EXTLOAD) { if (lml->lm_flags & LML_FLG_TRC_ENABLE) { if ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0) (void) printf(MSG_INTL(MSG_LDD_FIL_ILLEGAL), opath); } else eprintf(lml, ERR_WARNING, MSG_INTL(MSG_SEC_ILLEGAL), opath); return (0); } /* * Explicit file references are fatal. */ if ((info & LA_SER_MASK) == 0) { if (lml->lm_flags & LML_FLG_TRC_ENABLE) { /* BEGIN CSTYLED */ if ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0) { if (lml->lm_flags & (LML_FLG_TRC_VERBOSE | LML_FLG_TRC_SEARCH)) (void) printf( MSG_INTL(MSG_LDD_FIL_FIND), opath, NAME(clmp)); if (((rtld_flags & RT_FL_SILENCERR) == 0) || (lml->lm_flags & LML_FLG_TRC_VERBOSE)) (void) printf( MSG_INTL(MSG_LDD_FIL_ILLEGAL), opath); } /* END CSTYLED */ } else eprintf(lml, ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), opath, strerror(EACCES)); } else { /* * Search paths. */ DBG_CALL(Dbg_libs_insecure(lml, opath, 0)); if ((lml->lm_flags & LML_FLG_TRC_SEARCH) && ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0)) (void) printf(MSG_INTL(MSG_LDD_PTH_IGNORE), opath); } return (0); } /* * Determine whether a path already exists within the callers Pnode list. */ inline static uint_t is_path_unique(Alist *alp, const char *path) { Aliste idx; Pdesc *pdp; for (ALIST_TRAVERSE(alp, idx, pdp)) { if (pdp->pd_plen && (strcmp(pdp->pd_pname, path) == 0)) return (PD_FLG_DUPLICAT); } return (0); } /* * Expand one or more path names. This routine is called for all path strings, * i.e., NEEDED, rpaths, default search paths, configuration file search paths, * filtees, etc. The path may be a single path name, or a colon separated list * of path names. Each individual path name is processed for possible reserved * token expansion. All string nodes are maintained in allocated memory * (regardless of whether they are constant (":"), or token expanded) to * simplify path name descriptor removal. * * The info argument passes in auxiliary information regarding the callers * intended use of the path names. This information may be maintained in the * path name descriptor element produced to describe the path name (i.e., * LA_SER_LIBPATH etc.), or may be used to determine additional security or * diagnostic processing. */ int expand_paths(Rt_map *clmp, const char *list, Alist **alpp, Aliste alni, uint_t orig, uint_t omit) { char *str, *olist = 0, *nlist = (char *)list; int fnull = FALSE; /* TRUE if empty final path segment seen */ Pdesc *pdp = NULL; for (str = nlist; *nlist || fnull; str = nlist) { char *ostr; char *elist = NULL; size_t len, olen; uint_t tkns = 0; if (*nlist == ';') ++nlist, ++str; if ((*nlist == ':') || fnull) { /* If not a final null segment, check following one */ fnull = !(fnull || *(nlist + 1)); if (*nlist) nlist++; /* * When the shell sees a null PATH segment, it * treats it as if it were the cwd (.). We mimic * this behavior for LD_LIBRARY_PATH and runpaths * (mainly for backwards compatibility with previous * behavior). For other paths, this makes no sense, * so we simply ignore the segment. */ if (!(orig & (LA_SER_LIBPATH | LA_SER_RUNPATH))) continue; /* Process next segment */ str = (char *)MSG_ORIG(MSG_FMT_CWD); len = MSG_FMT_CWD_SIZE; } else { uint_t _tkns; len = 0; while (*nlist && (*nlist != ':') && (*nlist != ';')) { if (*nlist == '/') tkns |= PD_FLG_PNSLASH; nlist++, len++; } /* Check for a following final null segment */ fnull = (*nlist == ':') && !*(nlist + 1); if (*nlist) nlist++; /* * Expand the captured string. Besides expanding the * present path/file entry, we may have a new list to * deal with (ISALIST expands to multiple new entries). */ elist = nlist; ostr = str; olen = len; if ((_tkns = expand(&str, &len, &elist, orig, omit, clmp)) == 0) continue; tkns |= _tkns; } /* * If this a secure application, validation of the expanded * path name may be necessary. */ if ((rtld_flags & RT_FL_SECURE) && (is_path_secure(str, clmp, orig, tkns) == 0)) continue; /* * If required, ensure that the string is unique. For search * paths such as LD_LIBRARY_PATH, users often inherit multiple * paths which result in unnecessary duplication. Note, if * we're debugging, any duplicate entry is retained and flagged * so that the entry can be diagnosed later as part of unused * processing. */ if (orig & PD_FLG_UNIQUE) { Word tracing; tracing = LIST(clmp)->lm_flags & (LML_FLG_TRC_UNREF | LML_FLG_TRC_UNUSED); tkns |= is_path_unique(*alpp, str); /* * Note, use the debug strings rpl_debug and prm_debug * as an indicator that debugging has been requested, * rather than DBG_ENABLE(), as the initial use of * LD_LIBRARY_PATH occurs in preparation for loading * our debugging library. */ if ((tkns & PD_FLG_DUPLICAT) && (tracing == 0) && (rpl_debug == 0) && (prm_debug == 0)) continue; } /* * Create a new pathname descriptor. */ if ((pdp = alist_append(alpp, NULL, sizeof (Pdesc), alni)) == NULL) return (0); pdp->pd_pname = str; pdp->pd_plen = len; pdp->pd_flags = (orig & LA_SER_MASK) | (tkns & PD_MSK_INHERIT); /* * If token expansion occurred, maintain the original string. * This string can be used to provide a more informative error * diagnostic for a file that fails to load, or for displaying * unused search paths. */ if ((tkns & PD_MSK_EXPAND) && ((pdp->pd_oname = stravl_insert(ostr, 0, (olen + 1), 1)) == NULL)) return (0); /* * Now that any duplication of the original string has occurred, * release any previous old listing. */ if (elist && (elist != nlist)) { if (olist) free(olist); nlist = olist = elist; } } if (olist) free(olist); /* * If no paths could be determined (perhaps because of security), then * indicate a failure. */ return (pdp != NULL); } /* * Establish an objects fully resolved path. * * When $ORIGIN was first introduced, the expansion of a relative path name was * deferred until it was required. However now we insure a full path name is * always created - things like the analyzer wish to rely on librtld_db * returning a full path. The overhead of this is perceived to be low, * providing the associated libc version of getcwd is available (see 4336878). * This getcwd() was ported back to Solaris 8.1. */ size_t fullpath(Rt_map *lmp, Fdesc *fdp) { const char *name; /* * Determine whether this path name is already resolved. */ if (fdp && (fdp->fd_flags & FLG_FD_RESOLVED)) { /* * If the resolved path differed from the original name, the * resolved path would have been recorded as the fd_pname. * Steal this path name from the file descriptor. Otherwise, * the path name is the same as the name of this object. */ if (fdp->fd_pname) PATHNAME(lmp) = fdp->fd_pname; else PATHNAME(lmp) = NAME(lmp); } else { /* * If this path name has not yet been resolved, resolve the * current name. */ char _path[PATH_MAX]; const char *path; int size, rsize; if (fdp && fdp->fd_pname) PATHNAME(lmp) = fdp->fd_pname; else PATHNAME(lmp) = NAME(lmp); name = path = PATHNAME(lmp); size = strlen(name); if (path[0] != '/') { /* * If we can't determine the current directory (possible * if too many files are open - EMFILE), or if the * created path is too big, simply revert back to the * initial path name. */ if (getcwd(_path, (PATH_MAX - 2 - size)) != NULL) { (void) strcat(_path, MSG_ORIG(MSG_STR_SLASH)); (void) strcat(_path, name); path = _path; size = strlen(path); } } /* * See if the path name can be reduced further. */ if ((rsize = resolvepath(path, _path, (PATH_MAX - 1))) > 0) { _path[rsize] = '\0'; path = _path; size = rsize; } /* * If the path name is different from the original, duplicate it * so that it is available in a core file. If the duplication * fails simply leave the original path name alone. */ if ((PATHNAME(lmp) = stravl_insert(path, 0, (size + 1), 0)) == NULL) PATHNAME(lmp) = name; } name = ORIGNAME(lmp) = PATHNAME(lmp); /* * Establish the directory name size - this also acts as a flag that the * directory name has been computed. */ DIRSZ(lmp) = strrchr(name, '/') - name; return (DIRSZ(lmp)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Remove objects. Objects need removal from a process as part of: * * - a dlclose() request * * - tearing down a dlopen(), lazy-load, or filter hierarchy that failed to * completely load * * Any other failure condition will result in process exit (in which case all * we have to do is execute the fini's - tear down is unnecessary). * * Any removal of objects is therefore associated with a dlopen() handle. There * is a small window between creation of the first dlopen() object and creating * its handle (in which case remove_so() can get rid of the new link-map if * necessary), but other than this all object removal is driven by inspecting * the components of a handle. * * Things to note. The creation of a link-map, and its addition to the * link-map list occurs in elf_new_lm(), if this returns success the link-map * is valid and added, otherwise any steps (allocations) in the process of * creating the link-map would have been undone. If a failure occurs between * creating the link-map and adding it to a handle, remove_so() is called to * remove the link-map. If a failures occurs after a handle have been * created, remove_hdl() is called to remove the handle and the link-map. */ #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" #include "msg.h" /* * Atexit callback provided by libc. As part of dlclose() determine the address * ranges of all objects that are to be deleted. Pass this information to * libc's pre-atexit routine. Libc will purge any registered atexit() calls * related to those objects about to be deleted. */ static int purge_exit_handlers(Lm_list *lml, Rt_map **tobj) { uint_t num; Rt_map **_tobj; Lc_addr_range_t *addr, *_addr; int error; int (*fptr)(Lc_addr_range_t *, uint_t); /* * Has a callback been established? */ if ((fptr = lml->lm_lcs[CI_ATEXIT].lc_un.lc_func) == NULL) return (0); /* * Determine the total number of mapped segments that will be unloaded. */ for (num = 0, _tobj = tobj; *_tobj != NULL; _tobj++) { Rt_map *lmp = *_tobj; num += MMAPCNT(lmp); } /* * Account for a null entry at the end of the address range array. */ if (num++ == 0) return (0); /* * Allocate an array for the address range. */ if ((addr = malloc(num * sizeof (Lc_addr_range_t))) == NULL) return (1); /* * Fill the address range with each loadable segments size and address. */ for (_tobj = tobj, _addr = addr; *_tobj != NULL; _tobj++) { Rt_map *lmp = *_tobj; mmapobj_result_t *mpp = MMAPS(lmp); uint_t ndx; for (ndx = 0; ndx < MMAPCNT(lmp); ndx++, mpp++) { _addr->lb = (void *)(uintptr_t)(mpp->mr_addr + mpp->mr_offset); _addr->ub = (void *)(uintptr_t)(mpp->mr_addr + mpp->mr_msize); _addr++; } } _addr->lb = _addr->ub = 0; leave(LIST(*tobj), 0); error = (*fptr)(addr, (num - 1)); (void) enter(0); /* * If we fail to converse with libc, generate an error message to * satisfy any dlerror() usage. */ if (error) eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ARG_ATEXIT), error); free(addr); return (error); } /* * Break down an Alist containing pathname descriptors. In most instances, the * Alist is removed completely. However, in some instances the alist is cleaned * of all entries, but retained for later use. */ void remove_alist(Alist **alpp, int complete) { Alist *alp = *alpp; if (alp) { if (complete) { free((void *)alp); *alpp = NULL; } else { alist_reset(alp); } } } /* * Remove a link-map list descriptor. This is called to finalize the removal * of an entire link-map list, after all link-maps have been removed, or none * got added. As load_one() can process a list of potential candidate objects, * the link-map descriptor must be maintained as each object is processed. Only * after all objects have been processed can a failure condition finally tear * down the link-map list descriptor. */ void remove_lml(Lm_list *lml) { if (lml && (lml->lm_head == NULL)) { if (lml->lm_lmidstr) free(lml->lm_lmidstr); if (lml->lm_alp) free(lml->lm_alp); if (lml->lm_lists) free(lml->lm_lists); if (lml->lm_aud_cookies) free(lml->lm_aud_cookies); /* * Cleanup any pending RTLDINFO in the case where it was * allocated but not called (see _relocate_lmc()). */ if (lml->lm_rti) free(lml->lm_rti); if (lml->lm_fpavl) { /* * As we are freeing the link-map list, all nodes must * have previously been removed. */ ASSERT(avl_numnodes(lml->lm_fpavl) == 0); free(lml->lm_fpavl); } (void) aplist_delete_value(dynlm_list, lml); free(lml); } } /* * Remove a link-map. This removes a link-map from its associated list and * free's up the link-map itself. Note, all components that are freed are local * to the link-map, no inter-link-map lists are operated on as these are all * broken down by dlclose() while all objects are still mapped. * * This routine is called from dlclose() to zap individual link-maps after their * interdependencies (DEPENDS(), CALLER(), handles, etc.) have been removed. * This routine is also called from the bowels of load_one() in the case of a * link-map creation failure. */ void remove_so(Lm_list *lml, Rt_map *lmp, Rt_map *clmp) { Dyninfo *dip; if (lmp == NULL) return; /* * Unlink the link map from the link-map list. */ if (lml && lmp) lm_delete(lml, lmp, clmp); /* * If this object contributed any local external vectors for the current * link-map list, remove the vectors. If this object contributed any * global external vectors we should find some new candidates, or leave * this object lying around. */ if (lml) { int tag; for (tag = 0; tag < CI_MAX; tag++) { if (lml->lm_lcs[tag].lc_lmp == lmp) { lml->lm_lcs[tag].lc_lmp = NULL; lml->lm_lcs[tag].lc_un.lc_val = 0; } if (glcs[tag].lc_lmp == lmp) { ASSERT(glcs[tag].lc_lmp != NULL); glcs[tag].lc_lmp = NULL; glcs[tag].lc_un.lc_val = 0; } } } DBG_CALL(Dbg_file_delete(lmp)); /* * If this object is an auditor, determine whether any link-map lists * are maintaining cookies to represent this auditor. These cookies * are established for local auditing preinit and activity events. */ if (FLAGS(lmp) & FLG_RT_AUDIT) { Lm_list *nlml; Aliste idx1; for (APLIST_TRAVERSE(dynlm_list, idx1, nlml)) { Rt_map *hlmp = nlml->lm_head; Audit_client *acp; Aliste idx2; if ((hlmp == NULL) || (FLAGS(hlmp) & FLG_RT_AUDIT)) continue; for (ALIST_TRAVERSE(nlml->lm_aud_cookies, idx2, acp)) { if (acp->ac_lmp != lmp) { alist_delete(nlml->lm_aud_cookies, &idx2); break; } } } } /* * If this is a temporary link-map, put in place to facilitate the * link-edit or a relocatable object, then the link-map contains no * information that needs to be cleaned up. */ if (FLAGS(lmp) & FLG_RT_OBJECT) return; /* * Remove any FullpathNode AVL names if they still exist. */ if (FPNODE(lmp)) fpavl_remove(lmp); /* * Remove any alias names. */ if (ALIAS(lmp)) free(ALIAS(lmp)); /* * Remove any of this objects filtee infrastructure. The filtees them- * selves have already been removed. */ if (((dip = DYNINFO(lmp)) != NULL) && (FLAGS1(lmp) & MSK_RT_FILTER)) { uint_t cnt, max = DYNINFOCNT(lmp); for (cnt = 0; cnt < max; cnt++, dip++) { if ((dip->di_info == NULL) || ((dip->di_flags & MSK_DI_FILTER) == 0)) continue; remove_alist((Alist **)&(dip->di_info), 1); } } /* * Deallocate any remaining cruft and free the link-map. */ if (RLIST(lmp)) remove_alist(&RLIST(lmp), 1); if (AUDITORS(lmp)) audit_desc_cleanup(lmp); if (AUDINFO(lmp)) audit_info_cleanup(lmp); /* * Note that COPY_R() and COPY_S() reference the same memory * location, and that we want to release the memory referenced * without regard to which list it logically belongs to. We can * use either pointer to do this. */ if (COPY_R(lmp)) free(COPY_R(lmp)); /* * During a dlclose() any groups this object was a part of will have * been torn down. However, we can get here to remove an object that * has failed to load, perhaps because its addition to a handle failed. * Therefore if this object indicates that its part of a group tear * these associations down. */ if (GROUPS(lmp) != NULL) { Aliste idx1; Grp_hdl *ghp; for (APLIST_TRAVERSE(GROUPS(lmp), idx1, ghp)) { Grp_desc *gdp; Aliste idx2; for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { if (gdp->gd_depend != lmp) continue; alist_delete(ghp->gh_depends, &idx2); break; } } free(GROUPS(lmp)); } if (HANDLES(lmp)) free(HANDLES(lmp)); /* * Clean up reglist if needed */ if (reglist) { Reglist *cur, *prv, *del; cur = prv = reglist; while (cur) { if (cur->rl_lmp == lmp) { del = cur; if (cur == reglist) { reglist = cur->rl_next; cur = prv = reglist; } else { prv->rl_next = cur->rl_next; cur = cur->rl_next; } free(del); } else { prv = cur; cur = cur->rl_next; } } } /* * If this link map represents a relocatable object concatenation, then * the image was simply generated in allocated memory. Free the memory. * Note: memory maps were fabricated for the relocatable object, and * the mapping infrastructure must be free'd, but there are no address * mappings that must be unmapped. * * Otherwise, unmap the object. */ if (FLAGS(lmp) & FLG_RT_IMGALLOC) free((void *)ADDR(lmp)); if (CAPCHAIN(lmp)) free((void *)CAPCHAIN(lmp)); if (MMAPS(lmp)) { if ((FLAGS(lmp) & FLG_RT_IMGALLOC) == 0) unmap_obj(MMAPS(lmp), MMAPCNT(lmp)); free(MMAPS(lmp)); } free(lmp); } /* * Traverse an objects dependency list removing callers and dependencies. * There's a chicken and egg problem with tearing down link-maps. Any * relationship between link-maps is maintained on a DEPENDS list, and an * associated CALLERS list. These lists can't be broken down at the time a * single link-map is removed, as any related link-map may have already been * removed. Thus, lists between link-maps must be broken down before the * individual link-maps themselves. */ static void remove_lists(Rt_map *lmp, int lazy) { Aliste idx1; Bnd_desc *bdp; /* * First, traverse this objects dependencies. */ for (APLIST_TRAVERSE(DEPENDS(lmp), idx1, bdp)) { Rt_map *dlmp = bdp->b_depend; /* * Remove this object from the dependencies callers. */ (void) aplist_delete_value(CALLERS(dlmp), bdp); free(bdp); } if (DEPENDS(lmp)) { free(DEPENDS(lmp)); DEPENDS(lmp) = NULL; } /* * Second, traverse this objects callers. */ for (APLIST_TRAVERSE(CALLERS(lmp), idx1, bdp)) { Rt_map *clmp = bdp->b_caller; Dyninfo *dip; /* * If we're removing an object that was triggered by a lazyload, * remove the callers DYNINFO() entry and bump the lazy counts. * This reinitialization of the lazy information allows a lazy * object to be reloaded again later. Although we may be * breaking down a group of lazyloaded objects because one has * failed to relocate, it's possible that one or more of the * individual objects can be reloaded without a problem. */ if (lazy && ((dip = DYNINFO(clmp)) != NULL)) { uint_t cnt, max = DYNINFOCNT(clmp); for (cnt = 0; cnt < max; cnt++, dip++) { if ((dip->di_flags & FLG_DI_LAZY) == 0) continue; if (dip->di_info == (void *)lmp) { dip->di_info = NULL; if (LAZY(clmp)++ == 0) LIST(clmp)->lm_lazy++; } } } (void) aplist_delete_value(DEPENDS(clmp), bdp); free(bdp); } if (CALLERS(lmp)) { free(CALLERS(lmp)); CALLERS(lmp) = NULL; } } /* * Delete any temporary link-map control list. */ void remove_cntl(Lm_list *lml, Aliste lmco) { Aliste _lmco = lmco; #if DEBUG Lm_cntl *lmc; lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, lmco); /* * This element should be empty. */ ASSERT(lmc->lc_head == NULL); #endif alist_delete_by_offset(lml->lm_lists, &_lmco); } /* * If a lazy loaded object, or filtee fails to load, possibly because it, or * one of its dependencies can't be relocated, then tear down any objects * that are apart of this link-map control list. */ static void remove_incomplete(Lm_list *lml, Aliste lmco, Rt_map *clmp) { Rt_map *lmp; Lm_cntl *lmc; /* LINTED */ lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, lmco); /* * If auditing is in effect, the loading of these objects might have * resulted in la_objopen() events being posted. Normally, an * la_objclose() event is posted after an object's .fini is executed, * just before the objects are unloaded. These failed objects do not * have their .fini's executed, but an la_objclose() event should still * be posted to any auditors. */ if ((lml->lm_tflags | AFLAGS(clmp)) & LML_TFLG_AUD_OBJCLOSE) { for (lmp = lmc->lc_head; lmp; lmp = NEXT_RT_MAP(lmp)) audit_objclose(lmp, clmp); } /* * Remove any lists that may point between objects. */ for (lmp = lmc->lc_head; lmp; lmp = NEXT_RT_MAP(lmp)) remove_lists(lmp, 1); /* * Finally, remove each object. remove_so() calls lm_delete(), thus * effectively the link-map control head gets updated to point to the * next link-map. */ while ((lmp = lmc->lc_head) != NULL) remove_so(lml, lmp, clmp); lmc->lc_head = lmc->lc_tail = NULL; } /* * Determine whether an object is deletable. */ static int is_deletable(APlist **lmalp, APlist **ghalp, Rt_map *lmp) { Aliste idx; Bnd_desc *bdp; Grp_hdl *ghp; /* * If the object hasn't yet been relocated take this as a sign that * it's loading failed, thus we're here to cleanup. If the object is * relocated it will only be retained if it was marked non-deletable, * and exists on the main link-map control list. */ if ((FLAGS(lmp) & FLG_RT_RELOCED) && (MODE(lmp) & RTLD_NODELETE) && (CNTL(lmp) == ALIST_OFF_DATA)) return (0); /* * If this object is the head of a handle that has not been captured as * a candidate for deletion, then this object is in use from a dlopen() * outside of the scope of this dlclose() family. Dlopen'ed objects, * and filtees, have group descriptors for their callers. Typically * this parent will have callers that are not apart of this dlclose() * family, and thus would be caught by the CALLERS test below. However, * if the caller had itself been dlopen'ed, it may not have any explicit * callers registered for itself. Thus, by looking for objects with * handles we can ferret out these outsiders. */ for (APLIST_TRAVERSE(HANDLES(lmp), idx, ghp)) { /* * If this is a private handle, then the handle isn't referenced * from outside of the group of objects being deleted, and can * be ignored when evaluating objects for deletion. */ if (ghp->gh_flags & GPH_PRIVATE) continue; if (aplist_test(ghalp, ghp, 0) != ALE_EXISTS) return (0); } /* * If this object is called by any object outside of the family of * objects selected for deletion, it can't be deleted. */ for (APLIST_TRAVERSE(CALLERS(lmp), idx, bdp)) { if (aplist_test(lmalp, bdp->b_caller, 0) != ALE_EXISTS) return (0); } /* * This object is a candidate for deletion. */ return (1); } /* * Collect the groups (handles) and associated objects that are candidates for * deletion. The criteria for deleting an object is whether it is only refer- * enced from the objects within the groups that are candidates for deletion. */ static int gdp_collect(APlist **ghalpp, APlist **lmalpp, Grp_hdl *ghp1) { Aliste idx1; Grp_desc *gdp; int action; /* * Add this group to our group collection. If it isn't added either an * allocation has failed, or it already exists. */ if ((action = aplist_test(ghalpp, ghp1, AL_CNT_GRPCLCT)) != ALE_CREATE) return (action); /* * Traverse the dependencies of the group and collect the associated * objects. */ for (ALIST_TRAVERSE(ghp1->gh_depends, idx1, gdp)) { Rt_map *lmp = gdp->gd_depend; /* * We only want to process dependencies for deletion. Although * we want to purge group descriptors for parents, we don't want * to analyze the parent itself for additional filters or * deletion. */ if ((gdp->gd_flags & GPD_PARENT) || ((gdp->gd_flags & GPD_ADDEPS) == 0)) continue; if ((action = aplist_test(lmalpp, lmp, AL_CNT_GRPCLCT)) == ALE_ALLOCFAIL) return (0); if (action == ALE_EXISTS) continue; /* * If this object is a candidate for deletion, determine if the * object provides any filtees. If so, the filter groups are * added to the group collection. * * An object is a candidate for deletion if: * * - the object hasn't yet been relocated, in which case * we're here to clean up a failed load, or * - the object doesn't reside on the base link-map control * list, in which case a group of objects, typically * lazily loaded, or filtees, need cleaning up, or * - the object isn't tagged as non-deletable. */ if ((((FLAGS(lmp) & FLG_RT_RELOCED) == 0) || (CNTL(lmp) != ALIST_OFF_DATA) || ((MODE(lmp) & RTLD_NODELETE) == 0)) && (FLAGS1(lmp) & MSK_RT_FILTER)) { Dyninfo *dip = DYNINFO(lmp); uint_t cnt, max = DYNINFOCNT(lmp); for (cnt = 0; cnt < max; cnt++, dip++) { Alist *falp; Aliste idx2; Pdesc *pdp; if (((falp = (Alist *)dip->di_info) == NULL) || ((dip->di_flags & MSK_DI_FILTER) == 0)) continue; for (ALIST_TRAVERSE(falp, idx2, pdp)) { Grp_hdl *ghp2; if ((pdp->pd_plen == 0) || ((ghp2 = (Grp_hdl *)pdp->pd_info) == NULL)) continue; if (gdp_collect(ghalpp, lmalpp, ghp2) == 0) return (0); } } } } return (1); } /* * Traverse the list of deletable candidates. If an object can't be deleted * then neither can its dependencies or filtees. Any object that is cleared * from being deleted drops the deletion count, plus, if there are no longer * any deletions pending we can discontinue any further processing. */ static int remove_rescan(APlist *lmalp, APlist *ghalp, int *delcnt) { Aliste idx1; Rt_map *lmp; int rescan = 0; for (APLIST_TRAVERSE(lmalp, idx1, lmp)) { Aliste idx2; Bnd_desc *bdp; Dyninfo *dip; uint_t cnt, max; if (FLAGS(lmp) & FLG_RT_DELETE) continue; /* * As this object can't be deleted, make sure its dependencies * aren't deleted either. */ for (APLIST_TRAVERSE(DEPENDS(lmp), idx2, bdp)) { Rt_map *dlmp = bdp->b_depend; if (FLAGS(dlmp) & FLG_RT_DELETE) { FLAGS(dlmp) &= ~FLG_RT_DELETE; if (--(*delcnt) == 0) return (0); rescan = 1; } } /* * If this object is a filtee and one of its filters is outside * of this dlclose family, then it can't be deleted either. */ if ((FLAGS1(lmp) & MSK_RT_FILTER) == 0) continue; dip = DYNINFO(lmp); max = DYNINFOCNT(lmp); for (cnt = 0; cnt < max; cnt++, dip++) { Alist *falp; Pdesc *pdp; if (((falp = (Alist *)dip->di_info) == NULL) || ((dip->di_flags & MSK_DI_FILTER) == 0)) continue; for (ALIST_TRAVERSE(falp, idx2, pdp)) { Aliste idx3; Grp_hdl *ghp; Grp_desc *gdp; if ((pdp->pd_plen == 0) || ((ghp = (Grp_hdl *)pdp->pd_info) == NULL)) continue; if (aplist_test(&ghalp, ghp, 0) == ALE_EXISTS) continue; for (ALIST_TRAVERSE(ghp->gh_depends, idx3, gdp)) { Rt_map *dlmp = gdp->gd_depend; if (FLAGS(dlmp) & FLG_RT_DELETE) { FLAGS(dlmp) &= ~FLG_RT_DELETE; if (--(*delcnt) == 0) return (0); rescan = 1; } } /* * Remove this group handle from our dynamic * deletion list. */ (void) aplist_delete_value(ghalp, ghp); } } } return (rescan); } /* * Cleanup any collection alists we've created. */ static void remove_collect(APlist *ghalp, APlist *lmalp) { if (ghalp) free(ghalp); if (lmalp) free(lmalp); } /* * Remove a handle, leaving the associated objects intact. */ void free_hdl(Grp_hdl *ghp) { if (--(ghp->gh_refcnt) == 0) { Grp_desc *gdp; Aliste idx; uintptr_t ndx; for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) { Rt_map *lmp = gdp->gd_depend; if (ghp->gh_ownlmp == lmp) (void) aplist_delete_value(HANDLES(lmp), ghp); (void) aplist_delete_value(GROUPS(lmp), ghp); } (void) free(ghp->gh_depends); /* LINTED */ ndx = (uintptr_t)ghp % HDLIST_SZ; (void) aplist_delete_value(hdl_alp[ndx], ghp); (void) free(ghp); } } /* * If a load operation, using a new link-map control list, has failed, then * forcibly remove the failed objects. This failure can occur as a result * of a lazy load, a dlopen(), or a filtee load, once the application is * running. If the link-map control list has not yet started relocation, then * cleanup is simply a process of removing all the objects from the control * list. If relocation has begun, then other loads may have been triggered to * satisfy the relocations, and thus we need to break down the control list * using handles. * * The objects associated with this load must be part of a unique handle. In * the case of a dlopen() or filtee request, a handle will have been created. * For a lazyload request, a handle must be generated so that the remove * process can use the handle. * * During the course of processing these objects, other objects (handles) may * have been loaded to satisfy relocation requirements. After these families * have successfully loaded, they will have been propagated to the same link-map * control list. The failed objects need to be removed from this list, while * any successfully loaded families can be left alone, and propagated to the * previous link-map control list. By associating each load request with a * handle, we can isolate the failed objects while not interfering with any * successfully loaded families. */ void remove_lmc(Lm_list *lml, Rt_map *clmp, Aliste lmco, const char *name) { Grp_hdl *ghp; Grp_desc *gdp; Aliste idx; Lm_cntl *lmc; Rt_map *lmp; /* * Determine the link-map control list, and whether any object has been * added to this list. */ /* LINTED */ lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, lmco); if (lmc->lc_head == NULL) return; DBG_CALL(Dbg_file_cleanup(lml, name, lmco)); /* * Obtain a handle for the first object on the link-map control list. * If none exists (which would occur from a lazy load request), and * the link-map control list is being relocated, create a handle. */ lmp = lmc->lc_head; if (HANDLES(lmp)) { ghp = (Grp_hdl *)HANDLES(lmp)->apl_data[0]; /* * If this is a private handle, remove this state, so as to * prevent any attempt to remove the handle more than once. */ ghp->gh_flags &= ~GPH_PRIVATE; } else if (lmc->lc_flags & LMC_FLG_RELOCATING) { /* * Establish a handle, and should anything fail, fall through * to remove the link-map control list. */ if (((ghp = hdl_create(lml, lmc->lc_head, NULL, GPH_PUBLIC, GPD_ADDEPS, 0)) == NULL) || (hdl_initialize(ghp, lmc->lc_head, 0, 0) == 0)) lmc->lc_flags &= ~LMC_FLG_RELOCATING; } else { ghp = NULL; } /* * If relocation hasn't begun, simply remove all the objects from this * list, and any handle that may have been created. */ if ((lmc->lc_flags & LMC_FLG_RELOCATING) == 0) { remove_incomplete(lml, lmco, clmp); if (ghp) { ghp->gh_refcnt = 1; free_hdl(ghp); } return; } ASSERT(ghp != NULL); /* * As the objects of this handle are being forcibly removed, first * remove any associations to objects on parent link-map control * lists. This breaks the bond between a caller and a hierarchy of * dependencies represented by the handle, thus the caller doesn't lock * the hierarchy and prevent their deletion from the generic handle * processing or remove_hdl(). * * This scenario can be produced when the relocation of a object * results in vectoring through a filter that is already loaded. The * filtee may be on the link-map list that is presently being processed, * however an association between the filter and filtee would have been * established during filtee processing. It is this association that * must be broken to allow the objects on this link-map list to be * removed. */ for (ALIST_TRAVERSE(ghp->gh_depends, idx, gdp)) { Rt_map *lmp = gdp->gd_depend; /* * If this object has not been relocated, break down any * dependency relationships the object might have established. */ if ((FLAGS(lmp) & FLG_RT_RELOCED) == 0) remove_lists(lmp, 1); if (CNTL(lmp) == lmco) continue; if (gdp->gd_flags & GPD_FILTER) { Dyninfo *dip = DYNINFO(lmp); uint_t cnt, max = DYNINFOCNT(lmp); for (cnt = 0; cnt < max; cnt++, dip++) { Alist *falp; Aliste idx2; Pdesc *pdp; if (((falp = (Alist *)dip->di_info) == NULL) || ((dip->di_flags & MSK_DI_FILTER) == 0)) continue; for (ALIST_TRAVERSE(falp, idx2, pdp)) { if ((Grp_hdl *)pdp->pd_info == ghp) { pdp->pd_info = NULL; break; } } } } (void) aplist_delete_value(GROUPS(lmp), ghp); alist_delete(ghp->gh_depends, &idx); } /* * Having removed any callers, set the group handle reference count to * one, and let the generic handle remover delete the associated * objects. */ ghp->gh_refcnt = 1; (void) remove_hdl(ghp, clmp, NULL); /* * If this link-map control list still contains objects, determine the * previous control list and move the objects. */ if (lmc->lc_head) { Lm_cntl *plmc; Aliste plmco; plmco = lmco - lml->lm_lists->al_size; /* LINTED */ plmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, plmco); lm_move(lml, lmco, plmco, lmc, plmc); } } /* * Remove the objects associated with a handle. There are two goals here, to * delete the objects associated with the handle, and to remove the handle * itself. Things get a little more complex if the objects selected for * deletion are filters, in this case we also need to collect their filtees, * and process the combined groups as a whole. But, care still must be exer- * cised to make sure any filtees found aren't being used by filters outside of * the groups we've collect. The series of events is basically: * * - Determine the groups (handles) that might be deletable. * * - Determine the objects of these handles that can be deleted. * * - Fire the fini's of those objects selected for deletion. * * - Remove all inter-dependency linked lists while the objects link-maps * are still available. * * - Remove all deletable objects link-maps and unmap the objects themselves. * * - Remove the handle descriptors for each deleted object, and hopefully * the whole handle. * * A handle that can't be deleted is added to an orphans list. This list is * revisited any time another dlclose() request results in handle descriptors * being deleted. These deleted descriptors can be sufficient to allow the * final deletion of the orphaned handles. */ int remove_hdl(Grp_hdl *ghp, Rt_map *clmp, int *removed) { Rt_map *lmp; int rescan = 0; int delcnt = 0, rmcnt = 0, error = 0, orphans; APlist *lmalp = NULL, *ghalp = NULL; Aliste idx1, idx2; Grp_hdl *ghp2; Grp_desc *gdp; Lm_list *lml = NULL; /* * Generate the family of groups and objects that are candidates for * deletion. This consists of the objects that are explicitly defined * as dependencies of this handle, plus any filtee handles and their * associated objects. */ if (gdp_collect(&ghalp, &lmalp, ghp) == 0) { remove_collect(ghalp, lmalp); return (0); } DBG_CALL(Dbg_file_hdl_title(DBG_HDL_DELETE)); /* * Traverse the groups we've collected to determine if any filtees are * included. If so, and the filtee handle is in use by a filter outside * of the family of objects collected for this deletion, it can not be * removed. */ for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) { Grp_hdl *ghp = ghp2; DBG_CALL(Dbg_file_hdl_collect(ghp, 0)); if ((ghp->gh_flags & GPH_FILTEE) == 0) continue; /* * Special case for ld.so.1. There can be multiple instances of * libdl.so.1 using this handle, so although we want the handles * reference count to be decremented, we don't want the handle * removed. */ if (ghp->gh_flags & GPH_LDSO) { DBG_CALL(Dbg_file_hdl_collect(ghp, NAME(lml_rtld.lm_head))); aplist_delete(ghalp, &idx1); continue; } for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { Grp_hdl *ghp3; Aliste idx3; /* * Determine whether this dependency is the filtee's * parent filter, and that it isn't also an explicit * dependency (in which case it would have added its own * dependencies to the handle). */ if ((gdp->gd_flags & (GPD_FILTER | GPD_ADDEPS)) != GPD_FILTER) continue; lmp = gdp->gd_depend; if (FLAGS(lmp) & FLG_RT_DELETE) continue; if (aplist_test(&lmalp, lmp, 0) == ALE_EXISTS) continue; /* * Remove this group handle from our dynamic deletion * list. In addition, recompute the list of objects * that are candidates for deletion to continue this * group verification. */ DBG_CALL(Dbg_file_hdl_collect(ghp, NAME(lmp))); aplist_delete(ghalp, &idx1); free(lmalp); lmalp = NULL; for (APLIST_TRAVERSE(ghalp, idx3, ghp3)) { Aliste idx4; Grp_desc *gdp4; for (ALIST_TRAVERSE(ghp3->gh_depends, idx4, gdp4)) { if ((gdp4->gd_flags & GPD_ADDEPS) == 0) continue; if (aplist_test(&lmalp, gdp4->gd_depend, AL_CNT_GRPCLCT) == ALE_ALLOCFAIL) { remove_collect(ghalp, lmalp); return (0); } } } break; } } /* * Now that we've collected all the handles dependencies, traverse the * collection determining whether they are a candidate for deletion. */ for (APLIST_TRAVERSE(lmalp, idx1, lmp)) { /* * Establish which link-map list we're dealing with for later * .fini processing. */ if (lml == NULL) lml = LIST(lmp); /* * If an object isn't a candidate for deletion we'll have to * rescan the handle insuring that this objects dependencies * aren't deleted either. */ if (is_deletable(&lmalp, &ghalp, lmp)) { FLAGS(lmp) |= FLG_RT_DELETE; delcnt++; } else rescan = 1; } /* * Rescan the handle if any objects where found non-deletable. */ while (rescan) rescan = remove_rescan(lmalp, ghalp, &delcnt); /* * Now that we have determined the number of groups that are candidates * for removal, mark each group descriptor as a candidate for removal * from the group. */ for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) { for (ALIST_TRAVERSE(ghp2->gh_depends, idx2, gdp)) gdp->gd_flags |= GPD_REMOVE; } /* * Now that we know which objects on this handle can't be deleted * determine whether they still need to remain identified as belonging * to this group to be able to continue binding to one another. */ for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) { Grp_hdl *ghp = ghp2; for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { Aliste idx3; Bnd_desc *bdp; lmp = gdp->gd_depend; if (FLAGS(lmp) & FLG_RT_DELETE) continue; for (APLIST_TRAVERSE(DEPENDS(lmp), idx3, bdp)) { Aliste idx4; Grp_desc *gdp4; Rt_map *dlmp = bdp->b_depend; /* * If this dependency (dlmp) can be referenced * by the caller (clmp) without being part of * this group (ghp) then belonging to this group * is no longer necessary. This can occur when * objects are part of multiple handles, or if a * previously deleted handle was moved to the * orphan list and has been reopened. Note, * first make sure the caller can reference the * dependency with this group, if it can't we * must be bound to a filtee, so there's no need * to remain a part of this group either. */ if ((callable(lmp, dlmp, 0, 0) == 0) || callable(lmp, dlmp, ghp, 0)) continue; if (gdp->gd_flags & GPD_REMOVE) gdp->gd_flags &= ~GPD_REMOVE; for (ALIST_TRAVERSE(ghp->gh_depends, idx4, gdp4)) { if (gdp4->gd_depend != dlmp) continue; if (gdp4->gd_flags & GPD_REMOVE) gdp4->gd_flags &= ~GPD_REMOVE; } } } } /* * If the owner of a handle can't be deleted and it's handle descriptor * must remain also, don't delete the handle at all. Leave it for * possible later use. Although it's left intact, it will still be * moved to the orphans list, as we might be able to revisit it on later * dlclose() operations and finally remove the underlying objects. Note * that the handle still remains attached to the owner via the HANDLES * list, so that it can be re-associated to the owner if a dlopen() * of this object reoccurs. */ for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) { Grp_hdl *ghp = ghp2; /* * If this handle is already an orphan, or if it's owner is * deletable there's no need to inspect its dependencies. */ if ((ghp->gh_ownlmp == NULL) || (FLAGS(ghp->gh_ownlmp) & FLG_RT_DELETE)) continue; /* * Make sure all handle dependencies aren't removed or the * dependencies themselves aren't deleted. */ for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { lmp = gdp->gd_depend; /* * The first dependency of a non-orphaned handle is the * owner. If the handle descriptor for this isn't * required there's no need to look at any other of the * handles dependencies. */ if ((lmp == ghp->gh_ownlmp) && (gdp->gd_flags & GPD_REMOVE)) break; if (gdp->gd_flags & GPD_REMOVE) gdp->gd_flags &= ~GPD_REMOVE; if (FLAGS(lmp) & FLG_RT_DELETE) { FLAGS(lmp) &= ~FLG_RT_DELETE; delcnt--; } } } /* * Final scan of objects to see if any objects are to to be deleted. * Also - display diagnostic information on what operations are to be * performed on the collected handles before firing .fini's (which * produces additional diagnostics). */ for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) { Grp_hdl *ghp = ghp2; DBG_CALL(Dbg_file_hdl_title(DBG_HDL_DELETE)); for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { Grp_hdl *ghp3; Aliste idx3; int flag; lmp = gdp->gd_depend; /* * Note, we must never delete a parent. The parent * may already be tagged for deletion from a previous * dlclose(). That dlclose has triggered this dlclose(), * but the parents deletion is the responsibility of the * previous dlclose(), not this one. */ if ((FLAGS(lmp) & FLG_RT_DELETE) && ((gdp->gd_flags & GPD_PARENT) == 0)) { flag = DBG_DEP_DELETE; /* * Remove any pathnames from the FullpathNode * AVL tree. As we're about to fire .fini's, * it's possible this object will be required * again, in which case we want to make sure a * new version of the object gets loaded. */ if (FPNODE(lmp)) fpavl_remove(lmp); } else if (gdp->gd_flags & GPD_REMOVE) flag = DBG_DEP_REMOVE; else flag = DBG_DEP_REMAIN; DBG_CALL(Dbg_file_hdl_action(ghp, lmp, flag, 0)); /* * If this object contains any private handles, remove * them now. */ for (APLIST_TRAVERSE(HANDLES(lmp), idx3, ghp3)) { if (ghp3->gh_flags & GPH_PRIVATE) free_hdl(ghp3); } } } /* * If there are objects to be deleted process their .fini's. */ if (delcnt) { Rt_map **tobj; /* * Sort and fire all fini's of the objects selected for * deletion. Note that we have to start our search from the * link-map head - there's no telling whether this object has * dependencies on objects that were loaded before it and which * can now be deleted. If the tsort() fails because of an * allocation error then that might just be a symptom of why * we're here in the first place - forgo the fini's but * continue to try cleaning up. */ lml->lm_flags |= LML_FLG_OBJDELETED; if (((tobj = tsort(lml->lm_head, delcnt, (RT_SORT_DELETE | RT_SORT_FWD))) != NULL) && (tobj != (Rt_map **)S_ERROR)) { error = purge_exit_handlers(lml, tobj); call_fini(lml, tobj, clmp); } } /* * Now that .fini processing (which may have involved new bindings) * is complete, remove all inter-dependency lists from those objects * selected for deletion. */ for (APLIST_TRAVERSE(lmalp, idx1, lmp)) { Dyninfo *dip; uint_t cnt, max; if (FLAGS(lmp) & FLG_RT_DELETE) remove_lists(lmp, 0); /* * Determine whether we're dealing with a filter, and if so * process any inter-dependencies with its filtee's. */ if ((FLAGS1(lmp) & MSK_RT_FILTER) == 0) continue; dip = DYNINFO(lmp); max = DYNINFOCNT(lmp); for (cnt = 0; cnt < max; cnt++, dip++) { Alist *falp; Aliste idx2; Pdesc *pdp; if (((falp = (Alist *)dip->di_info) == NULL) || ((dip->di_flags & MSK_DI_FILTER) == 0)) continue; for (ALIST_TRAVERSE(falp, idx2, pdp)) { Grp_hdl *ghp; if ((pdp->pd_plen == 0) || ((ghp = (Grp_hdl *)pdp->pd_info) == NULL)) continue; /* * Determine whether this filtee's handle is a * part of the list of handles being deleted. */ if (aplist_test(&ghalp, ghp, 0) == ALE_EXISTS) { /* * If this handle exists on the deletion * list, then it has been removed. If * this filter isn't going to be * deleted, sever its reference to the * handle. */ pdp->pd_info = NULL; } else { /* * If this handle isn't on the deletion * list, then it must still exist. If * this filter is being deleted, make * sure the filtees reference count * gets decremented. */ if (FLAGS(lmp) & FLG_RT_DELETE) { (void) dlclose_core(ghp, lmp, lml); } } } } } /* * If called from dlclose(), determine if there are already handles on * the orphans list that we can reinvestigate. */ if ((removed == 0) && aplist_nitems(hdl_alp[HDLIST_ORP])) orphans = 1; else orphans = 0; /* * Finally remove any handle infrastructure and remove any objects * marked for deletion. */ for (APLIST_TRAVERSE(ghalp, idx1, ghp2)) { Grp_hdl *ghp = ghp2; /* * If we're not dealing with orphaned handles remove this handle * from its present handle list. */ if (removed == 0) { uintptr_t ndx; /* LINTED */ ndx = (uintptr_t)ghp % HDLIST_SZ; (void) aplist_delete_value(hdl_alp[ndx], ghp); } /* * Traverse each handle dependency. Retain the dependencies * flags to insure we don't delete any parents (the flags * information is deleted as part of the alist removal that * occurs before we inspect the object for deletion). */ for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { uint_t flags = gdp->gd_flags; if ((flags & GPD_REMOVE) == 0) continue; lmp = gdp->gd_depend; rmcnt++; /* * If this object is the owner of the handle break that * association in case the handle is retained. */ if (ghp->gh_ownlmp == lmp) { (void) aplist_delete_value(HANDLES(lmp), ghp); ghp->gh_ownlmp = NULL; } (void) aplist_delete_value(GROUPS(lmp), ghp); alist_delete(ghp->gh_depends, &idx2); /* * Complete the link-map deletion if appropriate. */ if ((FLAGS(lmp) & FLG_RT_DELETE) && ((flags & GPD_PARENT) == 0)) { tls_modaddrem(lmp, TM_FLG_MODREM); remove_so(LIST(lmp), lmp, clmp); } } /* * If we've deleted all the dependencies of the handle, finalize * the cleanup by removing the handle itself. * * Otherwise we're left with a handle containing one or more * objects that can not be deleted (they're in use by other * handles, non-deletable, etc.), but require to remain a part * of this group to allow them to continue binding to one * another. * * If the handles reference count is zero, or represents a * link-map list (dlopen(0)), then move that handle to the * orphans list. Should another dlclose() operation occur that * results in the removal of handle descriptors, these orphan * handles are re-examined to determine if their deletion can * be completed. */ if (ghp->gh_depends->al_nitems == 0) { free(ghp->gh_depends); free(ghp); } else if ((ghp->gh_refcnt == 0) && ((ghp->gh_flags & GPH_ZERO) == 0)) { /* * Move this handle to the orphans list. */ (void) aplist_append(&hdl_alp[HDLIST_ORP], ghp, AL_CNT_HANDLES); if (DBG_ENABLED) { DBG_CALL(Dbg_file_hdl_title(DBG_HDL_ORPHAN)); for (ALIST_TRAVERSE(ghp->gh_depends, idx1, gdp)) DBG_CALL(Dbg_file_hdl_action(ghp, gdp->gd_depend, DBG_DEP_ORPHAN, 0)); } } } /* * If no handle descriptors got removed there's no point in looking for * orphans to process. */ if (rmcnt == 0) orphans = 0; /* * Cleanup any alists we've created. */ remove_collect(ghalp, lmalp); /* * If orphan processing isn't required we're done. If our processing * originated from investigating orphans, return the number of handle * descriptors removed as an indication whether orphan processing * should continue. */ if (orphans == 0) { if (removed) *removed = rmcnt; return (error); } /* * Traverse the orphans list as many times as necessary until no * handle removals occur. */ do { APlist *alp; Aliste idx; Grp_hdl *ghp, *oghp = NULL; int title = 0; /* * Effectively clean the HDLIST_ORP list. Any object that can't * be removed will be re-added to the list. */ alp = hdl_alp[HDLIST_ORP]; hdl_alp[HDLIST_ORP] = NULL; rescan = 0; for (APLIST_TRAVERSE(alp, idx, ghp)) { int _error, _remove; if (title++ == 0) DBG_CALL(Dbg_file_del_rescan(ghp->gh_ownlml)); if (oghp) { (void) aplist_delete_value(alp, oghp); oghp = NULL; } if (((_error = remove_hdl(ghp, clmp, &_remove)) != 0) && (error == 0)) error = _error; if (_remove) rescan++; oghp = ghp; } if (oghp) { (void) aplist_delete_value(alp, oghp); oghp = NULL; } if (alp) free((void *)alp); } while (rescan && aplist_nitems(hdl_alp[HDLIST_ORP])); return (error); } # # Copyright 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Message file for cmd/sgs/rtld (ld.so.1) - 64bit specific. @ _END_ @ MSG_SYM_LASYMBIND_64 "la_symbind64" @ MSG_SYM_LAPLTEXIT_64 "la_pltexit64" @ MSG_PTH_CONFIG_64 "/var/ld/ld.config" @ MSG_PTH_LDPROF_64 "/usr/lib/link_audit/ldprof.so.1" @ MSG_PTH_LDPROFSE_64 "/usr/lib/secure/ldprof.so.1" @ MSG_PTH_LIB_64 "/lib" @ MSG_PTH_USRLIB_64 "/usr/lib" @ MSG_PTH_LIBSE_64 "/lib/secure" @ MSG_PTH_USRLIBSE_64 "/usr/lib/secure" # # Copyright 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # @ _START_ # Messages to satisfy chkmsg target. Numerous messages within common code # aren't required for each target, but chkmsg has no way of dealing with # ifdefs, thus this file exists to satisfy common code messages. @ _END_ @ MSG_PTH_CONFIG_64 "/var/ld/ld.config" @ MSG_PTH_LDPROF_64 "/usr/lib/link_audit/ldprof.so.1" @ MSG_PTH_LDPROFSE_64 "/usr/lib/secure/ldprof.so.1" @ MSG_PTH_RTLD_64 "/usr/lib/ld.so.1" @ MSG_PTH_LIB_64 "/lib" @ MSG_PTH_USRLIB_64 "/usr/lib" @ MSG_PTH_LIBSE_64 "/lib/secure" @ MSG_PTH_USRLIBSE_64 "/usr/lib/secure" @ MSG_SYM_LAV8PLTENTER "la_sparcv8_pltenter" @ MSG_SYM_LAV9PLTENTER "la_sparcv9_pltenter" @ MSG_SYM_LAX86PLTENTER "la_i86_pltenter" @ MSG_SYM_LAAMD64PLTENTER "la_amd64_pltenter" @ MSG_SYM_LAPLTEXIT "la_pltexit" @ MSG_SYM_LAPLTEXIT_64 "la_pltexit64" @ MSG_SYM_LASYMBIND "la_symbind" @ MSG_SYM_LASYMBIND_64 "la_symbind64" # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" # @ _END_ @ MSG_SYM_PLT "_PROCEDURE_LINKAGE_TABLE_" # # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # ident "%Z%%M% %I% %E% SMI" @ _START_ # Message file for cmd/sgs/rtld (ld.so.1) - 64bit amd64 specific. @ MSG_REL_NOFIT "relocation error: %s: file %s: symbol %s: \ value 0x%llx does not fit" @ MSG_UNW_BADVERS "dlgetamd64unwind: invalid data buffer dlui_version: \ %d: version unitialized, or exceeds current version: %d" @ _END_ @ MSG_SYM_LAAMD64PLTENTER "la_amd64_pltenter" @ MSG_PTH_RTLD_64 "/usr/lib/ld.so.1" # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2022 Oxide Computer Company # @ _START_ # Message file for cmd/sgs/rtld (ld.so.1) @ MSG_ID_RTLD # Usage error @ MSG_USG_BADOPT "usage: ld.so.1 [-e option,...] \ dynamic-object [object args,...]" # Message formatting error. @ MSG_EMG_BUFOVRFLW "ld.so.1: internal: message buffer overflow" # Argument processing errors @ MSG_ARG_ILLMODE_1 "illegal mode: RTLD_NOW or RTLD_LAZY or RTLD_NOLOAD \ required" @ MSG_ARG_ILLMODE_2 "illegal mode: RTLD_NOW cannot be combined with \ RTLD_LAZY" @ MSG_ARG_ILLMODE_3 "illegal mode: LM_ID_NEWLM requires non-zero path" @ MSG_ARG_ILLMODE_4 "illegal mode: LM_ID_NEWLM cannot be combined with \ RTLD_PARENT" @ MSG_ARG_ILLMODE_5 "illegal mode: potential multiple path expansion \ requires RTLD_FIRST" @ MSG_ARG_ILLPATH "illegal pathname" @ MSG_ARG_ILLSYM "illegal symbol name" @ MSG_ARG_ILLNAME "illegal name" @ MSG_ARG_INVADDR "address 0x%llx does not fall within any mapped object" @ MSG_ARG_INVHNDL "invalid handle: 0x%llx" @ MSG_ARG_ILLVAL "illegal request value" @ MSG_ARG_NOCONFIG "no configuration file in use" @ MSG_ARG_NOPROFNAME "no profile target specified" @ MSG_ARG_ATEXIT "purge of atexit() registrations failed: %d" @ MSG_ARG_SERCNT "information path count (%d) insufficient" @ MSG_ARG_SERSIZE "information buffer size (%lld) insufficient" @ MSG_ARG_ILLFLAGS "illegal flags value: %d" @ MSG_ARG_ILLINFO "non-null info field required for flags value: %d" @ MSG_ARG_INVSIG "invalid signal supplied: %d" # General error diagnostics @ MSG_GEN_NOOPEN "DF_1_NOOPEN tagged object may not be dlopen()'ed" @ MSG_GEN_NOFILE "%s: can't find file" @ MSG_GEN_ALTER "%s: alternate file in use" @ MSG_GEN_NOSYM "%s: can't find symbol" @ MSG_GEN_NODUMP "%s: DF_1_NODUMP tagged object may not be dldump()'ed" # Move related messages @ MSG_MOVE_ERR1 "move entry with illegal size; ignored" # Relocation processing messages (some of these are required to satisfy # do_reloc(), which is common code used by cmd/sgs/libld - make sure both # message files remain consistent). @ MSG_REL_NOSYM "relocation error: file %s: symbol %s: \ referenced symbol not found" @ MSG_REL_PLTREF "relocation error: %s: unidentifiable procedure \ reference: link-map 0x%llx, offset 0x%llx, \ called from 0x%llx" @ MSG_REL_UNSUPSZ "relocation error: %s: file %s: symbol %s: \ offset size (%d bytes) is not supported" @ MSG_REL_BADTLS "relocation error: %s: file %s: symbol %s: \ file contains insufficient TLS support information" # System call messages. @ MSG_SYS_BRK "%s: brk failed: %s" @ MSG_SYS_OPEN "%s: open failed: %s" @ MSG_SYS_MMAP "%s: mmap failed: %s" @ MSG_SYS_MPROT "%s: mprotect failed: %s" @ MSG_SYS_MMAPANON "mmap anon failed: %s" @ MSG_SEC_OPEN "%s: open failed: No such file in secure directories" @ MSG_SEC_ILLEGAL "%s: open failed: illegal insecure pathname" # Configuration failures @ MSG_CONF_APP "configuration file: %s: is specific to application: %s" @ MSG_CONF_DSTAT "configuration file: %s: original directory %s: stat \ failed: %s" @ MSG_CONF_FSTAT "configuration file: %s: original file %s: stat \ failed: %s" @ MSG_CONF_FCMP "configuration file: %s: original file %s: modified \ since configuration file creation" # Link Audit diagnostic message formats @ MSG_AUD_BADVERS "version mismatch: current %d: required %d" @ MSG_AUD_DISABLED "%s: audit initialization failure: disabled" # Versioning diagnostics. @ MSG_VER_NFOUND "%s: version '%s' not found (required by file %s)" # Diagnostics generated under the control of ldd(1). @ MSG_LDD_VER_FIND " find version=%s\n" @ MSG_LDD_VER_NFOUND "\t%s (%s) =>\t (version not found)\n" @ MSG_LDD_SYM_NFOUND "\tsymbol not found: %s\t\t(%s)\n" @ MSG_LDD_PTH_TRYING " trying path=%s%s\n" @ MSG_LDD_PTH_LIBPATH " search path=%s (LD_LIBRARY_PATH)\n" @ MSG_LDD_PTH_LIBPATHC " search path=%s (configuration \ LD_LIBRARY_PATH - %s)\n" @ MSG_LDD_PTH_RUNPATH " search path=%s (RUNPATH/RPATH from file %s)\n" @ MSG_LDD_PTH_BGNDFL " search path=" @ MSG_LDD_PTH_ENDDFL " (default)\n" @ MSG_LDD_PTH_ENDDFLC " (configuration default - %s)\n" @ MSG_LDD_PTH_IGNORE " ignore path=%s (insecure directory name)\n" @ MSG_LDD_FIL_FILTER "\n object=%s; filter for %s\n" @ MSG_LDD_FIL_FIND "\n find object=%s; required by %s\n" @ MSG_LDD_FIL_NFOUND "\t%s =>\t (file not found)\n" @ MSG_LDD_FIL_ILLEGAL "\t%s =>\t (illegal insecure pathname)\n" @ MSG_LDD_FIL_ALTER " (alternate)" @ MSG_LDD_CAP_NFOUND "\t%s =>\t (no capability objects found)\n" @ MSG_LDD_SEC_NFOUND "\t%s =>\t (file not found in secure directories)\n" @ MSG_LDD_REL_ERR1 "\trelocation %s offset invalid: %s: offset=0x%llx \ lies outside memory image; relocation discarded\n" @ MSG_LDD_REL_ERR2 "\tloading after relocation has started: interposition \ request (DF_1_INTERPOSE) ignored: %s\n" @ MSG_LDD_MOVE_ERR "\tmove %lld offset invalid: %s: offset=0x%llx \ lies outside memory image; move discarded\n" @ MSG_LDD_CPY_SIZDIF "\trelocation %s sizes differ: %s\n\ \t\t(file %s size=0x%llx; file %s size=0x%llx)\n" @ MSG_LDD_CPY_INSDATA "\t\t%s size used; possible insufficient data copied\n" @ MSG_LDD_CPY_DATRUNC "\t\t%s size used; possible data truncation\n" @ MSG_LDD_CPY_PROT "\trelocation %s symbol: %s: file %s: relocation bound \ to a symbol with STV_PROTECTED visibility\n" @ MSG_LDD_INIT_FMT_01 "\n cyclic dependencies detected, group [%d]:\n" @ MSG_LDD_INIT_FMT_02 " init object=%s\n" @ MSG_LDD_INIT_FMT_03 " init object=%s - cyclic group [%d], referenced \ by:\n" @ MSG_LDD_UNUSED_FMT " unused object=%s\n" @ MSG_LDD_UNCYC_FMT " unused object=%s; member of cyclic group [%d]\n" @ MSG_LDD_UNREF_FMT " unreferenced object=%s; unused dependency of %s\n" @ MSG_LDD_REL_CPYDISP "\tsymbol %s: file %s: copy relocation symbol may \ have been displacement relocated\n" @ MSG_LDD_REJ_MACH " - wrong ELF machine type: %s" @ MSG_LDD_REJ_CLASS " - wrong ELF class: %s" @ MSG_LDD_REJ_DATA " - wrong ELF data format: %s" @ MSG_LDD_REJ_TYPE " - bad ELF type: %s" @ MSG_LDD_REJ_BADFLAG " - bad ELF flags value: %s" @ MSG_LDD_REJ_MISFLAG " - mismatched ELF flags value: %s" @ MSG_LDD_REJ_VERSION " - mismatched ELF/lib version: %s" @ MSG_LDD_REJ_HAL " - HAL R1 extensions required" @ MSG_LDD_REJ_US3 " - Sun UltraSPARC III extensions required" @ MSG_LDD_REJ_STR " - %s" @ MSG_LDD_REJ_UNKFILE " - unknown file type" @ MSG_LDD_REJ_UNKCAP " - unknown capability: %d" @ MSG_LDD_REJ_HWCAP_1 " - hardware capability (CA_SUNW_HW_1) unsupported: %s" @ MSG_LDD_REJ_SFCAP_1 " - software capability (CA_SUNW_SF_1) unsupported: %s" @ MSG_LDD_REJ_MACHCAP " - machine capability (CA_SUNW_MACH) unsupported: %s" @ MSG_LDD_REJ_PLATCAP " - platform capability (CA_SUNW_PLAT) unsupported: %s" @ MSG_LDD_REJ_HWCAP_2 " - hardware capability (CA_SUNW_HW_2) unsupported: %s" @ MSG_LDD_REJ_HWCAP_3 " - hardware capability (CA_SUNW_HW_3) unsupported: %s" @ MSG_LDD_REJ_ARCHIVE " - invalid archive use" @ MSG_LDD_REJ_KMOD " - invalid kernel module use" @ MSG_LDD_WARN_UNKCAP "%s: unknown capability: %d" @ MSG_LDD_WARN_HWCAP_1 "%s: warning: hardware capability (CA_SUNW_HW_1) \ unsupported: %s\n" @ MSG_LDD_WARN_SFCAP_1 "%s: warning: software capability (CA_SUNW_SF_1) \ unsupported: %s\n" @ MSG_LDD_WARN_MACHCAP "%s: warning: machine capability (CA_SUNW_MACH) \ unsupported: %s\n" @ MSG_LDD_WARN_PLATCAP "%s: warning: platform capability (CA_SUNW_PLAT) \ unsupported: %s\n" @ MSG_LDD_WARN_HWCAP_2 "%s: warning: hardware capability (CA_SUNW_HW_2) \ unsupported: %s\n" @ MSG_LDD_WARN_HWCAP_3 "%s: warning: hardware capability (CA_SUNW_HW_3) \ unsupported: %s\n" # Error rejection messages. @ MSG_ERR_REJ_MACH "%s: wrong ELF machine type: %s" @ MSG_ERR_REJ_CLASS "%s: wrong ELF class: %s" @ MSG_ERR_REJ_DATA "%s: wrong ELF data format: %s" @ MSG_ERR_REJ_TYPE "%s: bad ELF type: %s" @ MSG_ERR_REJ_BADFLAG "%s: bad ELF flags value: %s" @ MSG_ERR_REJ_MISFLAG "%s: mismatched ELF flags value: %s" @ MSG_ERR_REJ_VERSION "%s: mismatched ELF/lib version: %s" @ MSG_ERR_REJ_HAL "%s: HAL R1 extensions required" @ MSG_ERR_REJ_US3 "%s: Sun UltraSPARC III extensions required" @ MSG_ERR_REJ_STR "%s: %s" @ MSG_ERR_REJ_UNKFILE "%s: unknown file type" @ MSG_ERR_REJ_UNKCAP "%s: unknown capability: %d" @ MSG_ERR_REJ_HWCAP_1 "%s: hardware capability (CA_SUNW_HW_1) unsupported: %s" @ MSG_ERR_REJ_SFCAP_1 "%s: software capability (CA_SUNW_SF_1) unsupported: %s" @ MSG_ERR_REJ_MACHCAP "%s: machine capability (CA_SUNW_MACH) unsupported: %s" @ MSG_ERR_REJ_PLATCAP "%s: platform capability (CA_SUNW_PLAT) unsupported: %s" @ MSG_ERR_REJ_HWCAP_2 "%s: hardware capability (CA_SUNW_HW_2) unsupported: %s" @ MSG_ERR_REJ_ARCHIVE "%s: invalid archive use" @ MSG_ERR_REJ_KMOD "%s: invalid kernel module use" @ MSG_ERR_REJ_HWCAP_3 "%s: hardware capability (CA_SUNW_HW_3) unsupported: %s" # Error TLS failures @ MSG_TLS_NOSUPPORT "%s: TLS requirement failure : TLS support is \ unavailable" @ MSG_TLS_STATBASE "%s: static TLS failure: object is not part of primary \ link-map list" @ MSG_TLS_STATSIZE "%s: static TLS failure: object loaded after process \ initialization: size (%#llx) exceeds available backup \ reservation (%#llx)" @ MSG_TLS_STATINIT "%s: static TLS failure: object loaded after process \ initialization: can not accommodate initialized data" # Error expand() @ MSG_ERR_EXPAND1 "%s: %s: path name too long" @ MSG_ERR_EXPAND2 "%s: %s: token %s could not be expanded" # Specific dlinfo() messages. @ MSG_DEF_NODEPFOUND "%s: no deferred dependency found" @ MSG_DEF_NOSYMFOUND "%s: no deferred symbol found" @ MSG_DEF_DEPLOADED "%s: deferred dependency is already loaded" # Error diagnostic standard prefixes. @ MSG_ERR_WARNING "warning: " @ MSG_ERR_GUIDANCE "guidance: " @ MSG_ERR_FATAL "fatal: " @ MSG_ERR_ELF "elf error: " @ MSG_STR_UNKNOWN "(unknown)" @ MSG_STR_NULL "(null)" # Unused errors - used by ldd. @ MSG_USD_LDLIBPATH " unused search path=%s (LD_LIBRARY_PATH)\n" @ MSG_DUP_LDLIBPATH " unused (duplicate) search path=%s \ (LD_LIBRARY_PATH)\n" @ MSG_USD_LDLIBPATHC " unused search path=%s (configuration \ LD_LIBRARY_PATH - %s)\n" @ MSG_DUP_LDLIBPATHC " unused (duplicate) search path=%s (configuration \ LD_LIBRARY_PATH - %s)\n" @ MSG_USD_RUNPATH " unused search path=%s (RUNPATH/RPATH from \ file %s)\n" @ MSG_CAP_IGN_UNKCAP "ignoring unknown capability: %s" @ _END_ # The following strings represent reserved words, files, pathnames and symbols. # Reference to this strings is via the MSG_ORIG() macro, and thus no message # translation is required. @ MSG_LDD_FIL_PATH "\t%s%s%s\n" @ MSG_LDD_FIL_EQUIV "\t%s =>\t %s%s%s\n" @ MSG_LDD_FMT_PATH1 "%s" @ MSG_LDD_FMT_PATHN ":%s" @ MSG_LDD_INIT_FMT_FILE "\t%s\n" @ MSG_LDD_VER_FOUND "\t%s (%s) =>\t %s\n" @ MSG_STR_EMPTY "" @ MSG_STR_NEGATE "-" @ MSG_STR_ZERO "0" @ MSG_STR_HEX "0x" @ MSG_STR_ELF "ELF" @ MSG_STR_EMSGFOR1 "%s: %s: %s" @ MSG_STR_EMSGFOR2 "%s: %s" @ MSG_STR_HEXNUM "0123456789abcdef" @ MSG_STR_NL "\n" @ MSG_STR_SLASH "/" @ MSG_STR_DELIMIT ": " @ MSG_STR_ONE "1" @ MSG_CAP_DELIMIT "," @ MSG_SUNW_OST_SGS "SUNW_OST_SGS" @ MSG_SUNW_OST_OSLIB "SUNW_OST_OSLIB" @ MSG_TKN_CAPABILITY "CAPABILITY" @ MSG_TKN_MACHINE "MACHINE" @ MSG_TKN_PLATFORM "PLATFORM" @ MSG_TKN_ORIGIN "ORIGIN" @ MSG_TKN_ISALIST "ISALIST" @ MSG_TKN_OSNAME "OSNAME" @ MSG_TKN_OSREL "OSREL" @ MSG_TKN_HWCAP "HWCAP" @ MSG_TKN_BINDINGS "bindings" @ MSG_TKN_POSIX "POSIX" @ MSG_TKN_DOTDOT ".." @ MSG_FMT_CWD "." @ MSG_FMT_MSGFILE "/usr/lib/locale/%s/LC_MESSAGES/%s.mo" @ MSG_FIL_RTLD "ld.so.1" @ MSG_FIL_LIBC "libc.so.1" @ MSG_SYM_ELFERRMSG "elf_errmsg" @ MSG_SYM_ELFERRNO "elf_errno" @ MSG_SYM_ELFPLTTRACE "elf_plt_trace" @ MSG_SYM_ENVIRON "_environ" @ MSG_SYM_LAPREINIT "la_preinit" @ MSG_SYM_LAVERSION "la_version" @ MSG_SYM_LAACTIVITY "la_activity" @ MSG_SYM_LAOBJSEARCH "la_objsearch" @ MSG_SYM_LAOBJOPEN "la_objopen" @ MSG_SYM_LAOBJFILTER "la_objfilter" @ MSG_SYM_LAOBJCLOSE "la_objclose" @ MSG_SYM_LADYNDATA "la_dyndata" @ MSG_SYM_START "_START_" @ MSG_SPECFIL_DYNPLT "dyn_plt(ld.so.1)" @ MSG_PTH_LDPROF "/usr/lib/link_audit/ldprof.so.1" @ MSG_PTH_LDPROFSE "/usr/lib/secure/ldprof.so.1" @ MSG_PTH_RTLD "/usr/lib/ld.so.1" @ MSG_PTH_LIB "/lib" @ MSG_PTH_USRLIB "/usr/lib" @ MSG_PTH_LIBSE "/lib/secure" @ MSG_PTH_USRLIBSE "/usr/lib/secure" @ MSG_PTH_DEVNULL "/dev/null" @ MSG_PTH_CONFIG "/var/ld/ld.config" @ MSG_PTH_VARTMP "/var/tmp" @ MSG_ORG_CONFIG "$ORIGIN/ld.config.%s" @ MSG_LD_AUDIT "AUDIT" @ MSG_LD_AUDIT_ARGS "AUDIT_ARGS" @ MSG_LD_BIND_LAZY "BIND_LAZY" @ MSG_LD_BIND_NOW "BIND_NOW" @ MSG_LD_BIND_NOT "BIND_NOT" @ MSG_LD_BINDINGS "BINDINGS" @ MSG_LD_CONFGEN "CONFGEN" @ MSG_LD_CAP_FILES "CAP_FILES" @ MSG_LD_CONFIG "CONFIG" @ MSG_LD_DEBUG "DEBUG" @ MSG_LD_DEBUG_OUTPUT "DEBUG_OUTPUT" @ MSG_LD_DEMANGLE "DEMANGLE" @ MSG_LD_FLAGS "FLAGS" @ MSG_LD_HWCAP "HWCAP" @ MSG_LD_INIT "INIT" @ MSG_LD_LIBPATH "LIBRARY_PATH" @ MSG_LD_LOADAVAIL "LOADAVAIL" @ MSG_LD_LOADFLTR "LOADFLTR" @ MSG_LD_MACHCAP "MACHCAP" @ MSG_LD_NOAUDIT "NOAUDIT" @ MSG_LD_NOAUXFLTR "NOAUXFLTR" @ MSG_LD_NOBAPLT "NOBAPLT" @ MSG_LD_NOCONFIG "NOCONFIG" @ MSG_LD_NODIRCONFIG "NODIRCONFIG" @ MSG_LD_NODIRECT "NODIRECT" @ MSG_LD_NOENVCONFIG "NOENVCONFIG" @ MSG_LD_NOENVIRON "NOENVIRON" @ MSG_LD_NOFLTCONFIG "NOFLTCONFIG" @ MSG_LD_NOLAZY "NOLAZYLOAD" @ MSG_LD_NOOBJALTER "NOOBJALTER" @ MSG_LD_NOPAREXT "NOPAREXT" @ MSG_LD_NOUNRESWEAK "NOUNRESWEAK" @ MSG_LD_NOVERSION "NOVERSION" @ MSG_LD_PLATCAP "PLATCAP" @ MSG_LD_PRELOAD "PRELOAD" @ MSG_LD_PROFILE "PROFILE" @ MSG_LD_PROFILE_OUTPUT "PROFILE_OUTPUT" @ MSG_LD_SFCAP "SFCAP" @ MSG_LD_SIGNAL "SIGNAL" @ MSG_LD_TRACE_OBJS "TRACE_LOADED_OBJECTS" @ MSG_LD_TRACE_OBJS_E "TRACE_LOADED_OBJECTS_E" @ MSG_LD_TRACE_PTHS "TRACE_SEARCH_PATHS" @ MSG_LD_UNREF "UNREF" @ MSG_LD_UNUSED "UNUSED" @ MSG_LD_VERBOSE "VERBOSE" @ MSG_LD_DEFERRED "DEFERRED" @ MSG_LD_WARN "WARN" @ MSG_LD_BRAND_PREFIX "BRAND_" @ MSG_LC_ALL "ALL=" @ MSG_LC_MESSAGES "MESSAGES=" @ MSG_EMG_ENOMEM "internal: Not enough space" @ MSG_DBG_PID "%5.5d: " @ MSG_DBG_RESET "---------\n" @ MSG_DBG_UNDEF "debug: " @ MSG_DBG_LMID "%s: " @ MSG_DBG_THREAD "%d: " @ MSG_DBG_FILE "%s.%5.5d" @ MSG_LMID_BASE "BASE" @ MSG_LMID_LDSO "LDSO" @ MSG_LMID_ALT "ALT" @ MSG_LMID_FMT "%s%d" @ MSG_LMID_MAXED "ALTMAXEDOUT" /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright (c) 1988 AT&T * All Rights Reserved */ /* * Copyright (c) 2012, Joyent, Inc. All rights reserved. * Copyright 2022 Oxide Computer Company */ /* * Run time linker common setup. * * Called from _setup to get the process going at startup. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" #include "msg.h" extern int _end, _edata, _etext; extern void _init(void); extern int _brk_unlocked(void *); /* needed for _brk_unlocked() */ void *_nd = &_end; /* * Counters that are incremented every time an object is mapped/unmapped. * * Note that exec() will usually map 2 objects before we receive control, * but this can be 1 if ld.so.1 is executed directly. We count one of these * here, and add another as necessary in setup(). */ u_longlong_t cnt_map = 1; u_longlong_t cnt_unmap = 0; /* * Define for the executable's interpreter. * Usually it is ld.so.1, but for the first release of ICL binaries * it is libc.so.1. We keep this information so that we don't end * up mapping libc twice if it is the interpreter. */ static Interp _interp; /* * LD_PRELOAD objects. */ static int preload(const char *str, Rt_map *mlmp, Rt_map **clmp) { Alist *palp = NULL; char *objs, *ptr, *next; Word lmflags = lml_main.lm_flags; int lddstub; DBG_CALL(Dbg_util_nl(&lml_main, DBG_NL_STD)); if ((objs = strdup(str)) == NULL) return (0); /* * Determine if we've been called from lddstub. */ lddstub = (lmflags & LML_FLG_TRC_ENABLE) && (FLAGS1(*clmp) & FL1_RT_LDDSTUB); for (ptr = strtok_r(objs, MSG_ORIG(MSG_STR_DELIMIT), &next); ptr != NULL; ptr = strtok_r(NULL, MSG_ORIG(MSG_STR_DELIMIT), &next)) { Rt_map *nlmp = NULL; uint_t flags; DBG_CALL(Dbg_file_preload(&lml_main, ptr)); /* * Establish the flags for loading each object. If we're * called via lddstub, then the first preloaded object is the * object being inspected by ldd(1). This object should not be * marked as an interposer, as this object is intended to act * as the target object of the process. */ if (lddstub) flags = FLG_RT_PRELOAD; else flags = (FLG_RT_PRELOAD | FLG_RT_OBJINTPO); /* * If this a secure application, then preload errors are * reduced to warnings, as the errors are non-fatal. */ if (rtld_flags & RT_FL_SECURE) rtld_flags2 |= RT_FL2_FTL2WARN; if (expand_paths(*clmp, ptr, &palp, AL_CNT_NEEDED, PD_FLG_EXTLOAD, 0) != 0) nlmp = load_one(&lml_main, ALIST_OFF_DATA, palp, *clmp, MODE(mlmp), flags, 0, NULL); remove_alist(&palp, 0); if (rtld_flags & RT_FL_SECURE) rtld_flags2 &= ~RT_FL2_FTL2WARN; if (nlmp && (bind_one(*clmp, nlmp, BND_NEEDED) == 0)) nlmp = NULL; if (lddstub && nlmp) { lddstub = 0; /* * Fabricate a binding between the target shared object * and lddstub so that the target object isn't called * out from unused() processing. */ if (lmflags & (LML_FLG_TRC_UNREF | LML_FLG_TRC_UNUSED)) { if (bind_one(*clmp, nlmp, BND_REFER) == 0) nlmp = NULL; } /* * By identifying lddstub as the caller, several * confusing ldd() diagnostics get suppressed. These * diagnostics would reveal how the target shared object * was found from lddstub. Now that the real target is * loaded, identify the target as the caller so that all * ldd() diagnostics are enabled for subsequent objects. */ if (nlmp) *clmp = nlmp; } /* * If no error occurred with loading this object, indicate that * this link-map list contains an interposer. */ if (nlmp == NULL) { if ((lmflags & LML_FLG_TRC_ENABLE) || (rtld_flags & RT_FL_SECURE)) continue; else return (0); } if (flags & FLG_RT_OBJINTPO) lml_main.lm_flags |= LML_FLG_INTRPOSE; } free(palp); free(objs); return (1); } Rt_map * setup(char **envp, auxv_t *auxv, Word _flags, char *_platform, int _syspagsz, char *_rtldname, ulong_t ld_base, ulong_t interp_base, int fd, Phdr *phdr, char *execname, char **argv, uid_t uid, uid_t euid, gid_t gid, gid_t egid, int auxflags, uint_t *hwcap) { Rt_map *rlmp, *mlmp, *clmp, **tobj = NULL; Ehdr *ehdr; rtld_stat_t status; int features = 0, ldsoexec = 0; size_t eaddr, esize; char *str, *argvname; Word lmflags; mmapobj_result_t *mpp; Fdesc fdr = { 0 }, fdm = { 0 }; Rej_desc rej = { 0 }; APlist *ealp = NULL; /* * Now that ld.so has relocated itself, initialize our own 'environ' so * as to establish an address suitable for any libc requirements. */ _environ = (char **)((ulong_t)auxv - sizeof (char *)); _init(); _environ = envp; /* * Establish a base time. Total time diagnostics start from entering * ld.so.1 here, however the base time is reset each time the ld.so.1 * is re-entered. Note also, there will be a large time associated * with the first diagnostic from ld.so.1, as bootstrapping ld.so.1 * and establishing the liblddbg infrastructure takes some time. */ (void) gettimeofday(&DBG_TOTALTIME, NULL); DBG_DELTATIME = DBG_TOTALTIME; /* * Determine how ld.so.1 has been executed. */ if ((fd == -1) && (phdr == NULL)) { /* * If we received neither the AT_EXECFD nor the AT_PHDR aux * vector, ld.so.1 must have been invoked directly from the * command line. */ ldsoexec = 1; /* * AT_SUN_EXECNAME provides the most precise name, if it is * available, otherwise fall back to argv[0]. At this time, * there is no process name. */ if (execname) rtldname = execname; else if (argv[0]) rtldname = argv[0]; else rtldname = (char *)MSG_INTL(MSG_STR_UNKNOWN); } else { /* * Otherwise, we have a standard process. AT_SUN_EXECNAME * provides the most precise name, if it is available, * otherwise fall back to argv[0]. Provided the application * is already mapped, the process is the application, so * simplify the application name for use in any diagnostics. */ if (execname) argvname = execname; else if (argv[0]) argvname = execname = argv[0]; else argvname = execname = (char *)MSG_INTL(MSG_STR_UNKNOWN); if (fd == -1) { if ((str = strrchr(argvname, '/')) != NULL) procname = ++str; else procname = argvname; } /* * At this point, we don't know the runtime linkers full path * name. The _rtldname passed to us is the SONAME of the * runtime linker, which is typically /lib/ld.so.1 no matter * what the full path is. Use this for now, we'll reset the * runtime linkers name once the application is analyzed. */ if (_rtldname) { if ((str = strrchr(_rtldname, '/')) != NULL) rtldname = ++str; else rtldname = _rtldname; } else rtldname = (char *)MSG_INTL(MSG_STR_UNKNOWN); /* exec() brought in two objects for us. Count the second one */ cnt_map++; } /* * Initialize any global variables. */ at_flags = _flags; if ((org_scapset->sc_plat = _platform) != NULL) org_scapset->sc_platsz = strlen(_platform); if (org_scapset->sc_plat == NULL) platform_name(org_scapset); if (org_scapset->sc_mach == NULL) machine_name(org_scapset); /* * If pagesize is unspecified find its value. */ if ((syspagsz = _syspagsz) == 0) syspagsz = _sysconfig(_CONFIG_PAGESIZE); /* * Add the unused portion of the last data page to the free space list. * The page size must be set before doing this. Here, _end refers to * the end of the runtime linkers bss. Note that we do not use the * unused data pages from any included .so's to supplement this free * space as badly behaved .os's may corrupt this data space, and in so * doing ruin our data. */ eaddr = S_DROUND((size_t)&_end); esize = eaddr % syspagsz; if (esize) { esize = syspagsz - esize; addfree((void *)eaddr, esize); } /* * Establish initial link-map list flags, and link-map list alists. */ if (alist_append(&lml_main.lm_lists, NULL, sizeof (Lm_cntl), AL_CNT_LMLISTS) == NULL) return (0); lml_main.lm_flags |= LML_FLG_BASELM; lml_main.lm_lmid = LM_ID_BASE; lml_main.lm_lmidstr = (char *)MSG_ORIG(MSG_LMID_BASE); if (alist_append(&lml_rtld.lm_lists, NULL, sizeof (Lm_cntl), AL_CNT_LMLISTS) == NULL) return (0); lml_rtld.lm_flags |= (LML_FLG_RTLDLM | LML_FLG_HOLDLOCK); lml_rtld.lm_tflags |= LML_TFLG_NOAUDIT; lml_rtld.lm_lmid = LM_ID_LDSO; lml_rtld.lm_lmidstr = (char *)MSG_ORIG(MSG_LMID_LDSO); /* * Determine whether we have a secure executable. */ security(uid, euid, gid, egid, auxflags); /* * Make an initial pass of environment variables to pick off those * related to locale processing. At the same time, collect and save * any LD_XXXX variables for later processing. Note that this later * processing will be skipped if ld.so.1 is invoked from the command * line with -e LD_NOENVIRON. */ if (envp && (readenv_user((const char **)envp, &ealp) == 1)) return (0); /* * If ld.so.1 has been invoked directly, process its arguments. */ if (ldsoexec) { /* * Process any arguments that are specific to ld.so.1, and * reorganize the process stack to effectively remove ld.so.1 * from the stack. Reinitialize the environment pointer, as * this pointer may have been shifted after skipping ld.so.1's * arguments. */ if (rtld_getopt(argv, &envp, &auxv, &(lml_main.lm_flags), &(lml_main.lm_tflags)) == 1) { eprintf(&lml_main, ERR_NONE, MSG_INTL(MSG_USG_BADOPT)); return (0); } _environ = envp; /* * Open the object that ld.so.1 is to execute. */ argvname = execname = argv[0]; if ((fd = open(argvname, O_RDONLY)) == -1) { int err = errno; eprintf(&lml_main, ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), argvname, strerror(err)); return (0); } } /* * Having processed any ld.so.1 command line options, return to process * any LD_XXXX environment variables. */ if (ealp) { if (((rtld_flags & RT_FL_NOENVIRON) == 0) && (procenv_user(ealp, &(lml_main.lm_flags), &(lml_main.lm_tflags)) == 1)) return (0); free(ealp); } /* * Initialize a hardware capability descriptor for use in comparing * each loaded object. The aux vector must provide AF_SUN_HWCAPVERIFY, * as prior to this setting any hardware capabilities that were found * could not be relied upon. */ if (auxflags & AF_SUN_HWCAPVERIFY) { rtld_flags2 |= RT_FL2_HWCAP; org_scapset->sc_hw_1 = (Xword)hwcap[0]; org_scapset->sc_hw_2 = (Xword)hwcap[1]; org_scapset->sc_hw_3 = (Xword)hwcap[2]; } /* * Create a mapping descriptor for ld.so.1. We can determine our * two segments information from known symbols. */ if ((mpp = calloc(2, sizeof (mmapobj_result_t))) == NULL) return (0); mpp[0].mr_addr = (caddr_t)M_PTRUNC(ld_base); mpp[0].mr_msize = (caddr_t)&_etext - mpp[0].mr_addr; mpp[0].mr_fsize = mpp[0].mr_msize; mpp[0].mr_prot = (PROT_READ | PROT_EXEC); mpp[1].mr_addr = (caddr_t)M_PTRUNC((uintptr_t)&r_debug); mpp[1].mr_msize = (caddr_t)&_end - mpp[1].mr_addr; mpp[1].mr_fsize = (caddr_t)&_edata - mpp[1].mr_addr; mpp[1].mr_prot = (PROT_READ | PROT_WRITE | PROT_EXEC); if ((fdr.fd_nname = stravl_insert(_rtldname, 0, 0, 0)) == NULL) return (0); if ((rlmp = elf_new_lmp(&lml_rtld, ALIST_OFF_DATA, &fdr, (Addr)mpp->mr_addr, (size_t)((uintptr_t)eaddr - (uintptr_t)ld_base), NULL, NULL, NULL)) == NULL) return (0); MMAPS(rlmp) = mpp; MMAPCNT(rlmp) = 2; PADSTART(rlmp) = (ulong_t)mpp[0].mr_addr; PADIMLEN(rlmp) = (ulong_t)mpp[0].mr_addr + (ulong_t)mpp[1].mr_addr + (ulong_t)mpp[1].mr_msize; MODE(rlmp) |= (RTLD_LAZY | RTLD_NODELETE | RTLD_GLOBAL | RTLD_WORLD); FLAGS(rlmp) |= (FLG_RT_ANALYZED | FLG_RT_RELOCED | FLG_RT_INITDONE | FLG_RT_INITCLCT | FLG_RT_FINICLCT | FLG_RT_MODESET); /* * Initialize the runtime linkers information. */ interp = &_interp; interp->i_name = (char *)rtldname; interp->i_faddr = (caddr_t)ADDR(rlmp); ldso_plt_init(rlmp); /* * Map in the file, if exec has not already done so, or if the file * was passed as an argument to an explicit execution of ld.so.1 from * the command line. */ if (fd != -1) { /* * Map the file. Once the object is mapped we no longer need * the file descriptor. */ (void) rtld_fstat(fd, &status); fdm.fd_oname = argvname; fdm.fd_ftp = map_obj(&lml_main, &fdm, status.st_size, argvname, fd, &rej); (void) close(fd); if (fdm.fd_ftp == NULL) { Conv_reject_desc_buf_t rej_buf; eprintf(&lml_main, ERR_FATAL, MSG_INTL(err_reject[rej.rej_type]), argvname, conv_reject_desc(&rej, &rej_buf, M_MACH)); return (0); } /* * Finish processing the loading of the file. */ if ((fdm.fd_nname = stravl_insert(argvname, 0, 0, 0)) == NULL) return (0); fdm.fd_dev = status.st_dev; fdm.fd_ino = status.st_ino; if ((mlmp = load_file(&lml_main, ALIST_OFF_DATA, NULL, &fdm, NULL)) == NULL) return (0); /* * We now have a process name for error diagnostics. */ if ((str = strrchr(argvname, '/')) != NULL) procname = ++str; else procname = argvname; if (ldsoexec) { mmapobj_result_t *mpp = MMAPS(mlmp); uint_t mnum, mapnum = MMAPCNT(mlmp); void *brkbase = NULL; /* * Since ld.so.1 was the primary executed object - the * brk() base has not yet been initialized, we need to * initialize it. For an executable, initialize it to * the end of the object. For a shared object (ET_DYN) * initialize it to the first page in memory. */ for (mnum = 0; mnum < mapnum; mnum++, mpp++) brkbase = mpp->mr_addr + mpp->mr_msize; if (brkbase == NULL) brkbase = (void *)syspagsz; if (_brk_unlocked(brkbase) == -1) { int err = errno; eprintf(&lml_main, ERR_FATAL, MSG_INTL(MSG_SYS_BRK), argvname, strerror(err)); return (0); } } } else { if (phdr != NULL) { Phdr *pptr; Off i_offset = 0; Addr base = 0; ulong_t phsize; mmapobj_result_t *mpp, *fmpp, *hmpp = NULL; uint_t mapnum = 0; int i; size_t msize; /* * Using the executables phdr address determine the base * address of the input file. NOTE, this assumes the * program headers and elf header are part of the same * mapped segment. Although this has held for many * years now, it might be more flexible if the kernel * gave use the ELF headers start address, rather than * the Program headers. * * Determine from the ELF header if we're been called * from a shared object or dynamic executable. If the * latter, then any addresses within the object are used * as is. Addresses within shared objects must be added * to the process's base address. */ ehdr = (Ehdr *)((Addr)phdr - phdr->p_offset); phsize = ehdr->e_phentsize; if (ehdr->e_type == ET_DYN) base = (Addr)ehdr; /* * Allocate a mapping array to retain mapped segment * information. */ if ((fmpp = mpp = calloc(ehdr->e_phnum, sizeof (mmapobj_result_t))) == NULL) return (0); /* * Extract the needed information from the segment * headers. */ for (i = 0, pptr = phdr; i < ehdr->e_phnum; i++) { if (pptr->p_type == PT_INTERP) { i_offset = pptr->p_offset; interp->i_faddr = (caddr_t)interp_base; } if ((pptr->p_type == PT_LOAD) && (pptr->p_filesz || pptr->p_memsz)) { int perm = (PROT_READ | PROT_EXEC); size_t off; if (i_offset && pptr->p_filesz && (i_offset >= pptr->p_offset) && (i_offset <= (pptr->p_memsz + pptr->p_offset))) { interp->i_name = (char *) pptr->p_vaddr + i_offset - pptr->p_offset + base; i_offset = 0; } if (pptr->p_flags & PF_W) perm |= PROT_WRITE; /* * Retain segments mapping info. Round * each segment to a page boundary, as * this insures addresses are suitable * for mprotect() if required. */ off = pptr->p_vaddr + base; if (hmpp == NULL) { hmpp = mpp; mpp->mr_addr = (caddr_t)ehdr; } else mpp->mr_addr = (caddr_t)off; off -= (size_t)(uintptr_t)mpp->mr_addr; mpp->mr_msize = pptr->p_memsz + off; mpp->mr_fsize = pptr->p_filesz + off; mpp->mr_prot = perm; mpp++, mapnum++; } pptr = (Phdr *)((ulong_t)pptr + phsize); } mpp--; msize = (size_t)(mpp->mr_addr + mpp->mr_msize) - S_ALIGN((size_t)fmpp->mr_addr, syspagsz); if ((fdm.fd_nname = stravl_insert(execname, 0, 0, 0)) == NULL) return (0); if ((mlmp = elf_new_lmp(&lml_main, ALIST_OFF_DATA, &fdm, (Addr)hmpp->mr_addr, msize, NULL, NULL, NULL)) == NULL) return (0); MMAPS(mlmp) = fmpp; MMAPCNT(mlmp) = mapnum; PADSTART(mlmp) = (ulong_t)fmpp->mr_addr; PADIMLEN(mlmp) = (ulong_t)fmpp->mr_addr + (ulong_t)mpp->mr_addr + (ulong_t)mpp->mr_msize; } } /* * Establish the interpretors name as that defined within the initial * object (executable). This provides for ORIGIN processing of ld.so.1 * dependencies. Note, the NAME() of the object remains that which was * passed to us as the SONAME on execution. */ if (ldsoexec == 0) { size_t len = strlen(interp->i_name); if (expand(&interp->i_name, &len, 0, 0, (PD_TKN_ISALIST | PD_TKN_CAP), rlmp) & PD_TKN_RESOLVED) fdr.fd_flags |= FLG_FD_RESOLVED; } fdr.fd_pname = interp->i_name; (void) fullpath(rlmp, &fdr); /* * The runtime linker acts as a filtee for various dl*() functions that * are defined in libc (and libdl). Make sure this standard name for * the runtime linker is also registered in the FullPathNode AVL tree. */ (void) fpavl_insert(&lml_rtld, rlmp, _rtldname, 0); /* * Having established the true runtime linkers name, simplify the name * for error diagnostics. */ if ((str = strrchr(PATHNAME(rlmp), '/')) != NULL) rtldname = ++str; else rtldname = PATHNAME(rlmp); /* * Expand the fullpath name of the application. This typically occurs * as a part of loading an object, but as the kernel probably mapped * it in, complete this processing now. */ (void) fullpath(mlmp, 0); /* * Some troublesome programs will change the value of argv[0]. Dupping * the process string protects us, and insures the string is left in * any core files. */ if ((str = (char *)strdup(procname)) == NULL) return (0); procname = str; FLAGS(mlmp) |= (FLG_RT_ISMAIN | FLG_RT_MODESET); FLAGS1(mlmp) |= FL1_RT_USED; /* * It's the responsibility of MAIN(crt0) to call it's _init and _fini * section, therefore null out any INIT/FINI so that this object isn't * collected during tsort processing. And, if the application has no * initarray or finiarray we can economize on establishing bindings. */ INIT(mlmp) = FINI(mlmp) = NULL; if ((INITARRAY(mlmp) == NULL) && (FINIARRAY(mlmp) == NULL)) FLAGS1(mlmp) |= FL1_RT_NOINIFIN; /* * Identify lddstub if necessary. */ if (lml_main.lm_flags & LML_FLG_TRC_LDDSTUB) FLAGS1(mlmp) |= FL1_RT_LDDSTUB; /* * Retain our argument information for use in dlinfo. */ argsinfo.dla_argv = argv--; argsinfo.dla_argc = (long)*argv; argsinfo.dla_envp = envp; argsinfo.dla_auxv = auxv; (void) enter(0); /* * Add our two main link-maps to the dynlm_list */ if (aplist_append(&dynlm_list, &lml_main, AL_CNT_DYNLIST) == NULL) return (0); if (aplist_append(&dynlm_list, &lml_rtld, AL_CNT_DYNLIST) == NULL) return (0); /* * Reset the link-map counts for both lists. The init count is used to * track how many objects have pending init sections, this gets incre- * mented each time an object is relocated. Since ld.so.1 relocates * itself, it's init count will remain zero. * The object count is used to track how many objects have pending fini * sections, as ld.so.1 handles its own fini we can zero its count. */ lml_main.lm_obj = 1; lml_rtld.lm_obj = 0; /* * Initialize debugger information structure. Some parts of this * structure were initialized statically. */ r_debug.rtd_rdebug.r_map = (Link_map *)lml_main.lm_head; r_debug.rtd_rdebug.r_ldsomap = (Link_map *)lml_rtld.lm_head; r_debug.rtd_rdebug.r_ldbase = r_debug.rtd_rdebug.r_ldsomap->l_addr; r_debug.rtd_dynlmlst = &dynlm_list; /* * Determine the dev/inode information for the executable to complete * load_so() checking for those who might call dlopen(3c) on the * executable . */ if (rtld_stat(PATHNAME(mlmp), &status) == 0) { STDEV(mlmp) = status.st_dev; STINO(mlmp) = status.st_ino; } /* * Initialize any configuration information. */ if (!(rtld_flags & RT_FL_NOCFG)) { if ((features = elf_config(mlmp)) == -1) return (0); } #if defined(_ELF64) /* * If this is a 64-bit process, determine whether this process has * restricted the process address space to 32-bits. Any dependencies * that are restricted to a 32-bit address space can only be loaded if * the executable has established this requirement. */ if (CAPSET(mlmp).sc_sf_1 & SF1_SUNW_ADDR32) rtld_flags2 |= RT_FL2_ADDR32; #endif /* * Establish any alternative capabilities, and validate this object * if it defines it's own capabilities information. */ if (cap_alternative() == 0) return (0); if (cap_check_lmp(mlmp, &rej) == 0) { if (lml_main.lm_flags & LML_FLG_TRC_ENABLE) { /* LINTED */ (void) printf(MSG_INTL(ldd_warn[rej.rej_type]), NAME(mlmp), rej.rej_str); } else { /* LINTED */ eprintf(&lml_main, ERR_FATAL, MSG_INTL(err_reject[rej.rej_type]), NAME(mlmp), rej.rej_str); return (0); } } /* * Establish the modes of the initial object. These modes are * propagated to any preloaded objects and explicit shared library * dependencies. * * If we're generating a configuration file using crle(1), remove * any RTLD_NOW use, as we don't want to trigger any relocation proc- * essing during crle(1)'s first past (this would just be unnecessary * overhead). Any filters are explicitly loaded, and thus RTLD_NOW is * not required to trigger filter loading. * * Note, RTLD_NOW may have been established during analysis of the * application had the application been built -z now. */ MODE(mlmp) |= (RTLD_NODELETE | RTLD_GLOBAL | RTLD_WORLD); if (rtld_flags & RT_FL_CONFGEN) { MODE(mlmp) |= RTLD_CONFGEN; MODE(mlmp) &= ~RTLD_NOW; rtld_flags2 &= ~RT_FL2_BINDNOW; } if ((MODE(mlmp) & RTLD_NOW) == 0) { if (rtld_flags2 & RT_FL2_BINDNOW) MODE(mlmp) |= RTLD_NOW; else MODE(mlmp) |= RTLD_LAZY; } /* * If debugging was requested initialize things now that any cache has * been established. A user can specify LD_DEBUG=help to discover the * list of debugging tokens available without running the application. * However, don't allow this setting from a configuration file. * * Note, to prevent recursion issues caused by loading and binding the * debugging libraries themselves, a local debugging descriptor is * initialized. Once the debugging setup has completed, this local * descriptor is copied to the global descriptor which effectively * enables diagnostic output. * * Ignore any debugging request if we're being monitored by a process * that expects the old getpid() initialization handshake. */ if ((rpl_debug || prm_debug) && ((rtld_flags & RT_FL_DEBUGGER) == 0)) { Dbg_desc _dbg_desc = {0}; struct timeval total = DBG_TOTALTIME; struct timeval delta = DBG_DELTATIME; if (rpl_debug) { if (dbg_setup(rpl_debug, &_dbg_desc) == 0) return (0); if (_dbg_desc.d_extra & DBG_E_HELP_EXIT) rtldexit(&lml_main, 0); } if (prm_debug) (void) dbg_setup(prm_debug, &_dbg_desc); *dbg_desc = _dbg_desc; DBG_TOTALTIME = total; DBG_DELTATIME = delta; } /* * Now that debugging is enabled generate any diagnostics from any * previous events. */ if (DBG_ENABLED) { DBG_CALL(Dbg_cap_val(&lml_main, org_scapset, alt_scapset, M_MACH)); DBG_CALL(Dbg_file_config_dis(&lml_main, config->c_name, features)); DBG_CALL(Dbg_file_ldso(rlmp, envp, auxv, LIST(rlmp)->lm_lmidstr, ALIST_OFF_DATA)); if (THIS_IS_ELF(mlmp)) { DBG_CALL(Dbg_file_elf(&lml_main, PATHNAME(mlmp), ADDR(mlmp), MSIZE(mlmp), LIST(mlmp)->lm_lmidstr, ALIST_OFF_DATA)); } } /* * Enable auditing. */ if (rpl_audit || prm_audit || profile_lib) { int ndx; const char *aud[3]; aud[0] = rpl_audit; aud[1] = prm_audit; aud[2] = profile_lib; /* * Any global auditing (set using LD_AUDIT or LD_PROFILE) that * can't be established is non-fatal. */ if ((auditors = calloc(1, sizeof (Audit_desc))) == NULL) return (0); for (ndx = 0; ndx < 3; ndx++) { if (aud[ndx]) { if ((auditors->ad_name = strdup(aud[ndx])) == NULL) return (0); rtld_flags2 |= RT_FL2_FTL2WARN; (void) audit_setup(mlmp, auditors, PD_FLG_EXTLOAD, NULL); rtld_flags2 &= ~RT_FL2_FTL2WARN; } } lml_main.lm_tflags |= auditors->ad_flags; } if (AUDITORS(mlmp)) { /* * Any object required auditing (set with a DT_DEPAUDIT dynamic * entry) that can't be established is fatal. */ if (FLAGS1(mlmp) & FL1_RT_GLOBAUD) { /* * If this object requires global auditing, use the * local auditing information to set the global * auditing descriptor. The effect is that a * DT_DEPAUDIT act as an LD_AUDIT. */ if ((auditors == NULL) && ((auditors = calloc(1, sizeof (Audit_desc))) == NULL)) return (0); auditors->ad_name = AUDITORS(mlmp)->ad_name; if (audit_setup(mlmp, auditors, 0, NULL) == 0) return (0); lml_main.lm_tflags |= auditors->ad_flags; /* * Clear the local auditor information. */ free((void *) AUDITORS(mlmp)); AUDITORS(mlmp) = NULL; } else { /* * Establish any local auditing. */ if (audit_setup(mlmp, AUDITORS(mlmp), 0, NULL) == 0) return (0); AFLAGS(mlmp) |= AUDITORS(mlmp)->ad_flags; lml_main.lm_flags |= LML_FLG_LOCAUDIT; } } /* * Explicitly add the initial object and ld.so.1 to those objects being * audited. Note, although the ld.so.1 link-map isn't auditable, * establish a cookie for ld.so.1 as this may be bound to via the * dl*() family. */ if ((lml_main.lm_tflags | AFLAGS(mlmp)) & LML_TFLG_AUD_MASK) { if (((audit_objopen(mlmp, mlmp) == 0) || (audit_objopen(mlmp, rlmp) == 0)) && (AFLAGS(mlmp) & LML_TFLG_AUD_MASK)) return (0); } /* * Map in any preloadable shared objects. Establish the caller as the * head of the main link-map list. In the case of being exercised from * lddstub, the caller gets reassigned to the first target shared object * so as to provide intuitive diagnostics from ldd(). * * Note, it is valid to preload a 4.x shared object with a 5.0 * executable (or visa-versa), as this functionality is required by * ldd(1). */ clmp = mlmp; if (rpl_preload && (preload(rpl_preload, mlmp, &clmp) == 0)) return (0); if (prm_preload && (preload(prm_preload, mlmp, &clmp) == 0)) return (0); /* * Load all dependent (needed) objects. */ if (analyze_lmc(&lml_main, ALIST_OFF_DATA, mlmp, mlmp, NULL) == NULL) return (0); /* * Relocate all the dependencies we've just added. * * If this process has been established via crle(1), the environment * variable LD_CONFGEN will have been set. crle(1) may create this * process twice. The first time crle only needs to gather dependency * information. The second time, is to dldump() the images. * * If we're only gathering dependencies, relocation is unnecessary. * As crle(1) may be building an arbitrary family of objects, they may * not fully relocate either. Hence the relocation phase is not carried * out now, but will be called by crle(1) once all objects have been * loaded. */ if ((rtld_flags & RT_FL_CONFGEN) == 0) { DBG_CALL(Dbg_util_nl(&lml_main, DBG_NL_STD)); if (relocate_lmc(&lml_main, ALIST_OFF_DATA, mlmp, mlmp, NULL) == 0) return (0); /* * Inform the debuggers that basic process initialization is * complete, and that the state of ld.so.1 (link-map lists, * etc.) is stable. This handshake enables the debugger to * initialize themselves, and consequently allows the user to * set break points in .init code. * * Most new debuggers use librtld_db to monitor activity events. * Older debuggers indicated their presence by setting the * DT_DEBUG entry in the dynamic executable (see elf_new_lm()). * In this case, getpid() is called so that the debugger can * catch the system call. This old mechanism has some * restrictions, as getpid() should not be called prior to * basic process initialization being completed. This * restriction has become increasingly difficult to maintain, * as the use of auditors, LD_DEBUG, and the initialization * handshake with libc can result in "premature" getpid() * calls. The use of this getpid() handshake is expected to * disappear at some point in the future, and there is intent * to work towards that goal. */ rd_event(&lml_main, RD_DLACTIVITY, RT_CONSISTENT); rd_event(&lml_rtld, RD_DLACTIVITY, RT_CONSISTENT); if (rtld_flags & RT_FL_DEBUGGER) { r_debug.rtd_rdebug.r_flags |= RD_FL_ODBG; (void) getpid(); } } /* * Indicate preinit activity, and call any auditing routines. These * routines are called before initializing any threads via libc, or * before collecting the complete set of .inits on the primary link-map. * Although most libc interfaces are encapsulated in local routines * within libc, they have been known to escape (ie. call a .plt). As * the appcert auditor uses preinit as a trigger to establish some * external interfaces to the main link-maps libc, we need to activate * this trigger before exercising any code within libc. Additionally, * I wouldn't put it past an auditor to add additional objects to the * primary link-map. Hence, we collect .inits after the audit call. */ rd_event(&lml_main, RD_PREINIT, 0); if (aud_activity || ((lml_main.lm_tflags | AFLAGS(mlmp)) & LML_TFLG_AUD_ACTIVITY)) audit_activity(mlmp, LA_ACT_CONSISTENT); if (aud_preinit || ((lml_main.lm_tflags | AFLAGS(mlmp)) & LML_TFLG_AUD_PREINIT)) audit_preinit(mlmp); /* * If we're creating initial configuration information, we're done * now that the auditing step has been called. */ if (rtld_flags & RT_FL_CONFGEN) { leave(LIST(mlmp), 0); return (mlmp); } /* * Sort the .init sections of all objects we've added. If we're * tracing we only need to execute this under ldd(1) with the -i or -u * options. */ lmflags = lml_main.lm_flags; if (((lmflags & LML_FLG_TRC_ENABLE) == 0) || (lmflags & (LML_FLG_TRC_INIT | LML_FLG_TRC_UNREF))) { if ((tobj = tsort(mlmp, LIST(mlmp)->lm_init, RT_SORT_REV)) == (Rt_map **)S_ERROR) return (0); } /* * If we are tracing we're done. This is the one legitimate use of a * direct call to rtldexit() rather than return, as we don't want to * return and jump to the application. */ if (lmflags & LML_FLG_TRC_ENABLE) { unused(&lml_main); rtldexit(&lml_main, 0); } /* * Check if this instance of the linker should have a primary link * map. This flag allows multiple copies of the -same- -version- * of the linker (and libc) to run in the same address space. * * Without this flag we only support one copy of the linker in a * process because by default the linker will always try to * initialize at one primary link map The copy of libc which is * initialized on a primary link map will initialize global TLS * data which can be shared with other copies of libc in the * process. The problem is that if there is more than one copy * of the linker, only one copy should link libc onto a primary * link map, otherwise libc will attempt to re-initialize global * TLS data. So when a copy of the linker is loaded with this * flag set, it will not initialize any primary link maps since * presumably another copy of the linker will do this. * * Note that this flag only allows multiple copies of the -same- * -version- of the linker (and libc) to coexist. This approach * will not work if we are trying to load different versions of * the linker and libc into the same process. The reason for * this is that the format of the global TLS data may not be * the same for different versions of libc. In this case each * different version of libc must have it's own primary link map * and be able to maintain it's own TLS data. The only way this * can be done is by carefully managing TLS pointers on transitions * between code associated with each of the different linkers. * Note that this is actually what is done for processes in lx * branded zones. Although in the lx branded zone case, the * other linker and libc are actually gld and glibc. But the * same general TLS management mechanism used by the lx brand * would apply to any attempts to run multiple versions of the * solaris linker and libc in a single process. */ if (auxflags & AF_SUN_NOPLM) rtld_flags2 |= RT_FL2_NOPLM; /* * Establish any static TLS for this primary link-map. Note, regardless * of whether TLS is available, an initial handshake occurs with libc to * indicate we're processing the primary link-map. Having identified * the primary link-map, initialize threads. */ if (rt_get_extern(&lml_main, mlmp) == 0) return (0); if ((rtld_flags2 & RT_FL2_NOPLM) == 0) { if (tls_statmod(&lml_main, mlmp) == 0) return (0); rt_thr_init(&lml_main); rtld_flags2 |= RT_FL2_PLMSETUP; } else { rt_thr_init(&lml_main); } /* * Fire all dependencies .init sections. Identify any unused * dependencies, and leave the runtime linker - effectively calling * the dynamic executables entry point. */ call_array(PREINITARRAY(mlmp), (uint_t)PREINITARRAYSZ(mlmp), mlmp, SHT_PREINIT_ARRAY); if (tobj) call_init(tobj, DBG_INIT_SORT); rd_event(&lml_main, RD_POSTINIT, 0); unused(&lml_main); DBG_CALL(Dbg_util_call_main(mlmp)); rtld_flags |= (RT_FL_OPERATION | RT_FL_APPLIC); leave(LIST(mlmp), 0); return (mlmp); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include <_rtld.h> #include <_elf.h> #include #include #define TLSBLOCKCNT 16 /* number of blocks of tmi_bits to allocate */ /* at a time. */ typedef struct { uint_t *tmi_bits; ulong_t tmi_lowfree; ulong_t tmi_cnt; } Tlsmodid; static Tlsmodid tmid = {0, 0, 0}; static ulong_t tls_getmodid() { ulong_t ndx, cnt; if (tmid.tmi_bits == 0) { if ((tmid.tmi_bits = calloc(TLSBLOCKCNT, sizeof (uint_t))) == NULL) return ((ulong_t)-1); tmid.tmi_bits[0] = 1; tmid.tmi_lowfree = 1; tmid.tmi_cnt = TLSBLOCKCNT; return (0); } for (cnt = tmid.tmi_lowfree / (sizeof (uint_t) * 8); cnt < tmid.tmi_cnt; cnt++) { uint_t bits; /* * If all bits are assigned - move on. */ if ((tmid.tmi_bits[cnt] ^ ~((uint_t)0)) == 0) continue; for (ndx = 0, bits = 1; bits; bits = bits << 1, ndx++) { if ((tmid.tmi_bits[cnt] & bits) == 0) { tmid.tmi_bits[cnt] |= bits; ndx = (cnt * (sizeof (uint_t)) * 8) + ndx; tmid.tmi_lowfree = ndx + 1; return (ndx); } } } /* * All bits taken - must allocate a new block */ if ((tmid.tmi_bits = realloc(tmid.tmi_bits, ((tmid.tmi_cnt * sizeof (uint_t)) + (TLSBLOCKCNT * sizeof (uint_t))))) == NULL) return ((ulong_t)-1); /* * Clear out the tail of the new allocation. */ bzero(&(tmid.tmi_bits[tmid.tmi_cnt]), TLSBLOCKCNT * sizeof (uint_t)); tmid.tmi_bits[tmid.tmi_cnt] = 1; ndx = (tmid.tmi_cnt * sizeof (uint_t)) * 8; tmid.tmi_lowfree = ndx + 1; tmid.tmi_cnt += TLSBLOCKCNT; return (ndx); } void tls_freemodid(ulong_t modid) { ulong_t i; uint_t j; i = modid / (sizeof (uint_t) * 8); /* LINTED */ j = modid % (sizeof (uint_t) * 8); j = ~(1 << j); tmid.tmi_bits[i] &= j; if (modid < tmid.tmi_lowfree) tmid.tmi_lowfree = modid; } void tls_modaddrem(Rt_map *lmp, uint_t flag) { Lm_list *lml = LIST(lmp); TLS_modinfo tmi; Phdr *tlsphdr; int (*fptr)(TLS_modinfo *); if (flag & TM_FLG_MODADD) { fptr = lml->lm_lcs[CI_TLS_MODADD].lc_un.lc_func; } else if (FLAGS1(lmp) & FL1_RT_TLSADD) { fptr = lml->lm_lcs[CI_TLS_MODREM].lc_un.lc_func; } else { return; } tlsphdr = PTTLS(lmp); bzero(&tmi, sizeof (tmi)); tmi.tm_modname = PATHNAME(lmp); tmi.tm_modid = TLSMODID(lmp); tmi.tm_tlsblock = (void *)(tlsphdr->p_vaddr); if (!(FLAGS(lmp) & FLG_RT_FIXED)) tmi.tm_tlsblock = (void *)((uintptr_t)tmi.tm_tlsblock + ADDR(lmp)); tmi.tm_filesz = tlsphdr->p_filesz; tmi.tm_memsz = tlsphdr->p_memsz; tmi.tm_flags = 0; tmi.tm_stattlsoffset = 0; DBG_CALL(Dbg_tls_modactivity(LIST(lmp), &tmi, flag)); (void) (*fptr)(&tmi); /* * Tag that this link-map has registered its TLS, and, if this object * is being removed, free up the module id. */ FLAGS1(lmp) |= FL1_RT_TLSADD; if (flag & TM_FLG_MODREM) tls_freemodid(TLSMODID(lmp)); } static ulong_t tls_static_size = 0; /* static TLS buffer size */ static ulong_t tls_static_resv = 512; /* (extra) static TLS reservation */ /* * Track any static TLS use, retain the TLS header, and assign a TLS module * identifier. */ int tls_assign(Lm_list *lml, Rt_map *lmp, Phdr *phdr) { ulong_t memsz = S_ROUND(phdr->p_memsz, M_TLSSTATALIGN); ulong_t filesz = phdr->p_filesz; ulong_t resv = tls_static_resv; /* * If this object explicitly references static TLS, then there are some * limitations. */ if (FLAGS1(lmp) & FL1_RT_TLSSTAT) { /* * Static TLS is only available to objects on the primary * link-map list. */ if (((lml->lm_flags & LML_FLG_BASELM) == 0) || ((rtld_flags2 & RT_FL2_NOPLM) != 0)) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_TLS_STATBASE), NAME(lmp)); return (0); } /* * All TLS blocks that are processed before thread * initialization, are registered with libc. This * initialization is carried out through a handshake with libc * prior to executing any user code (ie. before the first .init * sections are called). As part of this initialization, a * small backup TLS reservation is added (tls_static_resv). * Only explicit static TLS references that can be satisfied by * this TLS backup reservation can be satisfied. */ if (rtld_flags2 & RT_FL2_PLMSETUP) { /* * Initialized static TLS can not be satisfied from the * TLS backup reservation. */ if (filesz) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_TLS_STATINIT), NAME(lmp)); return (0); } /* * Make sure the backup reservation is sufficient. */ if (memsz > tls_static_resv) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_TLS_STATSIZE), NAME(lmp), EC_XWORD(memsz), EC_XWORD(tls_static_resv)); return (0); } tls_static_resv -= memsz; } } /* * If we haven't yet initialized threads, or this static reservation can * be satisfied from the TLS backup reservation, determine the total * static TLS size, and assign this object a static TLS offset. */ if (((rtld_flags2 & RT_FL2_PLMSETUP) == 0) || (FLAGS1(lmp) & FL1_RT_TLSSTAT)) { tls_static_size += memsz; TLSSTATOFF(lmp) = tls_static_size; } /* * Retain the PT_TLS header, obtain a new module identifier, and * indicate that this link-map list contains a new TLS object. */ PTTLS(lmp) = phdr; TLSMODID(lmp) = tls_getmodid(); /* * Now that we have a TLS module id, generate any static TLS reservation * diagnostic. */ if (resv != tls_static_resv) DBG_CALL(Dbg_tls_static_resv(lmp, memsz, tls_static_resv)); return (++lml->lm_tls); } int tls_statmod(Lm_list *lml, Rt_map *lmp) { uint_t tlsmodndx, tlsmodcnt = lml->lm_tls; TLS_modinfo **tlsmodlist, *tlsbuflist; Phdr *tlsphdr; int (*fptr)(TLS_modinfo **, ulong_t); fptr = lml->lm_lcs[CI_TLS_STATMOD].lc_un.lc_func; /* * Allocate a buffer to report the TLS modules, the buffer consists of: * * TLS_modinfo * ptrs[tlsmodcnt + 1] * TLS_modinfo bufs[tlsmodcnt] * * The ptrs are initialized to the bufs - except the last one which * null terminates the array. * * Note, even if no TLS has yet been observed, we still supply a * TLS buffer with a single null entry. This allows us to initialize * the backup TLS reservation. */ if ((tlsmodlist = calloc(1, (sizeof (TLS_modinfo *) * (tlsmodcnt + 1)) + (sizeof (TLS_modinfo) * tlsmodcnt))) == NULL) return (0); lml->lm_tls = 0; /* * If we don't have any TLS modules - report that and return. */ if (tlsmodcnt == 0) { if (fptr != NULL) (void) (*fptr)(tlsmodlist, tls_static_resv); DBG_CALL(Dbg_tls_static_block(&lml_main, 0, 0, tls_static_resv)); return (1); } /* * Initialize the TLS buffer. */ tlsbuflist = (TLS_modinfo *)((uintptr_t)tlsmodlist + ((tlsmodcnt + 1) * sizeof (TLS_modinfo *))); for (tlsmodndx = 0; tlsmodndx < tlsmodcnt; tlsmodndx++) tlsmodlist[tlsmodndx] = &tlsbuflist[tlsmodndx]; /* * Account for the initial dtv ptr in the TLSSIZE calculation. */ tlsmodndx = 0; for (lmp = lml->lm_head; lmp; lmp = NEXT_RT_MAP(lmp)) { if (THIS_IS_NOT_ELF(lmp) || (PTTLS(lmp) == 0) || (PTTLS(lmp)->p_memsz == 0)) continue; tlsphdr = PTTLS(lmp); tlsmodlist[tlsmodndx]->tm_modname = PATHNAME(lmp); tlsmodlist[tlsmodndx]->tm_modid = TLSMODID(lmp); tlsmodlist[tlsmodndx]->tm_tlsblock = (void *)(tlsphdr->p_vaddr); if (!(FLAGS(lmp) & FLG_RT_FIXED)) { tlsmodlist[tlsmodndx]->tm_tlsblock = (void *) ((uintptr_t)tlsmodlist[tlsmodndx]->tm_tlsblock + ADDR(lmp)); } tlsmodlist[tlsmodndx]->tm_filesz = tlsphdr->p_filesz; tlsmodlist[tlsmodndx]->tm_memsz = tlsphdr->p_memsz; tlsmodlist[tlsmodndx]->tm_flags = TM_FLG_STATICTLS; tlsmodlist[tlsmodndx]->tm_stattlsoffset = TLSSTATOFF(lmp); tlsmodndx++; } DBG_CALL(Dbg_tls_static_block(&lml_main, (void *)tlsmodlist, tls_static_size, tls_static_resv)); (void) (*fptr)(tlsmodlist, (tls_static_size + tls_static_resv)); /* * We're done with the list - clean it up. */ free(tlsmodlist); return (1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Utilities to handle shared object dependency graph. * * The algorithms used in this file are taken from the following book: * Algorithms in C * Robert Sedgewick * Addison-Wesley Publishing company * ISBN 0-201-51425-7 * From the following chapters: * Chapter 29 Elementary Graph Algorithms * Chapter 32 Directed Graph */ #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "msg.h" /* * Structure for maintaining sorting state. */ typedef struct { Rt_map **s_lmpa; /* link-map[] (returned to caller) */ Rt_map *s_lmp; /* originating link-map */ Rt_map **s_stack; /* strongly connected component stack */ APlist *s_scc; /* cyclic list */ APlist *s_queue; /* depth queue for cyclic components */ int s_sndx; /* present stack index */ int s_lndx; /* present link-map index */ int s_num; /* number of objects to sort */ int s_initfirst; /* no. of INITFIRST entries */ } Sort; #define AL_CNT_SCC 10 /* * qsort(3c) comparison function. */ static int compare(const void *lmpp1, const void *lmpp2) { Rt_map *lmp1 = *((Rt_map **)lmpp1); Rt_map *lmp2 = *((Rt_map **)lmpp2); if (IDX(lmp1) > IDX(lmp2)) return (-1); if (IDX(lmp1) < IDX(lmp2)) return (1); return (0); } /* * This routine is called when a cyclic dependency is detected between strongly * connected components. The nodes within the cycle are reverse breadth-first * sorted. */ static int sort_scc(Sort *sort, int fndx, int flag) { static const char *tfmt = 0, *ffmt; static int cnt = 1; int ndx; Rt_map *lmp; Lm_list *lml = LIST(sort->s_lmp); Word lmflags = lml->lm_flags; Word init, unref; /* * If this is the first cyclic dependency traverse the new objects that * have been added to the link-map list and for each object establish * a unique depth index. We build this dynamically as we have no idea * of the number of objects that will be inspected (logic matches that * used by dlsym() to traverse lazy dependencies). */ if (sort->s_queue == NULL) { Aliste idx1; Rt_map *lmp, *lmp2; lmp = sort->s_lmp; ndx = 1; if (aplist_append(&sort->s_queue, lmp, sort->s_num) == NULL) return (0); IDX(lmp) = ndx++; for (APLIST_TRAVERSE(sort->s_queue, idx1, lmp2)) { Bnd_desc *bdp; Aliste idx2; for (APLIST_TRAVERSE(DEPENDS(lmp2), idx2, bdp)) { Rt_map *lmp3 = bdp->b_depend; if (IDX(lmp3) || (LIST(lmp3) != lml)) continue; /* * If we're .init processing and this depend- * encies .init has been called, skip it. */ if ((flag & RT_SORT_REV) && (FLAGS(lmp3) & FLG_RT_INITCALL)) continue; if (aplist_append(&sort->s_queue, lmp3, sort->s_num) == NULL) return (0); IDX(lmp3) = ndx++; } } } /* * Sort the cyclics. */ qsort(&(sort->s_lmpa[fndx]), sort->s_lndx - fndx, sizeof (Rt_map *), compare); /* * Under ldd -i, or debugging, print this cycle. Under ldd -i/-U assign * each object a group identifier so that cyclic dependent callers can * be better traced (see trace_sort()), or analyzed for non-use. */ init = lmflags & LML_FLG_TRC_INIT; unref = lmflags & LML_FLG_TRC_UNREF; if ((init == 0) && (unref == 0) && (DBG_ENABLED == 0)) return (1); if (init) { if (tfmt == 0) { tfmt = MSG_INTL(MSG_LDD_INIT_FMT_01); ffmt = MSG_ORIG(MSG_LDD_INIT_FMT_FILE); } (void) printf(tfmt, cnt); } DBG_CALL(Dbg_util_scc_title(lml, (flag & RT_SORT_REV))); /* * Identify this cyclic group, and under ldd -i print the cycle in the * order its components will be run. */ if (flag & RT_SORT_REV) { for (ndx = fndx; ndx < sort->s_lndx; ndx++) { lmp = sort->s_lmpa[ndx]; CYCGROUP(lmp) = cnt; if (init) (void) printf(ffmt, NAME(lmp)); DBG_CALL(Dbg_util_scc_entry(lmp, ndx)); } cnt++; } else if (DBG_ENABLED) { for (ndx = sort->s_lndx - 1; ndx >= fndx; ndx--) { lmp = sort->s_lmpa[ndx]; DBG_CALL(Dbg_util_scc_entry(lmp, ndx)); } } /* * If we're looking for unused dependencies determine if any of these * cyclic components are referenced from outside of the cycle. */ if (unref || DBG_ENABLED) { for (ndx = fndx; ndx < sort->s_lndx; ndx++) { Bnd_desc *bdp; Aliste idx; lmp = sort->s_lmpa[ndx]; /* * If this object has a handle then it can be in use by * anyone. */ if (HANDLES(lmp)) return (1); /* * Traverse this objects callers looking for outside * references. */ for (APLIST_TRAVERSE(CALLERS(lmp), idx, bdp)) { Rt_map *clmp = bdp->b_caller; if ((bdp->b_flags & BND_REFER) == 0) continue; if (CYCGROUP(lmp) != CYCGROUP(clmp)) return (1); } } /* * If we're here then none of the cyclic dependents have been * referenced from outside of the cycle, mark them as unused. */ for (ndx = fndx; ndx < sort->s_lndx; ndx++) { lmp = sort->s_lmpa[ndx]; FLAGS1(lmp) &= ~FL1_RT_USED; } } return (1); } /* * Take elements off of the stack and move them to the link-map array. Typically * this routine just pops one strongly connected component (individual link-map) * at a time. When a cyclic dependency has been detected the stack will contain * more than just the present object to process, and will trigger the later call * to sort_scc() to sort these elements. */ static int visit(Lm_list *lml, Rt_map *lmp, Sort *sort, int flag) { APlist *alp = NULL; int num = sort->s_lndx; Word tracing = lml->lm_flags & LML_FLG_TRC_ENABLE; Rt_map *tlmp; do { tlmp = sort->s_stack[--(sort->s_sndx)]; SORTVAL(tlmp) = sort->s_num; DBG_CALL(Dbg_util_collect(tlmp, sort->s_lndx, flag)); sort->s_lmpa[(sort->s_lndx)++] = tlmp; if (flag & RT_SORT_REV) { /* * Indicate the object has had its .init collected. * Note, that regardless of the object having a .init * the object is added to the tsort list, as it's from * this list that any post-init flags are established. */ FLAGS(tlmp) |= FLG_RT_INITCLCT; lml->lm_init--; } else { /* * Indicate the object has had its .fini collected. * Note, that regardless of the object having a .fini, * the object is added to the tsort list, as it's from * this list that any audit_objclose() activity is * triggered. */ FLAGS(tlmp) |= FLG_RT_FINICLCT; } /* * If tracing, save the strongly connected component. */ if (tracing && (aplist_append(&alp, tlmp, AL_CNT_SCC) == NULL)) return (0); } while (tlmp != lmp); /* * Determine if there are cyclic dependencies to process. If so, sort * the components, and retain them for tracing output. */ if (sort->s_lndx > (num + 1)) { if (sort_scc(sort, num, flag) == 0) return (0); if (tracing && (aplist_append(&sort->s_scc, alp, AL_CNT_SCC) == NULL)) return (0); } else if (alp) free(alp); return (1); } static int dep_visit(Lm_list *, Rt_map *, uint_t, Rt_map *, Sort *, int); static int _dep_visit(Lm_list *lml, int min, Rt_map *clmp, Rt_map *dlmp, uint_t bflags, Sort *sort, int flag) { int _min; /* * Only collect objects that belong to the callers link-map. Catches * cross dependencies (filtering) to ld.so.1. */ if (LIST(dlmp) != lml) return (min); /* * Determine if this object hasn't been inspected. */ if ((_min = SORTVAL(dlmp)) == -1) { if (flag & RT_SORT_REV) { /* * For .init processing, only collect objects that have * been relocated and haven't already been collected. */ if ((FLAGS(dlmp) & (FLG_RT_RELOCED | FLG_RT_INITCLCT)) != FLG_RT_RELOCED) return (min); /* * If this object contains no .init, there's no need to * establish a dependency. */ if ((INIT(dlmp) == 0) && (INITARRAY(dlmp) == 0)) return (min); } else { /* * For .fini processing only collect objects that have * had their .init collected, and haven't already been * .fini collected. */ if ((FLAGS(dlmp) & (FLG_RT_INITCLCT | FLG_RT_FINICLCT)) != FLG_RT_INITCLCT) return (min); /* * If we're deleting a subset of objects, only collect * those marked for deletion. */ if ((flag & RT_SORT_DELETE) && ((FLAGS(dlmp) & FLG_RT_DELETE) == 0)) return (min); /* * If this object contains no .fini, there's no need to * establish a dependency. */ if ((FINI(dlmp) == 0) && (FINIARRAY(dlmp) == 0)) return (min); } /* * Inspect this new dependency. */ if ((_min = dep_visit(lml, clmp, bflags, dlmp, sort, flag)) == -1) return (-1); } /* * Keep track of the smallest SORTVAL that has been encountered. If * this value is smaller than the present object, then the dependency * edge has cycled back to objects that have been processed earlier * along this dependency edge. */ if (_min < min) { DBG_CALL(Dbg_util_edge_out(clmp, sort->s_stack[_min])); return (_min); } else return (min); } /* * Visit the dependencies of each object. */ static int dep_visit(Lm_list *lml, Rt_map *clmp, uint_t cbflags, Rt_map *lmp, Sort *sort, int flag) { int min; Aliste idx1; Bnd_desc *bdp; Dyninfo *dip; min = SORTVAL(lmp) = sort->s_sndx; sort->s_stack[(sort->s_sndx)++] = lmp; if (FLAGS(lmp) & FLG_RT_INITFRST) sort->s_initfirst++; DBG_CALL(Dbg_util_edge_in(lml, clmp, cbflags, lmp, min, flag)); /* * Traverse both explicit and implicit dependencies. */ for (APLIST_TRAVERSE(DEPENDS(lmp), idx1, bdp)) { if ((min = _dep_visit(lml, min, lmp, bdp->b_depend, bdp->b_flags, sort, flag)) == -1) return (-1); } /* * Traverse any filtee dependencies. */ if (((dip = DYNINFO(lmp)) != NULL) && (FLAGS1(lmp) & MSK_RT_FILTER)) { uint_t cnt, max = DYNINFOCNT(lmp); for (cnt = 0; cnt < max; cnt++, dip++) { Alist *falp; Pdesc *pdp; if (((falp = (Alist *)dip->di_info) == NULL) || ((dip->di_flags & MSK_DI_FILTER) == 0)) continue; for (ALIST_TRAVERSE(falp, idx1, pdp)) { Aliste idx2; Grp_hdl *ghp; Grp_desc *gdp; if ((pdp->pd_plen == 0) || ((ghp = (Grp_hdl *)pdp->pd_info) == NULL)) continue; for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { if (gdp->gd_depend == lmp) continue; if ((min = _dep_visit(lml, min, lmp, gdp->gd_depend, BND_FILTER, sort, flag)) == -1) return (-1); } } } } /* * Having returned to where the minimum SORTVAL is equivalent to the * object that has just been processed, collect any dependencies that * are available on the sorting stack. */ if (min == SORTVAL(lmp)) { if (visit(lml, lmp, sort, flag) == 0) return (-1); } return (min); } /* * Find corresponding strongly connected component structure. */ static APlist * trace_find_scc(Sort *sort, Rt_map *lmp) { APlist *alp; Aliste idx1; for (APLIST_TRAVERSE(sort->s_scc, idx1, alp)) { Rt_map *lmp2; Aliste idx2; for (APLIST_TRAVERSE(alp, idx2, lmp2)) { if (lmp == lmp2) return (alp); } } return (NULL); } /* * Print out the .init dependency information (ldd). */ static void trace_sort(Sort *sort) { int ndx = 0; APlist *alp; Rt_map *lmp1; (void) printf(MSG_ORIG(MSG_STR_NL)); while ((lmp1 = sort->s_lmpa[ndx++]) != NULL) { static const char *ffmt, *cfmt = 0, *sfmt = 0; Bnd_desc *bdp; Aliste idx1; if ((INIT(lmp1) == 0) || (FLAGS(lmp1) & FLG_RT_INITCALL)) continue; if (sfmt == 0) sfmt = MSG_INTL(MSG_LDD_INIT_FMT_02); /* * If the only component on the strongly connected list is * this link-map, then there are no dependencies. */ if ((alp = trace_find_scc(sort, lmp1)) == NULL) { (void) printf(sfmt, NAME(lmp1)); continue; } /* * Establish message formats for cyclic dependencies. */ if (cfmt == 0) { cfmt = MSG_INTL(MSG_LDD_INIT_FMT_03); ffmt = MSG_ORIG(MSG_LDD_INIT_FMT_FILE); } (void) printf(cfmt, NAME(lmp1), CYCGROUP(lmp1)); for (APLIST_TRAVERSE(CALLERS(lmp1), idx1, bdp)) { Rt_map *lmp3, *lmp2 = bdp->b_caller; Aliste idx2; for (APLIST_TRAVERSE(alp, idx2, lmp3)) { if (lmp2 != lmp3) continue; (void) printf(ffmt, NAME(lmp3)); } } } } /* * A reverse ordered list (for .init's) contains INITFIRST elements. Move each * of these elements to the front of the list. */ static void r_initfirst(Sort * sort, int end) { Rt_map *tlmp; int bgn, ifst, lifst = 0; for (bgn = 0; bgn < sort->s_initfirst; bgn++) { for (ifst = lifst; ifst <= end; ifst++) { tlmp = sort->s_lmpa[ifst]; if (!(FLAGS(tlmp) & FLG_RT_INITFRST)) continue; /* * If the INITFIRST element is already at the front of * the list leave it there. */ if (ifst == bgn) { lifst = ifst + 1; break; } /* * Move the elements from the front of the list up to * the INITFIRST element, back one position. */ (void) memmove(&sort->s_lmpa[bgn + 1], &sort->s_lmpa[bgn], ((ifst - bgn) * sizeof (Rt_map *))); /* * Insert INITFIRST element at the front of the list. */ sort->s_lmpa[bgn] = tlmp; lifst = ifst + 1; break; } } } /* * A forward ordered list (for .fini's) contains INITFIRST elements. Move each * of these elements to the front of the list. */ static void f_initfirst(Sort *sort, int end) { Rt_map *tlmp; int bgn, ifst, lifst = 0; for (bgn = 0; bgn < sort->s_initfirst; bgn++) { for (ifst = lifst; ifst <= end; ifst++) { tlmp = sort->s_lmpa[ifst]; if (!(FLAGS(tlmp) & FLG_RT_INITFRST)) continue; /* * If the INITFIRST element is already at the end of * the list leave it there. */ if (ifst == end) break; /* * Move the elements from after the INITFIRST element * up to the back of the list, up one position. */ (void) memmove(&sort->s_lmpa[ifst], &sort->s_lmpa[ifst + 1], ((end - ifst) * sizeof (Rt_map *))); /* * Insert INITFIRST element at the back of the list. */ sort->s_lmpa[end--] = tlmp; lifst = ifst; break; } } } /* * Determine whether .init or .fini processing is required. */ static int initorfini(Lm_list *lml, Rt_map *lmp, int flag, Sort *sort) { if (flag & RT_SORT_REV) { /* * For .init processing, only collect objects that have been * relocated and haven't already been collected. */ if ((FLAGS(lmp) & (FLG_RT_RELOCED | FLG_RT_INITCLCT)) != FLG_RT_RELOCED) return (0); if (dep_visit(lml, 0, 0, lmp, sort, flag) == -1) return (1); } else if (!(flag & RT_SORT_DELETE) || (FLAGS(lmp) & FLG_RT_DELETE)) { /* * Only collect objects that have had their .init collected, * and haven't already been .fini collected. */ if (!((FLAGS(lmp) & (FLG_RT_INITCLCT | FLG_RT_FINICLCT)) == (FLG_RT_INITCLCT))) return (0); if (dep_visit(lml, 0, 0, lmp, sort, flag) == -1) return (1); } return (0); } /* * Sort the dependency */ Rt_map ** tsort(Rt_map *lmp, int num, int flag) { Rt_map *_lmp; Lm_list *lml = LIST(lmp); Word init = lml->lm_flags & LML_FLG_TRC_INIT; Sort sort = { 0 }; size_t size; if (num == 0) return (0); /* * Prior to tsorting any .init sections, insure that the `environ' * symbol is initialized for this link-map list. */ if ((flag & RT_SORT_REV) && ((lml->lm_flags & (LML_FLG_TRC_ENABLE | LML_FLG_ENVIRON)) == 0)) set_environ(lml); /* * Allocate memory for link-map list array. Calloc the array to insure * all elements are zero, we might find that no objects need processing. * At the same time, allocate a stack for any topological sorting that * might be necessary. */ sort.s_lmp = lmp; sort.s_num = num + 1; size = sort.s_num * sizeof (Rt_map *); if ((sort.s_lmpa = calloc(2, size)) == NULL) return ((Rt_map **)S_ERROR); sort.s_stack = (Rt_map **)((uintptr_t)sort.s_lmpa + size); /* * Determine where to start searching for tsort() candidates. Any call * to tsort() for .init processing is passed the link-map from which to * start searching. However, if new objects have dependencies on * existing objects, or existing objects have been promoted (RTLD_LAZY * to RTLD_NOW), then start searching at the head of the link-map list. * These previously loaded objects will have been tagged for inclusion * in this tsort() pass. They still remain on an existing tsort() list, * which must have been prempted for control to have arrived here. * However, they will be ignored when encountered on any previous * tsort() list if their .init has already been called. */ if (lml->lm_flags & LML_FLG_OBJREEVAL) _lmp = lml->lm_head; else _lmp = lmp; DBG_CALL(Dbg_file_bindings(_lmp, flag)); lml->lm_flags &= ~(LML_FLG_OBJREEVAL | LML_FLG_OBJADDED | LML_FLG_OBJDELETED); /* * If interposers exist, inspect these objects first. * * Interposers can provide implicit dependencies - for example, an * application that has a dependency on libumem will caused any other * dependencies of the application that use the malloc family, to * have an implicit dependency on libumem. However, under the default * condition of lazy binding, these dependency relationships on libumem * are unknown to the tsorting process (ie. a call to one of the malloc * family has not occurred to establish the dependency). This lack of * dependency information makes the interposer look "standalone", * whereas the interposers .init/.fini should be analyzed with respect * to the dependency relationship libumem will eventually create. * * By inspecting interposing objects first, we are able to trigger * their .init sections to be accounted for before any others. * Selecting these .init sections first is important for the malloc * libraries, as these libraries need to prepare for pthread_atfork(). * However, handling interposer libraries in this generic fashion * should help provide for other dependency relationships that may * exist. */ if ((lml->lm_flags & (LML_FLG_INTRPOSE | LML_FLG_INTRPOSETSORT)) == LML_FLG_INTRPOSE) { Rt_map *ilmp = _lmp; /* * Unless the executable is tagged as an interposer, skip to * the next object. */ if ((FLAGS(ilmp) & MSK_RT_INTPOSE) == 0) ilmp = NEXT_RT_MAP(ilmp); for (; ilmp; ilmp = NEXT_RT_MAP(ilmp)) { if ((FLAGS(ilmp) & MSK_RT_INTPOSE) == 0) break; if (initorfini(lml, ilmp, (flag | RT_SORT_INTPOSE), &sort) != 0) return ((Rt_map **)S_ERROR); } /* * Once all interposers are processed, there is no need to * look for interposers again. An interposer can only * be introduced before any relocation takes place, thus * interposer .init's will be grabbed during the first tsort * starting at the head of the link-map list. * * Interposers can't be unloaded. Thus interposer .fini's can * only be called during atexit() processing. The interposer * tsort flag is removed from each link-map list during * atexit_fini() so that the interposers .fini sections are * processed appropriately. */ lml->lm_flags |= LML_FLG_INTRPOSETSORT; } /* * Inspect any standard objects. */ for (; _lmp; _lmp = NEXT_RT_MAP(_lmp)) { if (FLAGS(_lmp) & MSK_RT_INTPOSE) continue; if (initorfini(lml, _lmp, flag, &sort) != 0) return ((Rt_map **)S_ERROR); } /* * The dependencies have been collected such that they are appropriate * for an .init order, for .fini order reverse them. */ if (flag & RT_SORT_FWD) { int bgn = 0, end = sort.s_lndx - 1; while (bgn < end) { Rt_map *tlmp = sort.s_lmpa[end]; sort.s_lmpa[end] = sort.s_lmpa[bgn]; sort.s_lmpa[bgn] = tlmp; bgn++, end--; } } /* * If INITFIRST objects have been collected then move them to the front * or end of the list as appropriate. */ if (sort.s_initfirst) { if (flag & RT_SORT_REV) r_initfirst(&sort, sort.s_lndx - 1); else f_initfirst(&sort, sort.s_lndx - 1); } /* * If tracing .init sections (only meaningful for RT_SORT_REV), print * out the sorted dependencies. */ if (init) trace_sort(&sort); /* * Clean any temporary structures prior to return. */ if (sort.s_queue) { Aliste idx; Rt_map *lmp2; /* * Traverse the link-maps collected on the sort queue and * delete the depth index. These link-maps may be traversed * again to sort other components either for inits, and almost * certainly for .finis. */ for (APLIST_TRAVERSE(sort.s_queue, idx, lmp2)) IDX(lmp2) = 0; free(sort.s_queue); } if (sort.s_scc) { Aliste idx; APlist *alp; for (APLIST_TRAVERSE(sort.s_scc, idx, alp)) free(alp); free(sort.s_scc); } /* * The caller is responsible for freeing the sorted link-map list once * the associated .init/.fini's have been fired. */ DBG_CALL(Dbg_file_bindings_done(lml)); return (sort.s_lmpa); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1988 AT&T * All Rights Reserved * * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright (c) 2014 by Delphix. All rights reserved. */ /* * Utility routines for run-time linker. some are duplicated here from libc * (with different names) to avoid name space collisions. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "_audit.h" #include "_elf.h" #include "msg.h" /* * Null function used as place where a debugger can set a breakpoint. */ void rtld_db_dlactivity(Lm_list *lml) { DBG_CALL(Dbg_util_dbnotify(lml, r_debug.rtd_rdebug.r_rdevent, r_debug.rtd_rdebug.r_state)); } /* * Null function used as place where debugger can set a pre .init * processing breakpoint. */ void rtld_db_preinit(Lm_list *lml) { DBG_CALL(Dbg_util_dbnotify(lml, r_debug.rtd_rdebug.r_rdevent, r_debug.rtd_rdebug.r_state)); } /* * Null function used as place where debugger can set a post .init * processing breakpoint. */ void rtld_db_postinit(Lm_list *lml) { DBG_CALL(Dbg_util_dbnotify(lml, r_debug.rtd_rdebug.r_rdevent, r_debug.rtd_rdebug.r_state)); } /* * Debugger Event Notification * * This function centralizes all debugger event notification (ala rtld_db). * * There's a simple intent, focused on insuring the primary link-map control * list (or each link-map list) is consistent, and the indication that objects * have been added or deleted from this list. Although an RD_ADD and RD_DELETE * event are posted for each of these, most debuggers don't care, as their * view is that these events simply convey an "inconsistent" state. * * We also don't want to trigger multiple RD_ADD/RD_DELETE events any time we * enter ld.so.1. * * Set an RD_ADD/RD_DELETE event and indicate that an RD_CONSISTENT event is * required later (RT_FL_DBNOTIF): * * i. the first time we add or delete an object to the primary link-map * control list. * ii. the first time we move a secondary link-map control list to the primary * link-map control list (effectively, this is like adding a group of * objects to the primary link-map control list). * * Set an RD_CONSISTENT event when it is required (RT_FL_DBNOTIF is set): * * i. each time we leave the runtime linker. */ void rd_event(Lm_list *lml, rd_event_e event, r_state_e state) { void (*fptr)(Lm_list *); switch (event) { case RD_PREINIT: fptr = rtld_db_preinit; break; case RD_POSTINIT: fptr = rtld_db_postinit; break; case RD_DLACTIVITY: switch (state) { case RT_CONSISTENT: /* * Do we need to send a notification? */ if ((rtld_flags & RT_FL_DBNOTIF) == 0) return; rtld_flags &= ~RT_FL_DBNOTIF; break; case RT_ADD: case RT_DELETE: /* * If we are already in an inconsistent state, no * notification is required. */ if (rtld_flags & RT_FL_DBNOTIF) return; rtld_flags |= RT_FL_DBNOTIF; break; }; fptr = rtld_db_dlactivity; break; default: /* * RD_NONE - do nothing */ break; }; /* * Set event state and call 'notification' function. * * The debugging clients have previously been told about these * notification functions and have set breakpoints on them if they * are interested in the notification. */ r_debug.rtd_rdebug.r_state = state; r_debug.rtd_rdebug.r_rdevent = event; fptr(lml); r_debug.rtd_rdebug.r_rdevent = RD_NONE; } #if defined(__sparc) || defined(__x86) /* * Stack Cleanup. * * This function is invoked to 'remove' arguments that were passed in on the * stack. This is most likely if ld.so.1 was invoked directly. In that case * we want to remove ld.so.1 as well as it's arguments from the argv[] array. * Which means we then need to slide everything above it on the stack down * accordingly. * * While the stack layout is platform specific - it just so happens that __x86, * and __sparc platforms share the following initial stack layout. * * !_______________________! high addresses * ! ! * ! Information ! * ! Block ! * ! (size varies) ! * !_______________________! * ! 0 word ! * !_______________________! * ! Auxiliary ! * ! vector ! * ! 2 word entries ! * ! ! * !_______________________! * ! 0 word ! * !_______________________! * ! Environment ! * ! pointers ! * ! ... ! * ! (one word each) ! * !_______________________! * ! 0 word ! * !_______________________! * ! Argument ! low addresses * ! pointers ! * ! Argc words ! * !_______________________! * ! ! * ! Argc ! * !_______________________! * ! ... ! * */ static void stack_cleanup(char **argv, char ***envp, auxv_t **auxv, int rmcnt) { int ndx; long *argc; char **oargv, **nargv; char **oenvp, **nenvp; auxv_t *oauxv, *nauxv; /* * Slide ARGV[] and update argc. The argv pointer remains the same, * however slide the applications arguments over the arguments to * ld.so.1. */ nargv = &argv[0]; oargv = &argv[rmcnt]; for (ndx = 0; oargv[ndx]; ndx++) nargv[ndx] = oargv[ndx]; nargv[ndx] = oargv[ndx]; argc = (long *)((uintptr_t)argv - sizeof (long *)); *argc -= rmcnt; /* * Slide ENVP[], and update the environment array pointer. */ ndx++; nenvp = &nargv[ndx]; oenvp = &oargv[ndx]; *envp = nenvp; for (ndx = 0; oenvp[ndx]; ndx++) nenvp[ndx] = oenvp[ndx]; nenvp[ndx] = oenvp[ndx]; /* * Slide AUXV[], and update the aux vector pointer. */ ndx++; nauxv = (auxv_t *)&nenvp[ndx]; oauxv = (auxv_t *)&oenvp[ndx]; *auxv = nauxv; for (ndx = 0; (oauxv[ndx].a_type != AT_NULL); ndx++) nauxv[ndx] = oauxv[ndx]; nauxv[ndx] = oauxv[ndx]; } #else /* * Verify that the above routine is appropriate for any new platforms. */ #error unsupported architecture! #endif /* * Compare function for PathNode AVL tree. */ static int pnavl_compare(const void *n1, const void *n2) { uint_t hash1, hash2; const char *st1, *st2; int rc; hash1 = ((PathNode *)n1)->pn_hash; hash2 = ((PathNode *)n2)->pn_hash; if (hash1 > hash2) return (1); if (hash1 < hash2) return (-1); st1 = ((PathNode *)n1)->pn_name; st2 = ((PathNode *)n2)->pn_name; rc = strcmp(st1, st2); if (rc > 0) return (1); if (rc < 0) return (-1); return (0); } /* * Create an AVL tree. */ static avl_tree_t * pnavl_create(size_t size) { avl_tree_t *avlt; if ((avlt = malloc(sizeof (avl_tree_t))) == NULL) return (NULL); avl_create(avlt, pnavl_compare, size, SGSOFFSETOF(PathNode, pn_avl)); return (avlt); } /* * Determine whether a PathNode is recorded. */ int pnavl_recorded(avl_tree_t **pnavl, const char *name, uint_t hash, avl_index_t *where) { PathNode pn; /* * Create the avl tree if required. */ if ((*pnavl == NULL) && ((*pnavl = pnavl_create(sizeof (PathNode))) == NULL)) return (0); pn.pn_name = name; if ((pn.pn_hash = hash) == 0) pn.pn_hash = sgs_str_hash(name); if (avl_find(*pnavl, &pn, where) == NULL) return (0); return (1); } /* * Determine if a pathname has already been recorded on the full path name * AVL tree. This tree maintains a node for each path name that ld.so.1 has * successfully loaded. If the path name does not exist in this AVL tree, then * the next insertion point is deposited in "where". This value can be used by * fpavl_insert() to expedite the insertion. */ Rt_map * fpavl_recorded(Lm_list *lml, const char *name, uint_t hash, avl_index_t *where) { FullPathNode fpn, *fpnp; /* * Create the avl tree if required. */ if ((lml->lm_fpavl == NULL) && ((lml->lm_fpavl = pnavl_create(sizeof (FullPathNode))) == NULL)) return (NULL); fpn.fpn_node.pn_name = name; if ((fpn.fpn_node.pn_hash = hash) == 0) fpn.fpn_node.pn_hash = sgs_str_hash(name); if ((fpnp = avl_find(lml->lm_fpavl, &fpn, where)) == NULL) return (NULL); return (fpnp->fpn_lmp); } /* * Insert a name into the FullPathNode AVL tree for the link-map list. The * objects NAME() is the path that would have originally been searched for, and * is therefore the name to associate with any "where" value. If the object has * a different PATHNAME(), perhaps because it has resolved to a different file * (see fullpath()), then this name will be recorded as a separate FullPathNode * (see load_file()). */ int fpavl_insert(Lm_list *lml, Rt_map *lmp, const char *name, avl_index_t where) { FullPathNode *fpnp; uint_t hash = sgs_str_hash(name); if (where == 0) { Rt_map *_lmp __maybe_unused; _lmp = fpavl_recorded(lml, name, hash, &where); /* * We better not get a hit now, we do not want duplicates in * the tree. */ ASSERT(_lmp == NULL); } /* * Insert new node in tree. */ if ((fpnp = calloc(1, sizeof (FullPathNode))) == NULL) return (0); fpnp->fpn_node.pn_name = name; fpnp->fpn_node.pn_hash = hash; fpnp->fpn_lmp = lmp; if (aplist_append(&FPNODE(lmp), fpnp, AL_CNT_FPNODE) == NULL) { free(fpnp); return (0); } ASSERT(lml->lm_fpavl != NULL); avl_insert(lml->lm_fpavl, fpnp, where); return (1); } /* * Remove an object from the FullPathNode AVL tree. */ void fpavl_remove(Rt_map *lmp) { FullPathNode *fpnp; Aliste idx; for (APLIST_TRAVERSE(FPNODE(lmp), idx, fpnp)) { avl_remove(LIST(lmp)->lm_fpavl, fpnp); free(fpnp); } free(FPNODE(lmp)); FPNODE(lmp) = NULL; } /* * Insert a path name into the not-found AVL tree. * * This tree maintains a node for each path name that ld.so.1 has explicitly * inspected, but has failed to load during a single ld.so.1 operation. If the * path name does not exist in this AVL tree, then the next insertion point is * deposited in "where". This value can be used by nfavl_insert() to expedite * the insertion. */ void nfavl_insert(const char *name, avl_index_t where) { PathNode *pnp; uint_t hash = sgs_str_hash(name); if (where == 0) { int in_nfavl __maybe_unused; in_nfavl = pnavl_recorded(&nfavl, name, hash, &where); /* * We better not get a hit now, we do not want duplicates in * the tree. */ ASSERT(in_nfavl == 0); } /* * Insert new node in tree. */ if ((pnp = calloc(1, sizeof (PathNode))) != NULL) { pnp->pn_name = name; pnp->pn_hash = hash; avl_insert(nfavl, pnp, where); } } /* * Insert the directory name, of a full path name, into the secure path AVL * tree. * * This tree is used to maintain a list of directories in which the dependencies * of a secure process have been found. This list provides a fall-back in the * case that a $ORIGIN expansion is deemed insecure, when the expansion results * in a path name that has already provided dependencies. */ void spavl_insert(const char *name) { char buffer[PATH_MAX], *str; size_t size; avl_index_t where; PathNode *pnp; uint_t hash; /* * Separate the directory name from the path name. */ if ((str = strrchr(name, '/')) == name) size = 1; else size = str - name; (void) strncpy(buffer, name, size); buffer[size] = '\0'; hash = sgs_str_hash(buffer); /* * Determine whether this directory name is already recorded, or if * not, 'where" will provide the insertion point for the new string. */ if (pnavl_recorded(&spavl, buffer, hash, &where)) return; /* * Insert new node in tree. */ if ((pnp = calloc(1, sizeof (PathNode))) != NULL) { pnp->pn_name = strdup(buffer); pnp->pn_hash = hash; avl_insert(spavl, pnp, where); } } /* * Inspect the generic string AVL tree for the given string. If the string is * not present, duplicate it, and insert the string in the AVL tree. Return the * duplicated string to the caller. * * These strings are maintained for the life of ld.so.1 and represent path * names, file names, and search paths. All other AVL trees that maintain * FullPathNode and not-found path names use the same string pointer * established for this string. */ static avl_tree_t *stravl = NULL; static char *strbuf = NULL; static PathNode *pnbuf = NULL; static size_t strsize = 0, pnsize = 0; const char * stravl_insert(const char *name, uint_t hash, size_t nsize, int substr) { char str[PATH_MAX]; PathNode *pnp; avl_index_t where; /* * Create the avl tree if required. */ if ((stravl == NULL) && ((stravl = pnavl_create(sizeof (PathNode))) == NULL)) return (NULL); /* * Determine the string size if not provided by the caller. */ if (nsize == 0) nsize = strlen(name) + 1; else if (substr) { /* * The string passed to us may be a multiple path string for * which we only need the first component. Using the provided * size, strip out the required string. */ (void) strncpy(str, name, nsize); str[nsize - 1] = '\0'; name = str; } /* * Allocate a PathNode buffer if one doesn't exist, or any existing * buffer has been used up. */ if ((pnbuf == NULL) || (sizeof (PathNode) > pnsize)) { pnsize = syspagsz; if ((pnbuf = dz_map(0, 0, pnsize, (PROT_READ | PROT_WRITE), MAP_PRIVATE)) == MAP_FAILED) return (NULL); } /* * Determine whether this string already exists. */ pnbuf->pn_name = name; if ((pnbuf->pn_hash = hash) == 0) pnbuf->pn_hash = sgs_str_hash(name); if ((pnp = avl_find(stravl, pnbuf, &where)) != NULL) return (pnp->pn_name); /* * Allocate a string buffer if one does not exist, or if there is * insufficient space for the new string in any existing buffer. */ if ((strbuf == NULL) || (nsize > strsize)) { strsize = S_ROUND(nsize, syspagsz); if ((strbuf = dz_map(0, 0, strsize, (PROT_READ | PROT_WRITE), MAP_PRIVATE)) == MAP_FAILED) return (NULL); } (void) memcpy(strbuf, name, nsize); pnp = pnbuf; pnp->pn_name = strbuf; avl_insert(stravl, pnp, where); strbuf += nsize; strsize -= nsize; pnbuf++; pnsize -= sizeof (PathNode); return (pnp->pn_name); } /* * Prior to calling an object, either via a .plt or through dlsym(), make sure * its .init has fired. Through topological sorting, ld.so.1 attempts to fire * init's in the correct order, however, this order is typically based on needed * dependencies and non-lazy relocation bindings. Lazy relocations (.plts) can * still occur and result in bindings that were not captured during topological * sorting. This routine compensates for this lack of binding information, and * provides for dynamic .init firing. */ void is_dep_init(Rt_map *dlmp, Rt_map *clmp) { Rt_map **tobj; /* * If the caller is an auditor, and the destination isn't, then don't * run any .inits (see comments in load_completion()). */ if ((LIST(clmp)->lm_tflags & LML_TFLG_NOAUDIT) && ((LIST(dlmp)->lm_tflags & LML_TFLG_NOAUDIT) == 0)) return; if ((dlmp == clmp) || (rtld_flags & RT_FL_INITFIRST)) return; (void) rt_mutex_lock(&dlmp->rt_lock); while (dlmp->rt_init_thread != rt_thr_self() && (FLAGS(dlmp) & (FLG_RT_RELOCED | FLG_RT_INITCALL | FLG_RT_INITDONE)) == (FLG_RT_RELOCED | FLG_RT_INITCALL)) { leave(LIST(dlmp), 0); (void) _lwp_cond_wait(&dlmp->rt_cv, (mutex_t *)&dlmp->rt_lock); (void) rt_mutex_unlock(&dlmp->rt_lock); (void) enter(0); (void) rt_mutex_lock(&dlmp->rt_lock); } (void) rt_mutex_unlock(&dlmp->rt_lock); if ((FLAGS(dlmp) & (FLG_RT_RELOCED | FLG_RT_INITDONE)) == (FLG_RT_RELOCED | FLG_RT_INITDONE)) return; if ((tobj = calloc(2, sizeof (Rt_map *))) != NULL) { tobj[0] = dlmp; call_init(tobj, DBG_INIT_DYN); } } /* * Execute .{preinit|init|fini}array sections */ void call_array(Addr *array, uint_t arraysz, Rt_map *lmp, Word shtype) { int start, stop, incr, ndx; uint_t arraycnt = (uint_t)(arraysz / sizeof (Addr)); if (array == NULL) return; /* * initarray & preinitarray are walked from beginning to end - while * finiarray is walked from end to beginning. */ if (shtype == SHT_FINI_ARRAY) { start = arraycnt - 1; stop = incr = -1; } else { start = 0; stop = arraycnt; incr = 1; } /* * Call the .*array[] entries */ for (ndx = start; ndx != stop; ndx += incr) { uint_t rtldflags; void (*fptr)(void) = (void(*)())array[ndx]; DBG_CALL(Dbg_util_call_array(lmp, (void *)fptr, ndx, shtype)); APPLICATION_ENTER(rtldflags); leave(LIST(lmp), 0); (*fptr)(); (void) enter(0); APPLICATION_RETURN(rtldflags); } } /* * Execute any .init sections. These are passed to us in an lmp array which * (by default) will have been sorted. */ void call_init(Rt_map **tobj, int flag) { Rt_map **_tobj, **_nobj; static APlist *pending = NULL; /* * If we're in the middle of an INITFIRST, this must complete before * any new init's are fired. In this case add the object list to the * pending queue and return. We'll pick up the queue after any * INITFIRST objects have their init's fired. */ if (rtld_flags & RT_FL_INITFIRST) { (void) aplist_append(&pending, tobj, AL_CNT_PENDING); return; } /* * Traverse the tobj array firing each objects init. */ for (_tobj = _nobj = tobj, _nobj++; *_tobj != NULL; _tobj++, _nobj++) { Rt_map *lmp = *_tobj; void (*iptr)() = INIT(lmp); if (FLAGS(lmp) & FLG_RT_INITCALL) continue; FLAGS(lmp) |= FLG_RT_INITCALL; lmp->rt_init_thread = rt_thr_self(); /* * Establish an initfirst state if necessary - no other inits * will be fired (because of additional relocation bindings) * when in this state. */ if (FLAGS(lmp) & FLG_RT_INITFRST) rtld_flags |= RT_FL_INITFIRST; if (INITARRAY(lmp) || iptr) DBG_CALL(Dbg_util_call_init(lmp, flag)); if (iptr) { uint_t rtldflags; APPLICATION_ENTER(rtldflags); leave(LIST(lmp), 0); (*iptr)(); (void) enter(0); APPLICATION_RETURN(rtldflags); } call_array(INITARRAY(lmp), INITARRAYSZ(lmp), lmp, SHT_INIT_ARRAY); if (INITARRAY(lmp) || iptr) DBG_CALL(Dbg_util_call_init(lmp, DBG_INIT_DONE)); /* * Set the initdone flag regardless of whether this object * actually contains an .init section. This flag prevents us * from processing this section again for an .init and also * signifies that a .fini must be called should it exist. * Clear the sort field for use in later .fini processing. */ (void) rt_mutex_lock(&lmp->rt_lock); FLAGS(lmp) |= FLG_RT_INITDONE; lmp->rt_init_thread = (thread_t)0; (void) _lwp_cond_broadcast(&lmp->rt_cv); (void) rt_mutex_unlock(&lmp->rt_lock); SORTVAL(lmp) = -1; /* * If we're firing an INITFIRST object, and other objects must * be fired which are not INITFIRST, make sure we grab any * pending objects that might have been delayed as this * INITFIRST was processed. */ if ((rtld_flags & RT_FL_INITFIRST) && ((*_nobj == NULL) || !(FLAGS(*_nobj) & FLG_RT_INITFRST))) { Aliste idx; Rt_map **pobj; rtld_flags &= ~RT_FL_INITFIRST; for (APLIST_TRAVERSE(pending, idx, pobj)) { aplist_delete(pending, &idx); call_init(pobj, DBG_INIT_PEND); } } } free(tobj); } /* * Call .fini sections for the topologically sorted list of objects. This * routine is called from remove_hdl() for any objects being torn down as part * of a dlclose() operation, and from atexit() processing for all the remaining * objects within the process. */ void call_fini(Lm_list *lml, Rt_map **tobj, Rt_map *clmp) { Rt_map **_tobj; for (_tobj = tobj; *_tobj != NULL; _tobj++) { Rt_map *lmp = *_tobj; /* * Only fire a .fini if the objects corresponding .init has * completed. We collect all .fini sections of objects that * had their .init collected, but that doesn't mean that at * the time of collection, that the .init had completed. */ if (FLAGS(lmp) & FLG_RT_INITDONE) { void (*fptr)(void) = FINI(lmp); if (FINIARRAY(lmp) || fptr) DBG_CALL(Dbg_util_call_fini(lmp)); call_array(FINIARRAY(lmp), FINIARRAYSZ(lmp), lmp, SHT_FINI_ARRAY); if (fptr) { uint_t rtldflags; APPLICATION_ENTER(rtldflags); leave(lml, 0); (*fptr)(); (void) enter(0); APPLICATION_RETURN(rtldflags); } } /* * Skip main, this is explicitly called last in atexit_fini(). */ if (FLAGS(lmp) & FLG_RT_ISMAIN) continue; /* * This object has exercised its last instructions (regardless * of whether it will be unmapped or not). Audit this closure. */ if ((lml->lm_tflags & LML_TFLG_NOAUDIT) == 0) audit_objclose(lmp, clmp); } DBG_CALL(Dbg_bind_plt_summary(lml, M_MACH, pltcnt21d, pltcnt24d, pltcntu32, pltcntu44, pltcntfull, pltcntfar)); free(tobj); } /* * Function called by atexit(3C). Calls all .fini sections within the objects * that make up the process. As .fini processing is the last opportunity for * any new bindings to be established, this is also a convenient location to * check for unused objects. */ void atexit_fini() { Rt_map **tobj, *lmp; Lm_list *lml; Aliste idx; (void) enter(0); rtld_flags |= RT_FL_ATEXIT; lml = &lml_main; lml->lm_flags |= LML_FLG_ATEXIT; lml->lm_flags &= ~LML_FLG_INTRPOSETSORT; lmp = (Rt_map *)lml->lm_head; /* * Reverse topologically sort the main link-map for .fini execution. */ if (((tobj = tsort(lmp, lml->lm_obj, RT_SORT_FWD)) != NULL) && (tobj != (Rt_map **)S_ERROR)) call_fini(lml, tobj, NULL); /* * Now that all .fini code has been run, see what unreferenced objects * remain. */ unused(lml); /* * Traverse any alternative link-map lists, looking for non-auditors. */ for (APLIST_TRAVERSE(dynlm_list, idx, lml)) { /* * Ignore the base-link-map list, which has already been * processed, the runtime linkers link-map list, which is * processed last, and any auditors. */ if ((lml->lm_flags & (LML_FLG_BASELM | LML_FLG_RTLDLM)) || (lml->lm_tflags & LML_TFLG_AUD_MASK) || ((lmp = (Rt_map *)lml->lm_head) == NULL)) continue; lml->lm_flags |= LML_FLG_ATEXIT; lml->lm_flags &= ~LML_FLG_INTRPOSETSORT; /* * Reverse topologically sort the link-map for .fini execution. */ if (((tobj = tsort(lmp, lml->lm_obj, RT_SORT_FWD)) != NULL) && (tobj != (Rt_map **)S_ERROR)) call_fini(lml, tobj, NULL); unused(lml); } /* * Add an explicit close to main and ld.so.1. Although main's .fini is * collected in call_fini() to provide for FINITARRAY processing, its * audit_objclose is explicitly skipped. This provides for it to be * called last, here. This is the reverse of the explicit calls to * audit_objopen() made in setup(). */ lml = &lml_main; lmp = (Rt_map *)lml->lm_head; if ((lml->lm_tflags | AFLAGS(lmp)) & LML_TFLG_AUD_MASK) { audit_objclose((Rt_map *)lml_rtld.lm_head, lmp); audit_objclose(lmp, lmp); } /* * Traverse any alternative link-map lists, looking for non-auditors. */ for (APLIST_TRAVERSE(dynlm_list, idx, lml)) { /* * Ignore the base-link-map list, which has already been * processed, the runtime linkers link-map list, which is * processed last, and any non-auditors. */ if ((lml->lm_flags & (LML_FLG_BASELM | LML_FLG_RTLDLM)) || ((lml->lm_tflags & LML_TFLG_AUD_MASK) == 0) || ((lmp = (Rt_map *)lml->lm_head) == NULL)) continue; lml->lm_flags |= LML_FLG_ATEXIT; lml->lm_flags &= ~LML_FLG_INTRPOSETSORT; /* * Reverse topologically sort the link-map for .fini execution. */ if (((tobj = tsort(lmp, lml->lm_obj, RT_SORT_FWD)) != NULL) && (tobj != (Rt_map **)S_ERROR)) call_fini(lml, tobj, NULL); unused(lml); } /* * Finally reverse topologically sort the runtime linkers link-map for * .fini execution. */ lml = &lml_rtld; lml->lm_flags |= LML_FLG_ATEXIT; lml->lm_flags &= ~LML_FLG_INTRPOSETSORT; lmp = (Rt_map *)lml->lm_head; if (((tobj = tsort(lmp, lml->lm_obj, RT_SORT_FWD)) != NULL) && (tobj != (Rt_map **)S_ERROR)) call_fini(lml, tobj, NULL); leave(&lml_main, 0); } /* * This routine is called to complete any runtime linker activity which may have * resulted in objects being loaded. This is called from all user entry points * and from any internal dl*() requests. */ void load_completion(Rt_map *nlmp) { Rt_map **tobj = NULL; Lm_list *nlml; /* * Establish any .init processing. Note, in a world of lazy loading, * objects may have been loaded regardless of whether the users request * was fulfilled (i.e., a dlsym() request may have failed to find a * symbol but objects might have been loaded during its search). Thus, * any tsorting starts from the nlmp (new link-maps) pointer and not * necessarily from the link-map that may have satisfied the request. * * Note, the primary link-map has an initialization phase where dynamic * .init firing is suppressed. This provides for a simple and clean * handshake with the primary link-maps libc, which is important for * establishing uberdata. In addition, auditors often obtain handles * to primary link-map objects as the objects are loaded, so as to * inspect the link-map for symbols. This inspection is allowed without * running any code on the primary link-map, as running this code may * reenter the auditor, who may not yet have finished its own * initialization. */ if (nlmp) nlml = LIST(nlmp); if (nlmp && nlml->lm_init && ((nlml != &lml_main) || (rtld_flags2 & (RT_FL2_PLMSETUP | RT_FL2_NOPLM)))) { if ((tobj = tsort(nlmp, nlml->lm_init, RT_SORT_REV)) == (Rt_map **)S_ERROR) tobj = NULL; } /* * Make sure any alternative link-map retrieves any external interfaces * and initializes threads. */ if (nlmp && (nlml != &lml_main)) { (void) rt_get_extern(nlml, nlmp); rt_thr_init(nlml); } /* * Traverse the list of new link-maps and register any dynamic TLS. * This storage is established for any objects not on the primary * link-map, and for any objects added to the primary link-map after * static TLS has been registered. */ if (nlmp && nlml->lm_tls && ((nlml != &lml_main) || (rtld_flags2 & (RT_FL2_PLMSETUP | RT_FL2_NOPLM)))) { Rt_map *lmp; for (lmp = nlmp; lmp; lmp = NEXT_RT_MAP(lmp)) { if (PTTLS(lmp) && PTTLS(lmp)->p_memsz) tls_modaddrem(lmp, TM_FLG_MODADD); } nlml->lm_tls = 0; } /* * Fire any .init's. */ if (tobj) call_init(tobj, DBG_INIT_SORT); } /* * Append an item to the specified link map control list. */ void lm_append(Lm_list *lml, Aliste lmco, Rt_map *lmp) { Lm_cntl *lmc; int add = 1; /* * Indicate that this link-map list has a new object. */ (lml->lm_obj)++; /* * If we're about to add a new object to the main link-map control * list, alert the debuggers. Additions of individual objects to the * main link-map control list occur during initial setup as the * applications immediate dependencies are loaded. Additional objects * are loaded on the main link-map control list after they have been * fully initialized on an alternative link-map control list. See * lm_move(). */ if (lmco == ALIST_OFF_DATA) rd_event(lml, RD_DLACTIVITY, RT_ADD); /* LINTED */ lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, lmco); /* * A link-map list header points to one of more link-map control lists * (see include/rtld.h). The initial list, pointed to by lm_cntl, is * the list of relocated objects. Other lists maintain objects that * are still being analyzed or relocated. This list provides the core * link-map list information used by all ld.so.1 routines. */ if (lmc->lc_head == NULL) { /* * If this is the first link-map for the given control list, * initialize the list. */ lmc->lc_head = lmc->lc_tail = lmp; add = 0; } else if (FLAGS(lmp) & FLG_RT_OBJINTPO) { Rt_map *tlmp; /* * If this is an interposer then append the link-map following * any other interposers (these are objects that have been * previously preloaded, or were identified with -z interpose). * Interposers can only be inserted on the first link-map * control list, as once relocation has started, interposition * from new interposers can't be guaranteed. * * NOTE: We do not interpose on the head of a list. This model * evolved because dynamic executables have already been fully * relocated within themselves and thus can't be interposed on. * Nowadays it's possible to have shared objects at the head of * a list, which conceptually means they could be interposed on. * But, shared objects can be created via dldump() and may only * be partially relocated (just relatives), in which case they * are interposable, but are marked as fixed (ET_EXEC). * * Thus we really don't have a clear method of deciding when the * head of a link-map is interposable. So, to be consistent, * for now only add interposers after the link-map lists head * object. */ for (tlmp = NEXT_RT_MAP(lmc->lc_head); tlmp; tlmp = NEXT_RT_MAP(tlmp)) { if (FLAGS(tlmp) & FLG_RT_OBJINTPO) continue; /* * Insert the new link-map before this non-interposer, * and indicate an interposer is found. */ NEXT(PREV_RT_MAP(tlmp)) = (Link_map *)lmp; PREV(lmp) = PREV(tlmp); NEXT(lmp) = (Link_map *)tlmp; PREV(tlmp) = (Link_map *)lmp; lmc->lc_flags |= LMC_FLG_REANALYZE; add = 0; break; } } /* * Fall through to appending the new link map to the tail of the list. * If we're processing the initial objects of this link-map list, add * them to the backward compatibility list. */ if (add) { NEXT(lmc->lc_tail) = (Link_map *)lmp; PREV(lmp) = (Link_map *)lmc->lc_tail; lmc->lc_tail = lmp; } /* * Having added this link-map to a control list, indicate which control * list the link-map belongs to. Note, control list information is * always maintained as an offset, as the Alist can be reallocated. */ CNTL(lmp) = lmco; /* * Indicate if an interposer is found. Note that the first object on a * link-map can be explicitly defined as an interposer so that it can * provide interposition over direct binding requests. */ if (FLAGS(lmp) & MSK_RT_INTPOSE) lml->lm_flags |= LML_FLG_INTRPOSE; /* * For backward compatibility with debuggers, the link-map list contains * pointers to the main control list. */ if (lmco == ALIST_OFF_DATA) { lml->lm_head = lmc->lc_head; lml->lm_tail = lmc->lc_tail; } } /* * Delete an item from the specified link map control list. */ void lm_delete(Lm_list *lml, Rt_map *lmp, Rt_map *clmp) { Lm_cntl *lmc; /* * If the control list pointer hasn't been initialized, this object * never got added to a link-map list. */ if (CNTL(lmp) == 0) return; /* * If we're about to delete an object from the main link-map control * list, alert the debuggers. */ if (CNTL(lmp) == ALIST_OFF_DATA) rd_event(lml, RD_DLACTIVITY, RT_DELETE); /* * If we're being audited tell the audit library that we're * about to go deleting dependencies. */ if (clmp && (aud_activity || ((LIST(clmp)->lm_tflags | AFLAGS(clmp)) & LML_TFLG_AUD_ACTIVITY))) audit_activity(clmp, LA_ACT_DELETE); /* LINTED */ lmc = (Lm_cntl *)alist_item_by_offset(lml->lm_lists, CNTL(lmp)); if (lmc->lc_head == lmp) lmc->lc_head = NEXT_RT_MAP(lmp); else NEXT(PREV_RT_MAP(lmp)) = (void *)NEXT(lmp); if (lmc->lc_tail == lmp) lmc->lc_tail = PREV_RT_MAP(lmp); else PREV(NEXT_RT_MAP(lmp)) = PREV(lmp); /* * For backward compatibility with debuggers, the link-map list contains * pointers to the main control list. */ if (lmc == (Lm_cntl *)&lml->lm_lists->al_data) { lml->lm_head = lmc->lc_head; lml->lm_tail = lmc->lc_tail; } /* * Indicate we have one less object on this control list. */ (lml->lm_obj)--; } /* * Move a link-map control list to another. Objects that are being relocated * are maintained on secondary control lists. Once their relocation is * complete, the entire list is appended to the previous control list, as this * list must have been the trigger for generating the new control list. */ void lm_move(Lm_list *lml, Aliste nlmco, Aliste plmco, Lm_cntl *nlmc, Lm_cntl *plmc) { Rt_map *lmp; /* * If we're about to add a new family of objects to the main link-map * control list, alert the debuggers. Additions of object families to * the main link-map control list occur during lazy loading, filtering * and dlopen(). */ if (plmco == ALIST_OFF_DATA) rd_event(lml, RD_DLACTIVITY, RT_ADD); DBG_CALL(Dbg_file_cntl(lml, nlmco, plmco)); /* * Indicate each new link-map has been moved to the previous link-map * control list. */ for (lmp = nlmc->lc_head; lmp; lmp = NEXT_RT_MAP(lmp)) { CNTL(lmp) = plmco; /* * If these objects are being added to the main link-map * control list, indicate that there are init's available * for harvesting. */ if (plmco == ALIST_OFF_DATA) { lml->lm_init++; lml->lm_flags |= LML_FLG_OBJADDED; } } /* * Move the new link-map control list, to the callers link-map control * list. */ if (plmc->lc_head == NULL) { plmc->lc_head = nlmc->lc_head; PREV(nlmc->lc_head) = NULL; } else { NEXT(plmc->lc_tail) = (Link_map *)nlmc->lc_head; PREV(nlmc->lc_head) = (Link_map *)plmc->lc_tail; } plmc->lc_tail = nlmc->lc_tail; nlmc->lc_head = nlmc->lc_tail = NULL; /* * For backward compatibility with debuggers, the link-map list contains * pointers to the main control list. */ if (plmco == ALIST_OFF_DATA) { lml->lm_head = plmc->lc_head; lml->lm_tail = plmc->lc_tail; } } /* * Create, or assign a link-map control list. Each link-map list contains a * main control list, which has an Alist offset of ALIST_OFF_DATA (see the * description in include/rtld.h). During the initial construction of a * process, objects are added to this main control list. This control list is * never deleted, unless an alternate link-map list has been requested (say for * auditors), and the associated objects could not be loaded or relocated. * * Once relocation has started, any lazy loadable objects, or filtees, are * processed on a new, temporary control list. Only when these objects have * been fully relocated, are they moved to the main link-map control list. * Once the objects are moved, this temporary control list is deleted (see * remove_cntl()). * * A dlopen() always requires a new temporary link-map control list. * Typically, a dlopen() occurs on a link-map list that had already started * relocation, however, auditors can dlopen() objects on the main link-map * list while under initial construction, before any relocation has begun. * Hence, dlopen() requests are explicitly flagged. */ Aliste create_cntl(Lm_list *lml, int dlopen) { /* * If the head link-map object has already been relocated, create a * new, temporary, control list. */ if (dlopen || (lml->lm_head == NULL) || (FLAGS(lml->lm_head) & FLG_RT_RELOCED)) { Lm_cntl *lmc; if ((lmc = alist_append(&lml->lm_lists, NULL, sizeof (Lm_cntl), AL_CNT_LMLISTS)) == NULL) return (0); return ((Aliste)((char *)lmc - (char *)lml->lm_lists)); } return (ALIST_OFF_DATA); } /* * Environment variables can have a variety of defined permutations, and thus * the following infrastructure exists to allow this variety and to select the * required definition. * * Environment variables can be defined as 32- or 64-bit specific, and if so * they will take precedence over any instruction set neutral form. Typically * this is only useful when the environment value is an informational string. * * Environment variables may be obtained from the standard user environment or * from a configuration file. The latter provides a fallback if no user * environment setting is found, and can take two forms: * * - a replaceable definition - this will be used if no user environment * setting has been seen, or * * - an permanent definition - this will be used no matter what user * environment setting is seen. In the case of list variables it will be * appended to any process environment setting seen. * * Environment variables can be defined without a value (ie. LD_XXXX=) so as to * override any replaceable environment variables from a configuration file. */ static u_longlong_t rplgen = 0; /* replaceable generic */ /* variables */ static u_longlong_t rplisa = 0; /* replaceable ISA specific */ /* variables */ static u_longlong_t prmgen = 0; /* permanent generic */ /* variables */ static u_longlong_t prmisa = 0; /* permanent ISA specific */ /* variables */ static u_longlong_t cmdgen = 0; /* command line (-e) generic */ /* variables */ static u_longlong_t cmdisa = 0; /* command line (-e) ISA */ /* specific variables */ /* * Classify an environment variables type. */ #define ENV_TYP_IGNORE 0x01 /* ignore - variable is for */ /* the wrong ISA */ #define ENV_TYP_ISA 0x02 /* variable is ISA specific */ #define ENV_TYP_CONFIG 0x04 /* variable obtained from a */ /* config file */ #define ENV_TYP_PERMANT 0x08 /* variable is permanent */ #define ENV_TYP_CMDLINE 0x10 /* variable provide with -e */ #define ENV_TYP_NULL 0x20 /* variable is null */ /* * Identify all environment variables. */ #define ENV_FLG_AUDIT 0x0000000000001ULL #define ENV_FLG_AUDIT_ARGS 0x0000000000002ULL #define ENV_FLG_BIND_NOW 0x0000000000004ULL #define ENV_FLG_BIND_NOT 0x0000000000008ULL #define ENV_FLG_BINDINGS 0x0000000000010ULL #define ENV_FLG_CONFGEN 0x0000000000020ULL #define ENV_FLG_CONFIG 0x0000000000040ULL #define ENV_FLG_DEBUG 0x0000000000080ULL #define ENV_FLG_DEBUG_OUTPUT 0x0000000000100ULL #define ENV_FLG_DEMANGLE 0x0000000000200ULL #define ENV_FLG_FLAGS 0x0000000000400ULL #define ENV_FLG_INIT 0x0000000000800ULL #define ENV_FLG_LIBPATH 0x0000000001000ULL #define ENV_FLG_LOADAVAIL 0x0000000002000ULL #define ENV_FLG_LOADFLTR 0x0000000004000ULL #define ENV_FLG_NOAUDIT 0x0000000008000ULL #define ENV_FLG_NOAUXFLTR 0x0000000010000ULL #define ENV_FLG_NOBAPLT 0x0000000020000ULL #define ENV_FLG_NOCONFIG 0x0000000040000ULL #define ENV_FLG_NODIRCONFIG 0x0000000080000ULL #define ENV_FLG_NODIRECT 0x0000000100000ULL #define ENV_FLG_NOENVCONFIG 0x0000000200000ULL #define ENV_FLG_NOLAZY 0x0000000400000ULL #define ENV_FLG_NOOBJALTER 0x0000000800000ULL #define ENV_FLG_NOVERSION 0x0000001000000ULL #define ENV_FLG_PRELOAD 0x0000002000000ULL #define ENV_FLG_PROFILE 0x0000004000000ULL #define ENV_FLG_PROFILE_OUTPUT 0x0000008000000ULL #define ENV_FLG_SIGNAL 0x0000010000000ULL #define ENV_FLG_TRACE_OBJS 0x0000020000000ULL #define ENV_FLG_TRACE_PTHS 0x0000040000000ULL #define ENV_FLG_UNREF 0x0000080000000ULL #define ENV_FLG_UNUSED 0x0000100000000ULL #define ENV_FLG_VERBOSE 0x0000200000000ULL #define ENV_FLG_WARN 0x0000400000000ULL #define ENV_FLG_NOFLTCONFIG 0x0000800000000ULL #define ENV_FLG_BIND_LAZY 0x0001000000000ULL #define ENV_FLG_NOUNRESWEAK 0x0002000000000ULL #define ENV_FLG_NOPAREXT 0x0004000000000ULL #define ENV_FLG_HWCAP 0x0008000000000ULL #define ENV_FLG_SFCAP 0x0010000000000ULL #define ENV_FLG_MACHCAP 0x0020000000000ULL #define ENV_FLG_PLATCAP 0x0040000000000ULL #define ENV_FLG_CAP_FILES 0x0080000000000ULL #define ENV_FLG_DEFERRED 0x0100000000000ULL #define ENV_FLG_NOENVIRON 0x0200000000000ULL #define SEL_REPLACE 0x0001 #define SEL_PERMANT 0x0002 #define SEL_ACT_RT 0x0100 /* setting rtld_flags */ #define SEL_ACT_RT2 0x0200 /* setting rtld_flags2 */ #define SEL_ACT_STR 0x0400 /* setting string value */ #define SEL_ACT_LML 0x0800 /* setting lml_flags */ #define SEL_ACT_LMLT 0x1000 /* setting lml_tflags */ #define SEL_ACT_SPEC_1 0x2000 /* for FLG_{FLAGS, LIBPATH} */ #define SEL_ACT_SPEC_2 0x4000 /* need special handling */ /* * Pattern match an LD_XXXX environment variable. s1 points to the XXXX part * and len specifies its length (comparing a strings length before the string * itself speed things up). s2 points to the token itself which has already * had any leading white-space removed. */ static void ld_generic_env(const char *s1, size_t len, const char *s2, Word *lmflags, Word *lmtflags, uint_t env_flags) { u_longlong_t variable = 0; ushort_t select = 0; const char **str; Word val = 0; /* * Determine whether we're dealing with a replaceable or permanent * string. */ if (env_flags & ENV_TYP_PERMANT) { /* * If the string is from a configuration file and defined as * permanent, assign it as permanent. */ select |= SEL_PERMANT; } else select |= SEL_REPLACE; /* * Parse the variable given. * * The LD_AUDIT family. */ if (*s1 == 'A') { if ((len == MSG_LD_AUDIT_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_AUDIT), MSG_LD_AUDIT_SIZE) == 0)) { /* * Replaceable and permanent audit objects can exist. */ select |= SEL_ACT_STR; str = (select & SEL_REPLACE) ? &rpl_audit : &prm_audit; variable = ENV_FLG_AUDIT; } else if ((len == MSG_LD_AUDIT_ARGS_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_AUDIT_ARGS), MSG_LD_AUDIT_ARGS_SIZE) == 0)) { /* * A specialized variable for plt_exit() use, not * documented for general use. */ select |= SEL_ACT_SPEC_2; variable = ENV_FLG_AUDIT_ARGS; } } /* * The LD_BIND family. */ else if (*s1 == 'B') { if ((len == MSG_LD_BIND_LAZY_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_BIND_LAZY), MSG_LD_BIND_LAZY_SIZE) == 0)) { select |= SEL_ACT_RT2; val = RT_FL2_BINDLAZY; variable = ENV_FLG_BIND_LAZY; } else if ((len == MSG_LD_BIND_NOW_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_BIND_NOW), MSG_LD_BIND_NOW_SIZE) == 0)) { select |= SEL_ACT_RT2; val = RT_FL2_BINDNOW; variable = ENV_FLG_BIND_NOW; } else if ((len == MSG_LD_BIND_NOT_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_BIND_NOT), MSG_LD_BIND_NOT_SIZE) == 0)) { /* * Another trick, initially implemented to help debug * a.out executables under SunOS 4 binary * compatibility (now removed), not documented for * general use, but still useful for debugging around * the PLT, etc. */ select |= SEL_ACT_RT; val = RT_FL_NOBIND; variable = ENV_FLG_BIND_NOT; } else if ((len == MSG_LD_BINDINGS_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_BINDINGS), MSG_LD_BINDINGS_SIZE) == 0)) { /* * This variable is simply for backward compatibility. * If this and LD_DEBUG are both specified, only one of * the strings is going to get processed. */ select |= SEL_ACT_SPEC_2; variable = ENV_FLG_BINDINGS; } } /* * LD_CAP_FILES and LD_CONFIG family. */ else if (*s1 == 'C') { if ((len == MSG_LD_CAP_FILES_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_CAP_FILES), MSG_LD_CAP_FILES_SIZE) == 0)) { select |= SEL_ACT_STR; str = (select & SEL_REPLACE) ? &rpl_cap_files : &prm_cap_files; variable = ENV_FLG_CAP_FILES; } else if ((len == MSG_LD_CONFGEN_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_CONFGEN), MSG_LD_CONFGEN_SIZE) == 0)) { /* * This variable is not documented for general use. * Although originaly designed for internal use with * crle(1), this variable is in use by the Studio * auditing tools. Hence, it can't be removed. */ select |= SEL_ACT_SPEC_2; variable = ENV_FLG_CONFGEN; } else if ((len == MSG_LD_CONFIG_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_CONFIG), MSG_LD_CONFIG_SIZE) == 0)) { /* * Secure applications must use a default configuration * file. A setting from a configuration file doesn't * make sense (given we must be reading a configuration * file to have gotten this). */ if ((rtld_flags & RT_FL_SECURE) || (env_flags & ENV_TYP_CONFIG)) return; select |= SEL_ACT_STR; str = &config->c_name; variable = ENV_FLG_CONFIG; } } /* * The LD_DEBUG family, LD_DEFERRED (internal, used by ldd(1)), and * LD_DEMANGLE. */ else if (*s1 == 'D') { if ((len == MSG_LD_DEBUG_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_DEBUG), MSG_LD_DEBUG_SIZE) == 0)) { select |= SEL_ACT_STR; str = (select & SEL_REPLACE) ? &rpl_debug : &prm_debug; variable = ENV_FLG_DEBUG; } else if ((len == MSG_LD_DEBUG_OUTPUT_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_DEBUG_OUTPUT), MSG_LD_DEBUG_OUTPUT_SIZE) == 0)) { select |= SEL_ACT_STR; str = &dbg_file; variable = ENV_FLG_DEBUG_OUTPUT; } else if ((len == MSG_LD_DEFERRED_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_DEFERRED), MSG_LD_DEFERRED_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_DEFERRED; variable = ENV_FLG_DEFERRED; } else if ((len == MSG_LD_DEMANGLE_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_DEMANGLE), MSG_LD_DEMANGLE_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_DEMANGLE; variable = ENV_FLG_DEMANGLE; } } /* * LD_FLAGS - collect the best variable definition. On completion of * environment variable processing pass the result to ld_flags_env() * where they'll be decomposed and passed back to this routine. */ else if (*s1 == 'F') { if ((len == MSG_LD_FLAGS_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_FLAGS), MSG_LD_FLAGS_SIZE) == 0)) { select |= SEL_ACT_SPEC_1; str = (select & SEL_REPLACE) ? &rpl_ldflags : &prm_ldflags; variable = ENV_FLG_FLAGS; } } /* * LD_HWCAP. */ else if (*s1 == 'H') { if ((len == MSG_LD_HWCAP_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_HWCAP), MSG_LD_HWCAP_SIZE) == 0)) { select |= SEL_ACT_STR; str = (select & SEL_REPLACE) ? &rpl_hwcap : &prm_hwcap; variable = ENV_FLG_HWCAP; } } /* * LD_INIT (internal, used by ldd(1)). */ else if (*s1 == 'I') { if ((len == MSG_LD_INIT_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_INIT), MSG_LD_INIT_SIZE) == 0)) { select |= SEL_ACT_LML; val = LML_FLG_TRC_INIT; variable = ENV_FLG_INIT; } } /* * The LD_LIBRARY_PATH and LD_LOAD families. */ else if (*s1 == 'L') { if ((len == MSG_LD_LIBPATH_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_LIBPATH), MSG_LD_LIBPATH_SIZE) == 0)) { select |= SEL_ACT_SPEC_1; str = (select & SEL_REPLACE) ? &rpl_libpath : &prm_libpath; variable = ENV_FLG_LIBPATH; } else if ((len == MSG_LD_LOADAVAIL_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_LOADAVAIL), MSG_LD_LOADAVAIL_SIZE) == 0)) { /* * This variable is not documented for general use. * Although originaly designed for internal use with * crle(1), this variable is in use by the Studio * auditing tools. Hence, it can't be removed. */ select |= SEL_ACT_LML; val = LML_FLG_LOADAVAIL; variable = ENV_FLG_LOADAVAIL; } else if ((len == MSG_LD_LOADFLTR_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_LOADFLTR), MSG_LD_LOADFLTR_SIZE) == 0)) { select |= SEL_ACT_SPEC_2; variable = ENV_FLG_LOADFLTR; } } /* * LD_MACHCAP. */ else if (*s1 == 'M') { if ((len == MSG_LD_MACHCAP_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_MACHCAP), MSG_LD_MACHCAP_SIZE) == 0)) { select |= SEL_ACT_STR; str = (select & SEL_REPLACE) ? &rpl_machcap : &prm_machcap; variable = ENV_FLG_MACHCAP; } } /* * The LD_NO family. */ else if (*s1 == 'N') { if ((len == MSG_LD_NOAUDIT_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOAUDIT), MSG_LD_NOAUDIT_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_NOAUDIT; variable = ENV_FLG_NOAUDIT; } else if ((len == MSG_LD_NOAUXFLTR_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOAUXFLTR), MSG_LD_NOAUXFLTR_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_NOAUXFLTR; variable = ENV_FLG_NOAUXFLTR; } else if ((len == MSG_LD_NOBAPLT_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOBAPLT), MSG_LD_NOBAPLT_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_NOBAPLT; variable = ENV_FLG_NOBAPLT; } else if ((len == MSG_LD_NOCONFIG_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOCONFIG), MSG_LD_NOCONFIG_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_NOCFG; variable = ENV_FLG_NOCONFIG; } else if ((len == MSG_LD_NODIRCONFIG_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NODIRCONFIG), MSG_LD_NODIRCONFIG_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_NODIRCFG; variable = ENV_FLG_NODIRCONFIG; } else if ((len == MSG_LD_NODIRECT_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NODIRECT), MSG_LD_NODIRECT_SIZE) == 0)) { select |= SEL_ACT_LMLT; val = LML_TFLG_NODIRECT; variable = ENV_FLG_NODIRECT; } else if ((len == MSG_LD_NOENVCONFIG_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOENVCONFIG), MSG_LD_NOENVCONFIG_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_NOENVCFG; variable = ENV_FLG_NOENVCONFIG; } else if ((len == MSG_LD_NOFLTCONFIG_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOFLTCONFIG), MSG_LD_NOFLTCONFIG_SIZE) == 0)) { select |= SEL_ACT_RT2; val = RT_FL2_NOFLTCFG; variable = ENV_FLG_NOFLTCONFIG; } else if ((len == MSG_LD_NOLAZY_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOLAZY), MSG_LD_NOLAZY_SIZE) == 0)) { select |= SEL_ACT_LMLT; val = LML_TFLG_NOLAZYLD; variable = ENV_FLG_NOLAZY; } else if ((len == MSG_LD_NOOBJALTER_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOOBJALTER), MSG_LD_NOOBJALTER_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_NOOBJALT; variable = ENV_FLG_NOOBJALTER; } else if ((len == MSG_LD_NOVERSION_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOVERSION), MSG_LD_NOVERSION_SIZE) == 0)) { select |= SEL_ACT_RT; val = RT_FL_NOVERSION; variable = ENV_FLG_NOVERSION; } else if ((len == MSG_LD_NOUNRESWEAK_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOUNRESWEAK), MSG_LD_NOUNRESWEAK_SIZE) == 0)) { /* * LD_NOUNRESWEAK (internal, used by ldd(1)). */ select |= SEL_ACT_LML; val = LML_FLG_TRC_NOUNRESWEAK; variable = ENV_FLG_NOUNRESWEAK; } else if ((len == MSG_LD_NOPAREXT_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOPAREXT), MSG_LD_NOPAREXT_SIZE) == 0)) { select |= SEL_ACT_LML; val = LML_FLG_TRC_NOPAREXT; variable = ENV_FLG_NOPAREXT; } else if ((len == MSG_LD_NOENVIRON_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_NOENVIRON), MSG_LD_NOENVIRON_SIZE) == 0)) { /* * LD_NOENVIRON can only be set with ld.so.1 -e. */ select |= SEL_ACT_RT; val = RT_FL_NOENVIRON; variable = ENV_FLG_NOENVIRON; } } /* * LD_PLATCAP, LD_PRELOAD and LD_PROFILE family. */ else if (*s1 == 'P') { if ((len == MSG_LD_PLATCAP_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_PLATCAP), MSG_LD_PLATCAP_SIZE) == 0)) { select |= SEL_ACT_STR; str = (select & SEL_REPLACE) ? &rpl_platcap : &prm_platcap; variable = ENV_FLG_PLATCAP; } else if ((len == MSG_LD_PRELOAD_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_PRELOAD), MSG_LD_PRELOAD_SIZE) == 0)) { select |= SEL_ACT_STR; str = (select & SEL_REPLACE) ? &rpl_preload : &prm_preload; variable = ENV_FLG_PRELOAD; } else if ((len == MSG_LD_PROFILE_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_PROFILE), MSG_LD_PROFILE_SIZE) == 0)) { /* * Only one user library can be profiled at a time. */ select |= SEL_ACT_SPEC_2; variable = ENV_FLG_PROFILE; } else if ((len == MSG_LD_PROFILE_OUTPUT_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_PROFILE_OUTPUT), MSG_LD_PROFILE_OUTPUT_SIZE) == 0)) { /* * Only one user library can be profiled at a time. */ select |= SEL_ACT_STR; str = &profile_out; variable = ENV_FLG_PROFILE_OUTPUT; } } /* * LD_SFCAP and LD_SIGNAL. */ else if (*s1 == 'S') { if ((len == MSG_LD_SFCAP_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_SFCAP), MSG_LD_SFCAP_SIZE) == 0)) { select |= SEL_ACT_STR; str = (select & SEL_REPLACE) ? &rpl_sfcap : &prm_sfcap; variable = ENV_FLG_SFCAP; } else if ((len == MSG_LD_SIGNAL_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_SIGNAL), MSG_LD_SIGNAL_SIZE) == 0) && ((rtld_flags & RT_FL_SECURE) == 0)) { select |= SEL_ACT_SPEC_2; variable = ENV_FLG_SIGNAL; } } /* * The LD_TRACE family (internal, used by ldd(1)). This definition is * the key to enabling all other ldd(1) specific environment variables. * In case an auditor is called, which in turn might exec(2) a * subprocess, this variable is disabled, so that any subprocess * escapes ldd(1) processing. */ else if (*s1 == 'T') { if (((len == MSG_LD_TRACE_OBJS_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_TRACE_OBJS), MSG_LD_TRACE_OBJS_SIZE) == 0)) || ((len == MSG_LD_TRACE_OBJS_E_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_TRACE_OBJS_E), MSG_LD_TRACE_OBJS_E_SIZE) == 0))) { char *s0 = (char *)s1; select |= SEL_ACT_SPEC_2; variable = ENV_FLG_TRACE_OBJS; #if defined(__sparc) || defined(__x86) /* * The simplest way to "disable" this variable is to * truncate this string to "LD_'\0'". This string is * ignored by any ld.so.1 environment processing. * Use of such interfaces as unsetenv(3c) are overkill, * and would drag too much libc implementation detail * into ld.so.1. */ *s0 = '\0'; #else /* * Verify that the above write is appropriate for any new platforms. */ #error unsupported architecture! #endif } else if ((len == MSG_LD_TRACE_PTHS_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_TRACE_PTHS), MSG_LD_TRACE_PTHS_SIZE) == 0)) { select |= SEL_ACT_LML; val = LML_FLG_TRC_SEARCH; variable = ENV_FLG_TRACE_PTHS; } } /* * LD_UNREF and LD_UNUSED (internal, used by ldd(1)). */ else if (*s1 == 'U') { if ((len == MSG_LD_UNREF_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_UNREF), MSG_LD_UNREF_SIZE) == 0)) { select |= SEL_ACT_LML; val = LML_FLG_TRC_UNREF; variable = ENV_FLG_UNREF; } else if ((len == MSG_LD_UNUSED_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_UNUSED), MSG_LD_UNUSED_SIZE) == 0)) { select |= SEL_ACT_LML; val = LML_FLG_TRC_UNUSED; variable = ENV_FLG_UNUSED; } } /* * LD_VERBOSE (internal, used by ldd(1)). */ else if (*s1 == 'V') { if ((len == MSG_LD_VERBOSE_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_VERBOSE), MSG_LD_VERBOSE_SIZE) == 0)) { select |= SEL_ACT_LML; val = LML_FLG_TRC_VERBOSE; variable = ENV_FLG_VERBOSE; } } /* * LD_WARN (internal, used by ldd(1)). */ else if (*s1 == 'W') { if ((len == MSG_LD_WARN_SIZE) && (strncmp(s1, MSG_ORIG(MSG_LD_WARN), MSG_LD_WARN_SIZE) == 0)) { select |= SEL_ACT_LML; val = LML_FLG_TRC_WARN; variable = ENV_FLG_WARN; } } if (variable == 0) return; /* * If the variable is already processed with and ISA specific variable, * no further processing is needed. */ if (((select & SEL_REPLACE) && (rplisa & variable)) || ((select & SEL_PERMANT) && (prmisa & variable))) return; /* * If this variable has already been set via the command line, then * ignore this variable. The command line, -e, takes precedence. */ if (env_flags & ENV_TYP_ISA) { if (cmdisa & variable) return; if (env_flags & ENV_TYP_CMDLINE) cmdisa |= variable; } else { if (cmdgen & variable) return; if (env_flags & ENV_TYP_CMDLINE) cmdgen |= variable; } /* * Mark the appropriate variables. */ if (env_flags & ENV_TYP_ISA) { /* * This is an ISA setting. */ if (select & SEL_REPLACE) { if (rplisa & variable) return; rplisa |= variable; } else { prmisa |= variable; } } else { /* * This is a non-ISA setting. */ if (select & SEL_REPLACE) { if (rplgen & variable) return; rplgen |= variable; } else prmgen |= variable; } /* * Now perform the setting. */ if (select & SEL_ACT_RT) { if (s2) rtld_flags |= val; else rtld_flags &= ~val; } else if (select & SEL_ACT_RT2) { if (s2) rtld_flags2 |= val; else rtld_flags2 &= ~val; } else if (select & SEL_ACT_STR) { if (env_flags & ENV_TYP_NULL) *str = NULL; else *str = s2; } else if (select & SEL_ACT_LML) { if (s2) *lmflags |= val; else *lmflags &= ~val; } else if (select & SEL_ACT_LMLT) { if (s2) *lmtflags |= val; else *lmtflags &= ~val; } else if (select & SEL_ACT_SPEC_1) { /* * variable is either ENV_FLG_FLAGS or ENV_FLG_LIBPATH */ if (env_flags & ENV_TYP_NULL) *str = NULL; else *str = s2; if ((select & SEL_REPLACE) && (env_flags & ENV_TYP_CONFIG)) { if (s2) { if (variable == ENV_FLG_FLAGS) env_info |= ENV_INF_FLAGCFG; else env_info |= ENV_INF_PATHCFG; } else { if (variable == ENV_FLG_FLAGS) env_info &= ~ENV_INF_FLAGCFG; else env_info &= ~ENV_INF_PATHCFG; } } } else if (select & SEL_ACT_SPEC_2) { /* * variables can be: ENV_FLG_ * AUDIT_ARGS, BINDING, CONFGEN, LOADFLTR, PROFILE, * SIGNAL, TRACE_OBJS */ switch (variable) { case ENV_FLG_AUDIT_ARGS: if (s2) { audit_argcnt = atoi(s2); audit_argcnt += audit_argcnt % 2; } else audit_argcnt = 0; break; case ENV_FLG_BINDINGS: if (s2) rpl_debug = MSG_ORIG(MSG_TKN_BINDINGS); else rpl_debug = NULL; break; case ENV_FLG_CONFGEN: if (s2) { rtld_flags |= RT_FL_CONFGEN; *lmflags |= LML_FLG_IGNRELERR; } else { rtld_flags &= ~RT_FL_CONFGEN; *lmflags &= ~LML_FLG_IGNRELERR; } break; case ENV_FLG_LOADFLTR: if (s2) { *lmtflags |= LML_TFLG_LOADFLTR; if (*s2 == '2') rtld_flags |= RT_FL_WARNFLTR; } else { *lmtflags &= ~LML_TFLG_LOADFLTR; rtld_flags &= ~RT_FL_WARNFLTR; } break; case ENV_FLG_PROFILE: profile_name = s2; if (s2) { if (strcmp(s2, MSG_ORIG(MSG_FIL_RTLD)) == 0) { return; } /* BEGIN CSTYLED */ if (rtld_flags & RT_FL_SECURE) { profile_lib = #if defined(_ELF64) MSG_ORIG(MSG_PTH_LDPROFSE_64); #else MSG_ORIG(MSG_PTH_LDPROFSE); #endif } else { profile_lib = #if defined(_ELF64) MSG_ORIG(MSG_PTH_LDPROF_64); #else MSG_ORIG(MSG_PTH_LDPROF); #endif } /* END CSTYLED */ } else profile_lib = NULL; break; case ENV_FLG_SIGNAL: killsig = s2 ? atoi(s2) : SIGKILL; break; case ENV_FLG_TRACE_OBJS: if (s2) { *lmflags |= LML_FLG_TRC_ENABLE; if (*s2 == '2') *lmflags |= LML_FLG_TRC_LDDSTUB; } else *lmflags &= ~(LML_FLG_TRC_ENABLE | LML_FLG_TRC_LDDSTUB); break; } } } /* * Determine whether we have an architecture specific environment variable. * If we do, and we're the wrong architecture, it'll just get ignored. * Otherwise the variable is processed in it's architecture neutral form. */ static int ld_arch_env(const char *s1, size_t *len) { size_t _len = *len - 3; if (s1[_len++] == '_') { if ((s1[_len] == '3') && (s1[_len + 1] == '2')) { #if defined(_ELF64) return (ENV_TYP_IGNORE); #else *len = *len - 3; return (ENV_TYP_ISA); #endif } if ((s1[_len] == '6') && (s1[_len + 1] == '4')) { #if defined(_ELF64) *len = *len - 3; return (ENV_TYP_ISA); #else return (ENV_TYP_IGNORE); #endif } } return (0); } /* * Process an LD_FLAGS environment variable. The value can be a comma * separated set of tokens, which are sent (in upper case) into the generic * LD_XXXX environment variable engine. For example: * * LD_FLAGS=bind_now= -> LD_BIND_NOW= * LD_FLAGS=bind_now -> LD_BIND_NOW=1 * LD_FLAGS=library_path= -> LD_LIBRARY_PATH= * LD_FLAGS=library_path=/foo:. -> LD_LIBRARY_PATH=/foo:. * LD_FLAGS=debug=files:detail -> LD_DEBUG=files:detail * or * LD_FLAGS=bind_now,library_path=/foo:.,debug=files:detail */ static int ld_flags_env(const char *str, Word *lmflags, Word *lmtflags, uint_t env_flags) { char *nstr, *sstr, *estr = NULL; size_t nlen, len; if (str == NULL) return (0); /* * Create a new string as we're going to transform the token(s) into * uppercase and separate tokens with nulls. */ len = strlen(str); if ((nstr = malloc(len + 1)) == NULL) return (1); (void) strcpy(nstr, str); for (sstr = nstr; sstr; sstr++, len--) { int flags = 0; if ((*sstr != '\0') && (*sstr != ',')) { if (estr == NULL) { if (*sstr == '=') estr = sstr; else { /* * Translate token to uppercase. Don't * use toupper(3C) as including this * code doubles the size of ld.so.1. */ if ((*sstr >= 'a') && (*sstr <= 'z')) *sstr = *sstr - ('a' - 'A'); } } continue; } *sstr = '\0'; /* * Have we discovered an "=" string. */ if (estr) { nlen = estr - nstr; /* * If this is an unqualified "=", then this variable * is intended to ensure a feature is disabled. */ if ((*++estr == '\0') || (*estr == ',')) estr = NULL; } else { nlen = sstr - nstr; /* * If there is no "=" found, fabricate a boolean * definition for any unqualified variable. Thus, * LD_FLAGS=bind_now is represented as BIND_NOW=1. * The value "1" is sufficient to assert any boolean * variables. Setting of ENV_TYP_NULL ensures any * string usage is reset to a NULL string, thus * LD_FLAGS=library_path is equivalent to * LIBRARY_PATH='\0'. */ flags |= ENV_TYP_NULL; estr = (char *)MSG_ORIG(MSG_STR_ONE); } /* * Determine whether the environment variable is 32- or 64-bit * specific. The length, len, will reflect the architecture * neutral portion of the string. */ if ((flags |= ld_arch_env(nstr, &nlen)) != ENV_TYP_IGNORE) { ld_generic_env(nstr, nlen, estr, lmflags, lmtflags, (env_flags | flags)); } if (len == 0) break; nstr = sstr + 1; estr = NULL; } return (0); } /* * Variant of getopt(), intended for use when ld.so.1 is invoked directly * from the command line. The only command line option allowed is -e followed * by a runtime linker environment variable. */ int rtld_getopt(char **argv, char ***envp, auxv_t **auxv, Word *lmflags, Word *lmtflags) { int ndx; for (ndx = 1; argv[ndx]; ndx++) { char *str; if (argv[ndx][0] != '-') break; if (argv[ndx][1] == '\0') { ndx++; break; } if (argv[ndx][1] != 'e') return (1); if (argv[ndx][2] == '\0') { ndx++; if (argv[ndx] == NULL) return (1); str = argv[ndx]; } else str = &argv[ndx][2]; /* * If the environment variable starts with LD_, strip the LD_. * Otherwise, take things as is. Indicate that this variable * originates from the command line, as these variables take * precedence over any environment variables, or configuration * file variables. */ if ((str[0] == 'L') && (str[1] == 'D') && (str[2] == '_') && (str[3] != '\0')) str += 3; if (ld_flags_env(str, lmflags, lmtflags, ENV_TYP_CMDLINE) == 1) return (1); } /* * Make sure an object file has been specified. */ if (argv[ndx] == NULL) return (1); /* * Having gotten the arguments, clean ourselves off of the stack. * This results in a process that looks as if it was executed directly * from the application. */ stack_cleanup(argv, envp, auxv, ndx); return (0); } /* * Process a single LD_XXXX string. */ static void ld_str_env(const char *s1, Word *lmflags, Word *lmtflags, uint_t env_flags) { const char *s2; size_t len; int flags; /* * In a branded process we must ignore all LD_XXXX variables because * they are intended for the brand's linker. To affect the native * linker, use LD_BRAND_XXXX instead. */ if (rtld_flags2 & RT_FL2_BRANDED) { if (strncmp(s1, MSG_ORIG(MSG_LD_BRAND_PREFIX), MSG_LD_BRAND_PREFIX_SIZE) != 0) return; s1 += MSG_LD_BRAND_PREFIX_SIZE; } /* * Variables with no value (ie. LD_XXXX=) turn a capability off. */ if ((s2 = strchr(s1, '=')) == NULL) { len = strlen(s1); s2 = NULL; } else if (*++s2 == '\0') { len = strlen(s1) - 1; s2 = NULL; } else { len = s2 - s1 - 1; while (conv_strproc_isspace(*s2)) s2++; } /* * Determine whether the environment variable is 32-bit or 64-bit * specific. The length, len, will reflect the architecture neutral * portion of the string. */ if ((flags = ld_arch_env(s1, &len)) == ENV_TYP_IGNORE) return; env_flags |= flags; ld_generic_env(s1, len, s2, lmflags, lmtflags, env_flags); } /* * Internal getenv routine. Called immediately after ld.so.1 initializes * itself to process any locale specific environment variables, and collect * any LD_XXXX variables for later processing. */ #define LOC_LANG 1 #define LOC_MESG 2 #define LOC_ALL 3 int readenv_user(const char **envp, APlist **ealpp) { char *locale; const char *s1; int loc = 0; for (s1 = *envp; s1; envp++, s1 = *envp) { const char *s2; if (*s1++ != 'L') continue; /* * See if we have any locale environment settings. These * environment variables have a precedence, LC_ALL is higher * than LC_MESSAGES which is higher than LANG. */ s2 = s1; if ((*s2++ == 'C') && (*s2++ == '_') && (*s2 != '\0')) { if (strncmp(s2, MSG_ORIG(MSG_LC_ALL), MSG_LC_ALL_SIZE) == 0) { s2 += MSG_LC_ALL_SIZE; if ((*s2 != '\0') && (loc < LOC_ALL)) { glcs[CI_LCMESSAGES].lc_un.lc_ptr = (char *)s2; loc = LOC_ALL; } } else if (strncmp(s2, MSG_ORIG(MSG_LC_MESSAGES), MSG_LC_MESSAGES_SIZE) == 0) { s2 += MSG_LC_MESSAGES_SIZE; if ((*s2 != '\0') && (loc < LOC_MESG)) { glcs[CI_LCMESSAGES].lc_un.lc_ptr = (char *)s2; loc = LOC_MESG; } } continue; } s2 = s1; if ((*s2++ == 'A') && (*s2++ == 'N') && (*s2++ == 'G') && (*s2++ == '=') && (*s2 != '\0') && (loc < LOC_LANG)) { glcs[CI_LCMESSAGES].lc_un.lc_ptr = (char *)s2; loc = LOC_LANG; continue; } /* * Pick off any LD_XXXX environment variables. */ if ((*s1++ == 'D') && (*s1++ == '_') && (*s1 != '\0')) { if (aplist_append(ealpp, s1, AL_CNT_ENVIRON) == NULL) return (1); } } /* * If we have a locale setting make sure it's worth processing further. * C and POSIX locales don't need any processing. In addition, to * ensure no one escapes the /usr/lib/locale hierarchy, don't allow * the locale to contain a segment that leads upward in the file system * hierarchy (i.e. no '..' segments). Given that we'll be confined to * the /usr/lib/locale hierarchy, there is no need to extensively * validate the mode or ownership of any message file (as libc's * generic handling of message files does), or be concerned with * symbolic links that might otherwise send us elsewhere. Duplicate * the string so that new locale setting can generically cleanup any * previous locales. */ if ((locale = glcs[CI_LCMESSAGES].lc_un.lc_ptr) != NULL) { if (((*locale == 'C') && (*(locale + 1) == '\0')) || (strcmp(locale, MSG_ORIG(MSG_TKN_POSIX)) == 0) || (strstr(locale, MSG_ORIG(MSG_TKN_DOTDOT)) != NULL)) glcs[CI_LCMESSAGES].lc_un.lc_ptr = NULL; else glcs[CI_LCMESSAGES].lc_un.lc_ptr = strdup(locale); } return (0); } /* * Process any LD_XXXX environment variables collected by readenv_user(). */ int procenv_user(APlist *ealp, Word *lmflags, Word *lmtflags) { Aliste idx; const char *s1; for (APLIST_TRAVERSE(ealp, idx, s1)) ld_str_env(s1, lmflags, lmtflags, 0); /* * Having collected the best representation of any LD_FLAGS, process * these strings. */ if (rpl_ldflags) { if (ld_flags_env(rpl_ldflags, lmflags, lmtflags, 0) == 1) return (1); rpl_ldflags = NULL; } /* * Don't allow environment controlled auditing when tracing or if * explicitly disabled. Trigger all tracing modes from * LML_FLG_TRC_ENABLE. */ if ((*lmflags & LML_FLG_TRC_ENABLE) || (rtld_flags & RT_FL_NOAUDIT)) rpl_audit = profile_lib = profile_name = NULL; if ((*lmflags & LML_FLG_TRC_ENABLE) == 0) *lmflags &= ~LML_MSK_TRC; /* * If both LD_BIND_NOW and LD_BIND_LAZY are specified, the former wins. */ if ((rtld_flags2 & (RT_FL2_BINDNOW | RT_FL2_BINDLAZY)) == (RT_FL2_BINDNOW | RT_FL2_BINDLAZY)) rtld_flags2 &= ~RT_FL2_BINDLAZY; /* * When using ldd(1) -r or -d against an executable, assert -p. */ if ((*lmflags & (LML_FLG_TRC_WARN | LML_FLG_TRC_LDDSTUB)) == LML_FLG_TRC_WARN) *lmflags |= LML_FLG_TRC_NOPAREXT; return (0); } /* * Configuration environment processing. Called after the executable has been * processed (as the executable can specify its own configuration file). */ int readenv_config(Rtc_env * envtbl, Addr addr) { Word *lmflags = &(lml_main.lm_flags); Word *lmtflags = &(lml_main.lm_tflags); if (envtbl == NULL) return (0); while (envtbl->env_str) { uint_t env_flags = ENV_TYP_CONFIG; const char *s1 = (const char *)(envtbl->env_str + addr); if (envtbl->env_flags & RTC_ENV_PERMANT) env_flags |= ENV_TYP_PERMANT; if ((*s1++ == 'L') && (*s1++ == 'D') && (*s1++ == '_') && (*s1 != '\0')) ld_str_env(s1, lmflags, lmtflags, env_flags); envtbl++; } /* * Having collected the best representation of any LD_FLAGS, process * these strings. */ if (ld_flags_env(rpl_ldflags, lmflags, lmtflags, 0) == 1) return (1); if (ld_flags_env(prm_ldflags, lmflags, lmtflags, ENV_TYP_CONFIG) == 1) return (1); /* * Don't allow environment controlled auditing when tracing or if * explicitly disabled. Trigger all tracing modes from * LML_FLG_TRC_ENABLE. */ if ((*lmflags & LML_FLG_TRC_ENABLE) || (rtld_flags & RT_FL_NOAUDIT)) prm_audit = profile_lib = profile_name = NULL; if ((*lmflags & LML_FLG_TRC_ENABLE) == 0) *lmflags &= ~LML_MSK_TRC; return (0); } int dowrite(Prfbuf * prf) { /* * We do not have a valid file descriptor, so we are unable * to flush the buffer. */ if (prf->pr_fd == -1) return (0); (void) write(prf->pr_fd, prf->pr_buf, prf->pr_cur - prf->pr_buf); prf->pr_cur = prf->pr_buf; return (1); } /* * Simplified printing. The following conversion specifications are supported: * * % [#] [-] [min field width] [. precision] s|d|x|c * * * dorprf takes the output buffer in the form of Prfbuf which permits * the verification of the output buffer size and the concatenation * of data to an already existing output buffer. The Prfbuf * structure contains the following: * * pr_buf pointer to the beginning of the output buffer. * pr_cur pointer to the next available byte in the output buffer. By * setting pr_cur ahead of pr_buf you can append to an already * existing buffer. * pr_len the size of the output buffer. By setting pr_len to '0' you * disable protection from overflows in the output buffer. * pr_fd a pointer to the file-descriptor the buffer will eventually be * output to. If pr_fd is set to '-1' then it's assumed there is * no output buffer, and doprf() will return with an error to * indicate an output buffer overflow. If pr_fd is > -1 then when * the output buffer is filled it will be flushed to pr_fd and will * then be available for additional data. */ #define FLG_UT_MINUS 0x0001 /* - */ #define FLG_UT_SHARP 0x0002 /* # */ #define FLG_UT_DOTSEEN 0x0008 /* dot appeared in format spec */ /* * This macro is for use from within doprf only. It is to be used for checking * the output buffer size and placing characters into the buffer. */ #define PUTC(c) \ { \ char tmpc; \ \ tmpc = (c); \ if (bufsiz && (bp >= bufend)) { \ prf->pr_cur = bp; \ if (dowrite(prf) == 0) \ return (0); \ bp = prf->pr_cur; \ } \ *bp++ = tmpc; \ } /* * Define a local buffer size for building a numeric value - large enough to * hold a 64-bit value. */ #define NUM_SIZE 22 size_t doprf(const char *format, va_list args, Prfbuf *prf) { char c; char *bp = prf->pr_cur; char *bufend = prf->pr_buf + prf->pr_len; size_t bufsiz = prf->pr_len; while ((c = *format++) != '\0') { if (c != '%') { PUTC(c); } else { int base = 0, flag = 0, width = 0, prec = 0; size_t _i; int _c, _n; char *_s; int ls = 0; again: c = *format++; switch (c) { case '-': flag |= FLG_UT_MINUS; goto again; case '#': flag |= FLG_UT_SHARP; goto again; case '.': flag |= FLG_UT_DOTSEEN; goto again; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (flag & FLG_UT_DOTSEEN) prec = (prec * 10) + c - '0'; else width = (width * 10) + c - '0'; goto again; case 'x': case 'X': base = 16; break; case 'd': case 'D': case 'u': base = 10; flag &= ~FLG_UT_SHARP; break; case 'l': base = 10; ls++; /* number of l's (long or long long) */ if ((*format == 'l') || (*format == 'd') || (*format == 'D') || (*format == 'x') || (*format == 'X') || (*format == 'o') || (*format == 'O') || (*format == 'u') || (*format == 'U')) goto again; break; case 'o': case 'O': base = 8; break; case 'c': _c = va_arg(args, int); for (_i = 24; _i > 0; _i -= 8) { if ((c = ((_c >> _i) & 0x7f)) != 0) { PUTC(c); } } if ((c = ((_c >> _i) & 0x7f)) != 0) { PUTC(c); } break; case 's': _s = va_arg(args, char *); _i = strlen(_s); /* LINTED */ _n = (int)(width - _i); if (!prec) /* LINTED */ prec = (int)_i; if (width && !(flag & FLG_UT_MINUS)) { while (_n-- > 0) PUTC(' '); } while (((c = *_s++) != 0) && prec--) { PUTC(c); } if (width && (flag & FLG_UT_MINUS)) { while (_n-- > 0) PUTC(' '); } break; case '%': PUTC('%'); break; default: break; } /* * Numeric processing */ if (base) { char local[NUM_SIZE]; size_t ssize = 0, psize = 0; const char *string = MSG_ORIG(MSG_STR_HEXNUM); const char *prefix = MSG_ORIG(MSG_STR_EMPTY); u_longlong_t num; switch (ls) { case 0: /* int */ num = (u_longlong_t) va_arg(args, uint_t); break; case 1: /* long */ num = (u_longlong_t) va_arg(args, ulong_t); break; case 2: /* long long */ num = va_arg(args, u_longlong_t); break; } if (flag & FLG_UT_SHARP) { if (base == 16) { prefix = MSG_ORIG(MSG_STR_HEX); psize = 2; } else { prefix = MSG_ORIG(MSG_STR_ZERO); psize = 1; } } if ((base == 10) && (long)num < 0) { prefix = MSG_ORIG(MSG_STR_NEGATE); psize = MSG_STR_NEGATE_SIZE; num = (u_longlong_t)(-(longlong_t)num); } /* * Convert the numeric value into a local * string (stored in reverse order). */ _s = local; do { *_s++ = string[num % base]; num /= base; ssize++; } while (num); ASSERT(ssize < sizeof (local)); /* * Provide any precision or width padding. */ if (prec) { /* LINTED */ _n = (int)(prec - ssize); while ((_n-- > 0) && (ssize < sizeof (local))) { *_s++ = '0'; ssize++; } } if (width && !(flag & FLG_UT_MINUS)) { /* LINTED */ _n = (int)(width - ssize - psize); while (_n-- > 0) { PUTC(' '); } } /* * Print any prefix and the numeric string */ while (*prefix) PUTC(*prefix++); do { PUTC(*--_s); } while (_s > local); /* * Provide any width padding. */ if (width && (flag & FLG_UT_MINUS)) { /* LINTED */ _n = (int)(width - ssize - psize); while (_n-- > 0) PUTC(' '); } } } } PUTC('\0'); prf->pr_cur = bp; return (1); } static int doprintf(const char *format, va_list args, Prfbuf *prf) { char *ocur = prf->pr_cur; if (doprf(format, args, prf) == 0) return (0); /* LINTED */ return ((int)(prf->pr_cur - ocur)); } /* VARARGS2 */ int sprintf(char *buf, const char *format, ...) { va_list args; int len; Prfbuf prf; va_start(args, format); prf.pr_buf = prf.pr_cur = buf; prf.pr_len = 0; prf.pr_fd = -1; len = doprintf(format, args, &prf); va_end(args); /* * sprintf() return value excludes the terminating null byte. */ return (len - 1); } /* VARARGS3 */ int snprintf(char *buf, size_t n, const char *format, ...) { va_list args; int len; Prfbuf prf; va_start(args, format); prf.pr_buf = prf.pr_cur = buf; prf.pr_len = n; prf.pr_fd = -1; len = doprintf(format, args, &prf); va_end(args); return (len); } /* VARARGS2 */ int bufprint(Prfbuf *prf, const char *format, ...) { va_list args; int len; va_start(args, format); len = doprintf(format, args, prf); va_end(args); return (len); } /*PRINTFLIKE1*/ int printf(const char *format, ...) { va_list args; char buffer[ERRSIZE]; Prfbuf prf; va_start(args, format); prf.pr_buf = prf.pr_cur = buffer; prf.pr_len = ERRSIZE; prf.pr_fd = 1; (void) doprf(format, args, &prf); va_end(args); /* * Trim trailing '\0' form buffer */ prf.pr_cur--; return (dowrite(&prf)); } static char errbuf[ERRSIZE], *nextptr = errbuf, *prevptr = NULL; /* * All error messages go through eprintf(). During process initialization, * these messages are directed to the standard error, however once control has * been passed to the applications code these messages are stored in an internal * buffer for use with dlerror(). Note, fatal error conditions that may occur * while running the application will still cause a standard error message, see * rtldexit() in this file for details. * The RT_FL_APPLIC flag serves to indicate the transition between process * initialization and when the applications code is running. */ void veprintf(Lm_list *lml, Error error, const char *format, va_list args) { int overflow = 0; static int lock = 0; Prfbuf prf; if (lock || (nextptr == (errbuf + ERRSIZE))) return; /* * Note: this lock is here to prevent the same thread from recursively * entering itself during a eprintf. ie: during eprintf malloc() fails * and we try and call eprintf ... and then malloc() fails .... */ lock = 1; /* * If we have completed startup initialization, all error messages * must be saved. These are reported through dlerror(). If we're * still in the initialization stage, output the error directly and * add a newline. */ prf.pr_buf = prf.pr_cur = nextptr; prf.pr_len = ERRSIZE - (nextptr - errbuf); if ((rtld_flags & RT_FL_APPLIC) == 0) prf.pr_fd = 2; else prf.pr_fd = -1; if (error > ERR_NONE) { if ((error == ERR_FATAL) && (rtld_flags2 & RT_FL2_FTL2WARN)) error = ERR_WARNING; switch (error) { case ERR_WARNING_NF: if (err_strs[ERR_WARNING_NF] == NULL) err_strs[ERR_WARNING_NF] = MSG_INTL(MSG_ERR_WARNING); break; case ERR_WARNING: if (err_strs[ERR_WARNING] == NULL) err_strs[ERR_WARNING] = MSG_INTL(MSG_ERR_WARNING); break; case ERR_GUIDANCE: if (err_strs[ERR_GUIDANCE] == NULL) err_strs[ERR_GUIDANCE] = MSG_INTL(MSG_ERR_GUIDANCE); break; case ERR_ELF: if (err_strs[ERR_ELF] == NULL) err_strs[ERR_ELF] = MSG_INTL(MSG_ERR_ELF); break; /* If this API is mis-used, create a fatal error */ case ERR_FATAL: default: if (err_strs[ERR_FATAL] == NULL) err_strs[ERR_FATAL] = MSG_INTL(MSG_ERR_FATAL); break; } if (procname) { if (bufprint(&prf, MSG_ORIG(MSG_STR_EMSGFOR1), rtldname, procname, err_strs[error]) == 0) overflow = 1; } else { if (bufprint(&prf, MSG_ORIG(MSG_STR_EMSGFOR2), rtldname, err_strs[error]) == 0) overflow = 1; } if (overflow == 0) { /* * Remove the terminating '\0'. */ prf.pr_cur--; } } if ((overflow == 0) && doprf(format, args, &prf) == 0) overflow = 1; /* * If this is an ELF error, it will have been generated by a support * object that has a dependency on libelf. ld.so.1 doesn't generate any * ELF error messages as it doesn't interact with libelf. Determine the * ELF error string. */ if ((overflow == 0) && (error == ERR_ELF)) { static int (*elfeno)() = 0; static const char *(*elfemg)(); const char *emsg; Rt_map *dlmp, *lmp = lml_rtld.lm_head; if (NEXT(lmp) && (elfeno == 0)) { if (((elfemg = (const char *(*)())dlsym_intn(RTLD_NEXT, MSG_ORIG(MSG_SYM_ELFERRMSG), lmp, &dlmp)) == NULL) || ((elfeno = (int (*)())dlsym_intn(RTLD_NEXT, MSG_ORIG(MSG_SYM_ELFERRNO), lmp, &dlmp)) == NULL)) elfeno = 0; } /* * Lookup the message; equivalent to elf_errmsg(elf_errno()). */ if (elfeno && ((emsg = (* elfemg)((* elfeno)())) != NULL)) { prf.pr_cur--; if (bufprint(&prf, MSG_ORIG(MSG_STR_EMSGFOR2), emsg) == 0) overflow = 1; } } /* * Push out any message that's been built. Note, in the case of an * overflow condition, this message may be incomplete, in which case * make sure any partial string is null terminated. */ if ((rtld_flags & (RT_FL_APPLIC | RT_FL_SILENCERR)) == 0) { *(prf.pr_cur - 1) = '\n'; (void) dowrite(&prf); } if (overflow) *(prf.pr_cur - 1) = '\0'; DBG_CALL(Dbg_util_str(lml, nextptr)); /* * Determine if there was insufficient space left in the buffer to * complete the message. If so, we'll have printed out as much as had * been processed if we're not yet executing the application. * Otherwise, there will be some debugging diagnostic indicating * as much of the error message as possible. Write out a final buffer * overflow diagnostic - unlocalized, so we don't chance more errors. */ if (overflow) { char *str = (char *)MSG_INTL(MSG_EMG_BUFOVRFLW); if ((rtld_flags & RT_FL_SILENCERR) == 0) { lasterr = str; if ((rtld_flags & RT_FL_APPLIC) == 0) { (void) write(2, str, strlen(str)); (void) write(2, MSG_ORIG(MSG_STR_NL), MSG_STR_NL_SIZE); } } DBG_CALL(Dbg_util_str(lml, str)); lock = 0; nextptr = errbuf + ERRSIZE; return; } /* * If the application has started, then error messages are being saved * for retrieval by dlerror(), or possible flushing from rtldexit() in * the case of a fatal error. In this case, establish the next error * pointer. If we haven't started the application, the whole message * buffer can be reused. */ if ((rtld_flags & RT_FL_SILENCERR) == 0) { lasterr = nextptr; /* * Note, should we encounter an error such as ENOMEM, there may * be a number of the same error messages (ie. an operation * fails with ENOMEM, and then the attempts to construct the * error message itself, which incurs additional ENOMEM errors). * Compare any previous error message with the one we've just * created to prevent any duplication clutter. */ if ((rtld_flags & RT_FL_APPLIC) && ((prevptr == NULL) || (strcmp(prevptr, nextptr) != 0))) { prevptr = nextptr; nextptr = prf.pr_cur; *nextptr = '\0'; } } lock = 0; } /*PRINTFLIKE3*/ void eprintf(Lm_list *lml, Error error, const char *format, ...) { va_list args; va_start(args, format); veprintf(lml, error, format, args); va_end(args); } static const char rtld_panicstr[] = "rtld assertion failure"; /* * Provide assfail() for ASSERT() statements. See for further * details. */ void assfail(const char *a, const char *f, int l) { (void) printf("assertion failed: %s, file: %s, line: %d\n", a, f, l); (void) _lwp_kill(_lwp_self(), SIGABRT); upanic(rtld_panicstr, sizeof (rtld_panicstr)); } void assfail3(const char *msg, uintmax_t a, const char *op, uintmax_t b, const char *f, int l) { (void) printf("assertion failed: %s (0x%llx %s 0x%llx), " "file: %s, line: %d\n", msg, (unsigned long long)a, op, (unsigned long long)b, f, l); (void) _lwp_kill(_lwp_self(), SIGABRT); upanic(rtld_panicstr, sizeof (rtld_panicstr)); } /* * Exit. If we arrive here with a non zero status it's because of a fatal * error condition (most commonly a relocation error). If the application has * already had control, then the actual fatal error message will have been * recorded in the dlerror() message buffer. Print the message before really * exiting. */ void rtldexit(Lm_list * lml, int status) { if (status) { if (rtld_flags & RT_FL_APPLIC) { /* * If the error buffer has been used, write out all * pending messages - lasterr is simply a pointer to * the last message in this buffer. However, if the * buffer couldn't be created at all, lasterr points * to a constant error message string. */ if (*errbuf) { char *errptr = errbuf; char *errend = errbuf + ERRSIZE; while ((errptr < errend) && *errptr) { size_t size = strlen(errptr); (void) write(2, errptr, size); (void) write(2, MSG_ORIG(MSG_STR_NL), MSG_STR_NL_SIZE); errptr += (size + 1); } } if (lasterr && ((lasterr < errbuf) || (lasterr > (errbuf + ERRSIZE)))) { (void) write(2, lasterr, strlen(lasterr)); (void) write(2, MSG_ORIG(MSG_STR_NL), MSG_STR_NL_SIZE); } } leave(lml, 0); (void) _lwp_kill(_lwp_self(), killsig); } _exit(status); } /* * Map anonymous memory via MAP_ANON (added in Solaris 8). */ void * dz_map(Lm_list *lml, caddr_t addr, size_t len, int prot, int flags) { caddr_t va; if ((va = (caddr_t)mmap(addr, len, prot, (flags | MAP_ANON), -1, 0)) == MAP_FAILED) { int err = errno; eprintf(lml, ERR_FATAL, MSG_INTL(MSG_SYS_MMAPANON), strerror(err)); return (MAP_FAILED); } return (va); } static int nu_fd = FD_UNAVAIL; void * nu_map(Lm_list *lml, caddr_t addr, size_t len, int prot, int flags) { caddr_t va; int err; if (nu_fd == FD_UNAVAIL) { if ((nu_fd = open(MSG_ORIG(MSG_PTH_DEVNULL), O_RDONLY)) == FD_UNAVAIL) { err = errno; eprintf(lml, ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), MSG_ORIG(MSG_PTH_DEVNULL), strerror(err)); return (MAP_FAILED); } } if ((va = (caddr_t)mmap(addr, len, prot, flags, nu_fd, 0)) == MAP_FAILED) { err = errno; eprintf(lml, ERR_FATAL, MSG_INTL(MSG_SYS_MMAP), MSG_ORIG(MSG_PTH_DEVNULL), strerror(err)); } return (va); } /* * Generic entry point from user code - simply grabs a lock, and bumps the * entrance count. */ int enter(int flags) { if (rt_bind_guard(THR_FLG_RTLD | thr_flg_nolock | flags)) { if (!thr_flg_nolock) (void) rt_mutex_lock(&rtldlock); if (rtld_flags & RT_FL_OPERATION) { ld_entry_cnt++; /* * Reset the diagnostic time information for each new * "operation". Thus timing diagnostics are relative * to entering ld.so.1. */ if (DBG_ISTIME() && (gettimeofday(&DBG_TOTALTIME, NULL) == 0)) { DBG_DELTATIME = DBG_TOTALTIME; DBG_ONRESET(); } } return (1); } return (0); } /* * Determine whether a search path has been used. */ static void is_path_used(Lm_list *lml, Word unref, int *nl, Alist *alp, const char *obj) { Pdesc *pdp; Aliste idx; for (ALIST_TRAVERSE(alp, idx, pdp)) { const char *fmt, *name; if ((pdp->pd_plen == 0) || (pdp->pd_flags & PD_FLG_USED)) continue; /* * If this pathname originated from an expanded token, use the * original for any diagnostic output. */ if ((name = pdp->pd_oname) == NULL) name = pdp->pd_pname; if (unref == 0) { if ((*nl)++ == 0) DBG_CALL(Dbg_util_nl(lml, DBG_NL_STD)); DBG_CALL(Dbg_unused_path(lml, name, pdp->pd_flags, (pdp->pd_flags & PD_FLG_DUPLICAT), obj)); continue; } if (pdp->pd_flags & LA_SER_LIBPATH) { if (pdp->pd_flags & LA_SER_CONFIG) { if (pdp->pd_flags & PD_FLG_DUPLICAT) fmt = MSG_INTL(MSG_DUP_LDLIBPATHC); else fmt = MSG_INTL(MSG_USD_LDLIBPATHC); } else { if (pdp->pd_flags & PD_FLG_DUPLICAT) fmt = MSG_INTL(MSG_DUP_LDLIBPATH); else fmt = MSG_INTL(MSG_USD_LDLIBPATH); } } else if (pdp->pd_flags & LA_SER_RUNPATH) { fmt = MSG_INTL(MSG_USD_RUNPATH); } else continue; if ((*nl)++ == 0) (void) printf(MSG_ORIG(MSG_STR_NL)); (void) printf(fmt, name, obj); } } /* * Generate diagnostics as to whether an object has been used. A symbolic * reference that gets bound to an object marks it as used. Dependencies that * are unused when RTLD_NOW is in effect should be removed from future builds * of an object. Dependencies that are unused without RTLD_NOW in effect are * candidates for lazy-loading. * * Unreferenced objects identify objects that are defined as dependencies but * are unreferenced by the caller. These unreferenced objects may however be * referenced by other objects within the process, and therefore don't qualify * as completely unused. They are still an unnecessary overhead. * * Unreferenced runpaths are also captured under ldd -U, or "unused,detail" * debugging. */ void unused(Lm_list *lml) { Rt_map *lmp; int nl = 0; Word unref, unuse; /* * If we're not tracing unused references or dependencies, or debugging * there's nothing to do. */ unref = lml->lm_flags & LML_FLG_TRC_UNREF; unuse = lml->lm_flags & LML_FLG_TRC_UNUSED; if ((unref == 0) && (unuse == 0) && (DBG_ENABLED == 0)) return; /* * Detect unused global search paths. */ if (rpl_libdirs) is_path_used(lml, unref, &nl, rpl_libdirs, config->c_name); if (prm_libdirs) is_path_used(lml, unref, &nl, prm_libdirs, config->c_name); nl = 0; lmp = lml->lm_head; if (RLIST(lmp)) is_path_used(lml, unref, &nl, RLIST(lmp), NAME(lmp)); /* * Traverse the link-maps looking for unreferenced or unused * dependencies. Ignore the first object on a link-map list, as this * is always used. */ nl = 0; for (lmp = NEXT_RT_MAP(lmp); lmp; lmp = NEXT_RT_MAP(lmp)) { /* * Determine if this object contains any runpaths that have * not been used. */ if (RLIST(lmp)) is_path_used(lml, unref, &nl, RLIST(lmp), NAME(lmp)); /* * If tracing unreferenced objects, or under debugging, * determine whether any of this objects callers haven't * referenced it. */ if (unref || DBG_ENABLED) { Bnd_desc *bdp; Aliste idx; for (APLIST_TRAVERSE(CALLERS(lmp), idx, bdp)) { Rt_map *clmp; if (bdp->b_flags & BND_REFER) continue; clmp = bdp->b_caller; if (FLAGS1(clmp) & FL1_RT_LDDSTUB) continue; /* BEGIN CSTYLED */ if (nl++ == 0) { if (unref) (void) printf(MSG_ORIG(MSG_STR_NL)); else DBG_CALL(Dbg_util_nl(lml, DBG_NL_STD)); } if (unref) (void) printf(MSG_INTL(MSG_LDD_UNREF_FMT), NAME(lmp), NAME(clmp)); else DBG_CALL(Dbg_unused_unref(lmp, NAME(clmp))); /* END CSTYLED */ } } /* * If tracing unused objects simply display those objects that * haven't been referenced by anyone. */ if (FLAGS1(lmp) & FL1_RT_USED) continue; if (nl++ == 0) { if (unref || unuse) (void) printf(MSG_ORIG(MSG_STR_NL)); else DBG_CALL(Dbg_util_nl(lml, DBG_NL_STD)); } if (CYCGROUP(lmp)) { if (unref || unuse) (void) printf(MSG_INTL(MSG_LDD_UNCYC_FMT), NAME(lmp), CYCGROUP(lmp)); else DBG_CALL(Dbg_unused_file(lml, NAME(lmp), 0, CYCGROUP(lmp))); } else { if (unref || unuse) (void) printf(MSG_INTL(MSG_LDD_UNUSED_FMT), NAME(lmp)); else DBG_CALL(Dbg_unused_file(lml, NAME(lmp), 0, 0)); } } DBG_CALL(Dbg_util_nl(lml, DBG_NL_STD)); } /* * Generic cleanup routine called prior to returning control to the user. * Ensures that any ld.so.1 specific file descriptors or temporary mapping are * released, and any locks dropped. */ void leave(Lm_list *lml, int flags) { /* * Alert the debuggers that the link-maps are consistent. */ rd_event(lml, RD_DLACTIVITY, RT_CONSISTENT); /* * Alert any auditors that the link-maps are consistent. */ if (lml->lm_flags & LML_FLG_ACTAUDIT) { audit_activity(lml->lm_head, LA_ACT_CONSISTENT); lml->lm_flags &= ~LML_FLG_ACTAUDIT; } if (nu_fd != FD_UNAVAIL) { (void) close(nu_fd); nu_fd = FD_UNAVAIL; } /* * Reinitialize error message pointer, and any overflow indication. */ nextptr = errbuf; prevptr = NULL; /* * Defragment any freed memory. */ if (aplist_nitems(free_alp)) defrag(); /* * Don't drop our lock if we are running on our link-map list as * there's little point in doing so since we are single-threaded. * * LML_FLG_HOLDLOCK is set for: * - The ld.so.1's link-map list. * - The auditor's link-map if the environment is pre-UPM. */ if (lml->lm_flags & LML_FLG_HOLDLOCK) return; if (rt_bind_clear(0) & THR_FLG_RTLD) { if (!thr_flg_nolock) (void) rt_mutex_unlock(&rtldlock); (void) rt_bind_clear(THR_FLG_RTLD | thr_flg_nolock | flags); } } int callable(Rt_map *clmp, Rt_map *dlmp, Grp_hdl *ghp, uint_t slflags) { APlist *calp, *dalp; Aliste idx1, idx2; Grp_hdl *ghp1, *ghp2; /* * An object can always find symbols within itself. */ if (clmp == dlmp) return (1); /* * The search for a singleton must look in every loaded object. */ if (slflags & LKUP_SINGLETON) return (1); /* * Don't allow an object to bind to an object that is being deleted * unless the binder is also being deleted. */ if ((FLAGS(dlmp) & FLG_RT_DELETE) && ((FLAGS(clmp) & FLG_RT_DELETE) == 0)) return (0); /* * An object with world access can always bind to an object with global * visibility. */ if (((MODE(clmp) & RTLD_WORLD) || (slflags & LKUP_WORLD)) && (MODE(dlmp) & RTLD_GLOBAL)) return (1); /* * An object with local access can only bind to an object that is a * member of the same group. */ if (((MODE(clmp) & RTLD_GROUP) == 0) || ((calp = GROUPS(clmp)) == NULL) || ((dalp = GROUPS(dlmp)) == NULL)) return (0); /* * Traverse the list of groups the caller is a part of. */ for (APLIST_TRAVERSE(calp, idx1, ghp1)) { /* * If we're testing for the ability of two objects to bind to * each other regardless of a specific group, ignore that group. */ if (ghp && (ghp1 == ghp)) continue; /* * Traverse the list of groups the destination is a part of. */ for (APLIST_TRAVERSE(dalp, idx2, ghp2)) { Grp_desc *gdp; Aliste idx3; if (ghp1 != ghp2) continue; /* * Make sure the relationship between the destination * and the caller provide symbols for relocation. * Parents are maintained as callers, but unless the * destination object was opened with RTLD_PARENT, the * parent doesn't provide symbols for the destination * to relocate against. */ for (ALIST_TRAVERSE(ghp2->gh_depends, idx3, gdp)) { if (dlmp != gdp->gd_depend) continue; if (gdp->gd_flags & GPD_RELOC) return (1); } } } return (0); } /* * Initialize the environ symbol. Traditionally this is carried out by the crt * code prior to jumping to main. However, init sections get fired before this * variable is initialized, so ld.so.1 sets this directly from the AUX vector * information. In addition, a process may have multiple link-maps (ld.so.1's * debugging and preloading objects), and link auditing, and each may need an * environ variable set. * * This routine is called after a relocation() pass, and thus provides for: * * - setting environ on the main link-map after the initial application and * its dependencies have been established. Typically environ lives in the * application (provided by its crt), but in older applications it might * be in libc. Who knows what's expected of applications not built on * Solaris. * * - after loading a new shared object. We can add shared objects to various * link-maps, and any link-map dependencies requiring getopt() require * their own environ. In addition, lazy loading might bring in the * supplier of environ (libc used to be a lazy loading candidate) after * the link-map has been established and other objects are present. * * This routine handles all these scenarios, without adding unnecessary overhead * to ld.so.1. */ void set_environ(Lm_list *lml) { Slookup sl; Sresult sr; uint_t binfo; /* * Initialize the symbol lookup, and symbol result, data structures. */ SLOOKUP_INIT(sl, MSG_ORIG(MSG_SYM_ENVIRON), lml->lm_head, lml->lm_head, ld_entry_cnt, 0, 0, 0, 0, LKUP_WEAK); SRESULT_INIT(sr, MSG_ORIG(MSG_SYM_ENVIRON)); if (LM_LOOKUP_SYM(lml->lm_head)(&sl, &sr, &binfo, 0)) { Rt_map *dlmp = sr.sr_dmap; lml->lm_environ = (char ***)sr.sr_sym->st_value; if (!(FLAGS(dlmp) & FLG_RT_FIXED)) lml->lm_environ = (char ***)((uintptr_t)lml->lm_environ + (uintptr_t)ADDR(dlmp)); *(lml->lm_environ) = (char **)environ; lml->lm_flags |= LML_FLG_ENVIRON; } } /* * Determine whether we have a secure executable. Uid and gid information * can be passed to us via the aux vector, however if these values are -1 * then use the appropriate system call to obtain them. * * - If the user is the root they can do anything * * - If the real and effective uid's don't match, or the real and * effective gid's don't match then this is determined to be a `secure' * application. * * This function is called prior to any dependency processing (see _setup.c). * Any secure setting will remain in effect for the life of the process. */ void security(uid_t uid, uid_t euid, gid_t gid, gid_t egid, int auxflags) { if (auxflags != -1) { if ((auxflags & AF_SUN_SETUGID) != 0) rtld_flags |= RT_FL_SECURE; return; } if (uid == (uid_t)-1) uid = getuid(); if (uid) { if (euid == (uid_t)-1) euid = geteuid(); if (uid != euid) rtld_flags |= RT_FL_SECURE; else { if (gid == (gid_t)-1) gid = getgid(); if (egid == (gid_t)-1) egid = getegid(); if (gid != egid) rtld_flags |= RT_FL_SECURE; } } } /* * Determine whether ld.so.1 itself is owned by root and has its mode setuid. */ int is_rtld_setuid() { rtld_stat_t status; const char *name; if (rtld_flags2 & RT_FL2_SETUID) return (1); if (interp && interp->i_name) name = interp->i_name; else name = NAME(lml_rtld.lm_head); if (((rtld_stat(name, &status) == 0) && (status.st_uid == 0) && (status.st_mode & S_ISUID))) { rtld_flags2 |= RT_FL2_SETUID; return (1); } return (0); } /* * Determine that systems platform name. Normally, this name is provided from * the AT_SUN_PLATFORM aux vector from the kernel. This routine provides a * fall back. */ void platform_name(Syscapset *scapset) { char info[SYS_NMLN]; size_t size; if ((scapset->sc_platsz = size = sysinfo(SI_PLATFORM, info, SYS_NMLN)) == (size_t)-1) return; if ((scapset->sc_plat = malloc(size)) == NULL) { scapset->sc_platsz = (size_t)-1; return; } (void) strcpy(scapset->sc_plat, info); } /* * Determine that systems machine name. Normally, this name is provided from * the AT_SUN_MACHINE aux vector from the kernel. This routine provides a * fall back. */ void machine_name(Syscapset *scapset) { char info[SYS_NMLN]; size_t size; if ((scapset->sc_machsz = size = sysinfo(SI_MACHINE, info, SYS_NMLN)) == (size_t)-1) return; if ((scapset->sc_mach = malloc(size)) == NULL) { scapset->sc_machsz = (size_t)-1; return; } (void) strcpy(scapset->sc_mach, info); } /* * _REENTRANT code gets errno redefined to a function so provide for return * of the thread errno if applicable. This has no meaning in ld.so.1 which * is basically singled threaded. Provide the interface for our dependencies. */ #undef errno int * ___errno() { extern int errno; return (&errno); } /* * Determine whether a symbol name should be demangled. */ const char * demangle(const char *name) { if (rtld_flags & RT_FL_DEMANGLE) return (conv_demangle_name(name)); else return (name); } #ifndef _LP64 /* * Wrappers on stat() and fstat() for 32-bit rtld that uses stat64() * underneath while preserving the object size limits of a non-largefile * enabled 32-bit process. The purpose of this is to prevent large inode * values from causing stat() to fail. */ inline static int rtld_stat_process(int r, struct stat64 *lbuf, rtld_stat_t *restrict buf) { extern int errno; /* * Although we used a 64-bit capable stat(), the 32-bit rtld * can only handle objects < 2GB in size. If this object is * too big, turn the success into an overflow error. */ if ((lbuf->st_size & 0xffffffff80000000) != 0) { errno = EOVERFLOW; return (-1); } /* * Transfer the information needed by rtld into a rtld_stat_t * structure that preserves the non-largile types for everything * except inode. */ buf->st_dev = lbuf->st_dev; buf->st_ino = lbuf->st_ino; buf->st_mode = lbuf->st_mode; buf->st_uid = lbuf->st_uid; buf->st_size = (off_t)lbuf->st_size; buf->st_mtim = lbuf->st_mtim; #ifdef sparc buf->st_blksize = lbuf->st_blksize; #endif return (r); } int rtld_stat(const char *restrict path, rtld_stat_t *restrict buf) { struct stat64 lbuf; int r; r = stat64(path, &lbuf); if (r != -1) r = rtld_stat_process(r, &lbuf, buf); return (r); } int rtld_fstat(int fildes, rtld_stat_t *restrict buf) { struct stat64 lbuf; int r; r = fstat64(fildes, &lbuf); if (r != -1) r = rtld_stat_process(r, &lbuf, buf); return (r); } #endif # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # #ident "%Z%%M% %I% %E% SMI" # # Copyright (c) 1996 by Sun Microsystems, Inc. # All rights reserved. include $(SRC)/cmd/sgs/Makefile.sub # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # LIBRARY= rtld.a VERS= .1 COMOBJS= rtld.o BLTOBJ= msg.o OBJECTS= $(BLTOBJ) $(COMOBJS) # include library definitions include $(SRC)/lib/Makefile.lib include $(SRC)/cmd/sgs/Makefile.com SGSMSGALL= rtld.msg MAPFILES = ../common/mapfile SRCS= $(OBJECTS:%.o=../common/%.c) BLTDEFS= msg.h BLTDATA= msg.c BLTFILES= $(BLTDEFS) $(BLTDATA) SGSMSGTARG= ../common/rtld.msg SGSMSGALL= ../common/rtld.msg SGSMSGFLAGS += -h $(BLTDEFS) -d $(BLTDATA) CERRWARN += $(CNOWARN_UNINIT) MSGSRCS= $(COMOBJS:%.o=../common/%.c) $(BLTDATA) FILEMODE= 555 DYNLIB= ld.so LIBS = $(DYNLIB) CPPFLAGS += -I. -I../common -I../../common -I../../../include \ -I../../../include/$(MACH) \ -I$(SRC)/lib/libc/inc \ -I$(SRC)/uts/common/krtld \ -I$(SRC)/common/sgsrtcid \ -I$(SRC)/uts/$(ARCH)/sys DYNFLAGS += $(VERSREF) LDLIBS += $(CONVLIBDIR) -lconv -lc # Hammerhead: MDB debugger module - mdb_printf, mdb_warn, mdb_vread, etc. # are provided by the MDB framework at load time. ZDEFS = ROOTMDBLIB= $(ROOT)/usr/lib/mdb/proc ROOTMDBLIB64= $(ROOTMDBLIB)/$(MACH64) ROOTMDBLIBS= $(ROOTMDBLIB)/$(MTARG)$(DYNLIB) CLEANFILES += $(BLTFILES) # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # catalog: pics/%.o: ../common/%.c $(COMPILE.c) -o $@ $< $(POST_PROCESS_O) include $(SRC)/lib/Makefile.targ chkmsg: $(MSGSRCS) sh $(CHKMSG) $(CHKMSGFLAGS) $(MSGSRCS) # Hammerhead: GNU Make grouped target syntax (&:) - was dmake + syntax $(BLTDEFS) $(BLTDATA) $(BLTMESG) &: $(SGSMSGALL) $(SGSMSG) $(SGSMSGFLAGS) $(SGSMSGALL) $(ROOTMDBLIB)/%: % $(INS.file) $(ROOTMDBLIB64)/%: % $(INS.file) # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # include ../Makefile.com CONVLIBDIR = $(CONVLIBDIR64) MTARG = amd64/ CPPFLAGS += -D_ELF64 .KEEP_STATE: all: $(BLTDEFS) .WAIT $(DYNLIB) install: all .WAIT $(ROOTMDBLIBS) include ../Makefile.targ include $(SRC)/Makefile.master.64 # # Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 SYMBOL_VERSION SUNWprivate_1.1 { global: # # Exported Interface # _mdb_init; local: *; }; SYMBOL_SCOPE { # # Imported Interface # mdb_alloc { FLAGS = EXTERN }; mdb_call_dcmd { FLAGS = EXTERN }; mdb_dec_indent { FLAGS = EXTERN }; mdb_getopts { FLAGS = EXTERN }; mdb_inc_indent { FLAGS = EXTERN }; mdb_lookup_by_obj { FLAGS = EXTERN }; mdb_printf { FLAGS = EXTERN }; mdb_pwalk { FLAGS = EXTERN }; mdb_readstr { FLAGS = EXTERN }; mdb_set_dot { FLAGS = EXTERN }; mdb_vread { FLAGS = EXTERN }; mdb_warn { FLAGS = EXTERN }; }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include <_rtld.h> #include #include #include #include /* * Data structure for walkers. */ typedef struct { uint_t w_flags; } W_desc; /* * Flags values for dcmds */ #define RTLD_FLG_VERBOSE 0x0001 /* verbose output */ static const mdb_bitmask_t rtflags_bits[] = { { MSG_ORIG(MSG_FLG_ISMAIN), FLG_RT_ISMAIN, FLG_RT_ISMAIN}, { MSG_ORIG(MSG_FLG_IMGALLOC), FLG_RT_IMGALLOC, FLG_RT_IMGALLOC}, { MSG_ORIG(MSG_FLG_RELOCED), FLG_RT_RELOCED, FLG_RT_RELOCED}, { MSG_ORIG(MSG_FLG_SETGROUP), FLG_RT_SETGROUP, FLG_RT_SETGROUP}, { MSG_ORIG(MSG_FLG_CAP), FLG_RT_CAP, FLG_RT_CAP}, { MSG_ORIG(MSG_FLG_OBJECT), FLG_RT_OBJECT, FLG_RT_OBJECT}, { MSG_ORIG(MSG_FLG_NEWLOAD), FLG_RT_NEWLOAD, FLG_RT_NEWLOAD}, { MSG_ORIG(MSG_FLG_NODUMP), FLG_RT_NODUMP, FLG_RT_NODUMP}, { MSG_ORIG(MSG_FLG_DELETE), FLG_RT_DELETE, FLG_RT_DELETE}, { MSG_ORIG(MSG_FLG_ANALYZED), FLG_RT_ANALYZED, FLG_RT_ANALYZED}, { MSG_ORIG(MSG_FLG_INITDONE), FLG_RT_INITDONE, FLG_RT_INITDONE}, { MSG_ORIG(MSG_FLG_TRANS), FLG_RT_TRANS, FLG_RT_TRANS}, { MSG_ORIG(MSG_FLG_FIXED), FLG_RT_FIXED, FLG_RT_FIXED}, { MSG_ORIG(MSG_FLG_PRELOAD), FLG_RT_PRELOAD, FLG_RT_PRELOAD}, { MSG_ORIG(MSG_FLG_ALTER), FLG_RT_ALTER, FLG_RT_ALTER}, { MSG_ORIG(MSG_FLG_LOADFLTR), FLG_RT_LOADFLTR, FLG_RT_LOADFLTR}, { MSG_ORIG(MSG_FLG_AUDIT), FLG_RT_AUDIT, FLG_RT_AUDIT}, { MSG_ORIG(MSG_FLG_MODESET), FLG_RT_MODESET, FLG_RT_MODESET}, { MSG_ORIG(MSG_FLG_ANALZING), FLG_RT_ANALZING, FLG_RT_ANALZING}, { MSG_ORIG(MSG_FLG_INITFRST), FLG_RT_INITFRST, FLG_RT_INITFRST}, { MSG_ORIG(MSG_FLG_NOOPEN), FLG_RT_NOOPEN, FLG_RT_NOOPEN}, { MSG_ORIG(MSG_FLG_FINICLCT), FLG_RT_FINICLCT, FLG_RT_FINICLCT}, { MSG_ORIG(MSG_FLG_INITCALL), FLG_RT_INITCALL, FLG_RT_INITCALL}, { MSG_ORIG(MSG_FLG_OBJINTPO), FLG_RT_OBJINTPO, FLG_RT_OBJINTPO}, { MSG_ORIG(MSG_FLG_SYMINTPO), FLG_RT_SYMINTPO, FLG_RT_SYMINTPO}, { MSG_ORIG(MSG_FLG_MOVE), FLG_RT_MOVE, FLG_RT_MOVE}, { MSG_ORIG(MSG_FLG_RELOCING), FLG_RT_RELOCING, FLG_RT_RELOCING}, { MSG_ORIG(MSG_FLG_REGSYMS), FLG_RT_REGSYMS, FLG_RT_REGSYMS}, { MSG_ORIG(MSG_FLG_INITCLCT), FLG_RT_INITCLCT, FLG_RT_INITCLCT}, { MSG_ORIG(MSG_FLG_PUBHDL), FLG_RT_PUBHDL, FLG_RT_PUBHDL}, { MSG_ORIG(MSG_FLG_PRIHDL), FLG_RT_PRIHDL, FLG_RT_PRIHDL}, { NULL, 0, 0} }; static const mdb_bitmask_t rtflags1_bits[] = { { MSG_ORIG(MSG_FL1_COPYTOOK), FL1_RT_COPYTOOK, FL1_RT_COPYTOOK}, { MSG_ORIG(MSG_FL1_ALTCHECK), FL1_RT_ALTCHECK, FL1_RT_ALTCHECK}, { MSG_ORIG(MSG_FL1_ALTCAP), FL1_RT_ALTCAP, FL1_RT_ALTCAP}, { MSG_ORIG(MSG_FL1_CONFSET), FL1_RT_CONFSET, FL1_RT_CONFSET }, { MSG_ORIG(MSG_FL1_NODEFLIB), FL1_RT_NODEFLIB, FL1_RT_NODEFLIB }, { MSG_ORIG(MSG_FL1_ENDFILTE), FL1_RT_ENDFILTE, FL1_RT_ENDFILTE }, { MSG_ORIG(MSG_FL1_DISPREL), FL1_RT_DISPREL, FL1_RT_DISPREL }, { MSG_ORIG(MSG_FL1_DTFLAGS), FL1_RT_DTFLAGS, FL1_RT_DTFLAGS}, { MSG_ORIG(MSG_FL1_LDDSTUB), FL1_RT_LDDSTUB, FL1_RT_LDDSTUB}, { MSG_ORIG(MSG_FL1_NOINIFIN), FL1_RT_NOINIFIN, FL1_RT_NOINIFIN }, { MSG_ORIG(MSG_FL1_USED), FL1_RT_USED, FL1_RT_USED }, { MSG_ORIG(MSG_FL1_SYMBOLIC), FL1_RT_SYMBOLIC, FL1_RT_SYMBOLIC }, { MSG_ORIG(MSG_FL1_OBJSFLTR), FL1_RT_OBJSFLTR, FL1_RT_OBJSFLTR }, { MSG_ORIG(MSG_FL1_OBJAFLTR), FL1_RT_OBJAFLTR, FL1_RT_OBJAFLTR }, { MSG_ORIG(MSG_FL1_SYMSFLTR), FL1_RT_SYMSFLTR, FL1_RT_SYMSFLTR }, { MSG_ORIG(MSG_FL1_SYMAFLTR), FL1_RT_SYMAFLTR, FL1_RT_SYMAFLTR }, { MSG_ORIG(MSG_FL1_TLSADD), FL1_RT_TLSADD, FL1_RT_TLSADD }, { MSG_ORIG(MSG_FL1_TLSSTAT), FL1_RT_TLSSTAT, FL1_RT_TLSSTAT }, { MSG_ORIG(MSG_FL1_DIRECT), FL1_RT_DIRECT, FL1_RT_DIRECT}, { MSG_ORIG(MSG_FL1_GLOBAUD), FL1_RT_GLOBAUD, FL1_RT_GLOBAUD}, { MSG_ORIG(MSG_FL1_DEPAUD), FL1_RT_DEPAUD, FL1_RT_DEPAUD}, { NULL, 0, 0} }; static const mdb_bitmask_t rtaflags_bits[] = { { MSG_ORIG(MSG_LTFL_AUD_PREINIT), LML_TFLG_AUD_PREINIT, LML_TFLG_AUD_PREINIT }, { MSG_ORIG(MSG_LTFL_AUD_OBJSEARCH), LML_TFLG_AUD_OBJSEARCH, LML_TFLG_AUD_OBJSEARCH }, { MSG_ORIG(MSG_LTFL_AUD_OBJOPEN), LML_TFLG_AUD_OBJOPEN, LML_TFLG_AUD_OBJOPEN }, { MSG_ORIG(MSG_LTFL_AUD_OBJFILTER), LML_TFLG_AUD_OBJFILTER, LML_TFLG_AUD_OBJFILTER }, { MSG_ORIG(MSG_LTFL_AUD_OBJCLOSE), LML_TFLG_AUD_OBJCLOSE, LML_TFLG_AUD_OBJCLOSE }, { MSG_ORIG(MSG_LTFL_AUD_SYMBIND), LML_TFLG_AUD_SYMBIND, LML_TFLG_AUD_SYMBIND }, { MSG_ORIG(MSG_LTFL_AUD_PLTENTER), LML_TFLG_AUD_PLTENTER, LML_TFLG_AUD_PLTENTER }, { MSG_ORIG(MSG_LTFL_AUD_PLTEXIT), LML_TFLG_AUD_PLTEXIT, LML_TFLG_AUD_PLTEXIT }, { MSG_ORIG(MSG_LTFL_AUD_ACTIVITY), LML_TFLG_AUD_ACTIVITY, LML_TFLG_AUD_ACTIVITY }, { NULL, 0, 0} }; static const mdb_bitmask_t rtmode_bits[] = { { MSG_ORIG(MSG_MODE_LAZY), RTLD_LAZY, RTLD_LAZY }, { MSG_ORIG(MSG_MODE_NOW), RTLD_NOW, RTLD_NOW }, { MSG_ORIG(MSG_MODE_NOLOAD), RTLD_NOLOAD, RTLD_NOLOAD }, { MSG_ORIG(MSG_MODE_GLOBAL), RTLD_GLOBAL, RTLD_GLOBAL }, { MSG_ORIG(MSG_MODE_PARENT), RTLD_PARENT, RTLD_PARENT }, { MSG_ORIG(MSG_MODE_GROUP), RTLD_GROUP, RTLD_GROUP }, { MSG_ORIG(MSG_MODE_WORLD), RTLD_WORLD, RTLD_WORLD }, { MSG_ORIG(MSG_MODE_NODELETE), RTLD_NODELETE, RTLD_NODELETE }, { MSG_ORIG(MSG_MODE_FIRST), RTLD_FIRST, RTLD_FIRST }, { MSG_ORIG(MSG_MODE_CONFGEN), RTLD_CONFGEN, RTLD_CONFGEN }, { NULL, 0, 0} }; static const mdb_bitmask_t bndflags_bits[] = { { MSG_ORIG(MSG_BFL_NEEDED), BND_NEEDED, BND_NEEDED }, { MSG_ORIG(MSG_BFL_REFER), BND_REFER, BND_REFER }, { MSG_ORIG(MSG_BFL_FILTER), BND_FILTER, BND_FILTER }, { NULL, 0, 0} }; static const mdb_bitmask_t grhflags_bits[] = { { MSG_ORIG(MSG_GPH_PUBLIC), GPH_PUBLIC, GPH_PUBLIC }, { MSG_ORIG(MSG_GPH_PRIVATE), GPH_PRIVATE, GPH_PRIVATE }, { MSG_ORIG(MSG_GPH_ZERO), GPH_ZERO, GPH_ZERO }, { MSG_ORIG(MSG_GPH_LDSO), GPH_LDSO, GPH_LDSO }, { MSG_ORIG(MSG_GPH_FIRST), GPH_FIRST, GPH_FIRST }, { MSG_ORIG(MSG_GPH_FILTEE), GPH_FILTEE, GPH_FILTEE }, { MSG_ORIG(MSG_GPH_INITIAL), GPH_INITIAL, GPH_INITIAL }, { NULL, 0, 0} }; static const mdb_bitmask_t grdflags_bits[] = { { MSG_ORIG(MSG_GPD_DLSYM), GPD_DLSYM, GPD_DLSYM }, { MSG_ORIG(MSG_GPD_RELOC), GPD_RELOC, GPD_RELOC }, { MSG_ORIG(MSG_GPD_ADDEPS), GPD_ADDEPS, GPD_ADDEPS }, { MSG_ORIG(MSG_GPD_PARENT), GPD_PARENT, GPD_PARENT }, { MSG_ORIG(MSG_GPD_FILTER), GPD_FILTER, GPD_FILTER }, { MSG_ORIG(MSG_GPD_REMOVE), GPD_REMOVE, GPD_REMOVE }, { NULL, 0, 0} }; static const mdb_bitmask_t lmc_bits[] = { { MSG_ORIG(MSG_LMC_ANALYZING), LMC_FLG_ANALYZING, LMC_FLG_ANALYZING}, { MSG_ORIG(MSG_LMC_RELOCATING), LMC_FLG_RELOCATING, LMC_FLG_RELOCATING}, { MSG_ORIG(MSG_LMC_REANALYZE), LMC_FLG_REANALYZE, LMC_FLG_REANALYZE}, { NULL, 0, 0} }; /* * Obtain a string - typically a link-map name. */ static char * String(uintptr_t addr, const char *name) { static char str[MAXPATHLEN]; if (addr) { if (mdb_readstr(str, MAXPATHLEN, addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), name, addr); return (0); } return (str); } return ((char *)MSG_ORIG(MSG_STR_EMPTY)); } /* * Obtain a link-map name. */ static char * Rtmap_Name(uintptr_t addr) { Rt_map rtmap; if (addr) { if (mdb_vread(&rtmap, sizeof (Rt_map), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_RTMAP_STR), addr); return (0); } return (String((uintptr_t)NAME(&rtmap), MSG_ORIG(MSG_STR_NAME))); } return ((char *)MSG_ORIG(MSG_STR_EMPTY)); } void dcmd_Bind_help(void) { mdb_printf(MSG_ORIG(MSG_BND_HELP)); } static int /* ARGSUSED2 */ dcmd_Bind(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Bnd_desc bnd; char *str; /* * Insure we have a valid address. */ if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn(MSG_ORIG(MSG_ERR_NAS), MSG_ORIG(MSG_BND_STR)); return (DCMD_USAGE); } /* * Obtain the binding descriptor. */ if (mdb_vread(&bnd, sizeof (Bnd_desc), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_BND_STR), addr); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_BND_TITLE), addr); /* * Establish the identity of the caller. */ if ((str = Rtmap_Name((uintptr_t)bnd.b_caller)) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_BND_LINE1), bnd.b_caller, str); /* * Establish the identity of the dependency. */ if ((str = Rtmap_Name((uintptr_t)bnd.b_depend)) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_BND_LINE2), bnd.b_depend, str); /* * Display any flags. */ mdb_printf(MSG_ORIG(MSG_BND_LINE3), bnd.b_flags, bnd.b_flags, bndflags_bits); return (DCMD_OK); } static void dcmd_Depends_help(void) { mdb_printf(MSG_ORIG(MSG_DEPENDS_HELP)); } static int Depends(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv, uint_t flg, const char *msg) { APlist apl; uintptr_t datap, nitems; Bnd_desc *bdp; /* * Obtain the APlist and determine its number of elements and those * that are in use. */ if (mdb_vread(&apl, sizeof (APlist), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_STR_APLIST), addr); return (DCMD_ERR); } mdb_printf(msg, addr, (size_t)apl.apl_nitems, (size_t)apl.apl_arritems); if (((flg & RTLD_FLG_VERBOSE) == 0) || (apl.apl_nitems == 0)) return (DCMD_OK); /* * Under verbose mode print the name of each dependency. An APlist can * have a variable number of data items, so read each individual entry. */ datap = APLIST_OFF_DATA + (uintptr_t)addr; if (mdb_vread(&bdp, sizeof (Bnd_desc *), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_BNDDESC_STR), datap); return (DCMD_ERR); } (void) mdb_inc_indent(4); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (dcmd_Bind((uintptr_t)bdp, flags, argc, argv) == DCMD_ERR) { (void) mdb_dec_indent(4); return (DCMD_ERR); } for (nitems = 1; nitems < apl.apl_nitems; nitems++) { datap += sizeof (void *); if (mdb_vread(&bdp, sizeof (Bnd_desc *), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_BNDDESC_STR), datap); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (dcmd_Bind((uintptr_t)bdp, flags, argc, argv) == DCMD_ERR) { (void) mdb_dec_indent(4); return (DCMD_ERR); } } (void) mdb_dec_indent(4); return (DCMD_OK); } static int dcmd_Depends(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Rt_map rtmap; char *str; uint_t flg = 0; /* * Insure we have a valid address, and provide for a -v option. */ if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn(MSG_ORIG(MSG_ERR_NAS), MSG_ORIG(MSG_DEPENDS_STR)); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, RTLD_FLG_VERBOSE, &flg, NULL) != argc) return (DCMD_USAGE); /* * Read the Rt_map contents. */ if (mdb_vread(&rtmap, sizeof (Rt_map), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_RTMAP_STR), addr); return (DCMD_ERR); } if ((str = String((uintptr_t)NAME(&rtmap), MSG_ORIG(MSG_STR_NAME))) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_DEPENDS_LINE1), str); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (DEPENDS(&rtmap) == NULL) return (DCMD_OK); return (Depends((uintptr_t)DEPENDS(&rtmap), flags, argc, argv, flg, MSG_ORIG(MSG_DEPENDS_LINE2))); } static void dcmd_Callers_help(void) { mdb_printf(MSG_ORIG(MSG_CALLERS_HELP)); } static int dcmd_Callers(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Rt_map rtmap; char *str; uint_t flg = 0; /* * Insure we have a valid address, and provide for a -v option. */ if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn(MSG_ORIG(MSG_ERR_NAS), MSG_ORIG(MSG_DEPENDS_STR)); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, RTLD_FLG_VERBOSE, &flg, NULL) != argc) return (DCMD_USAGE); /* * Read the Rt_map contents. */ if (mdb_vread(&rtmap, sizeof (Rt_map), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_RTMAP_STR), addr); return (DCMD_ERR); } if ((str = String((uintptr_t)NAME(&rtmap), MSG_ORIG(MSG_STR_NAME))) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_CALLERS_LINE1), str); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (CALLERS(&rtmap) == NULL) return (DCMD_OK); return (Depends((uintptr_t)CALLERS(&rtmap), flags, argc, argv, flg, MSG_ORIG(MSG_CALLERS_LINE2))); } void dcmd_rtmap_help(void) { mdb_printf(MSG_ORIG(MSG_RTMAP_HELP)); } static int /* ARGSUSED2 */ dcmd_rtmap(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Rt_map rtmap; char *str; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn(MSG_ORIG(MSG_ERR_NAS), MSG_ORIG(MSG_RTMAP_STR)); return (DCMD_USAGE); } if (mdb_vread(&rtmap, sizeof (Rt_map), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_RTMAP_STR), addr); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_RTMAP_TITLE), addr); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); /* * Determine the objects name. NAME() is the name by which the object * has been opened, typically from adding a search path to a file name. * PATHNAME() is the fully resolve name, which is displayed by the proc * tools and debuggers. If the two names differ, print the PATHNAME(). */ if ((str = String((uintptr_t)NAME(&rtmap), MSG_ORIG(MSG_STR_NAME))) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE1), str); if (NAME(&rtmap) != PATHNAME(&rtmap)) { if ((str = String((uintptr_t)PATHNAME(&rtmap), MSG_ORIG(MSG_STR_PATHNAME))) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE2), str); } mdb_printf(MSG_ORIG(MSG_RTMAP_LINE3), ADDR(&rtmap), DYN(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE4), NEXT(&rtmap), PREV(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE5), rtmap.rt_fct, TLSMODID(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE6), INIT(&rtmap), FINI(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE7), GROUPS(&rtmap), HANDLES(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE8), DEPENDS(&rtmap), CALLERS(&rtmap)); if ((str = String((uintptr_t)REFNAME(&rtmap), MSG_ORIG(MSG_STR_REFNAME))) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE9), DYNINFO(&rtmap), str); if ((str = String((uintptr_t)RPATH(&rtmap), MSG_ORIG(MSG_STR_RPATH))) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE10), RLIST(&rtmap), str); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE11), LIST(&rtmap), LIST(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE12), FLAGS(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE20), FLAGS(&rtmap), rtflags_bits); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE13), FLAGS1(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE20), FLAGS1(&rtmap), rtflags1_bits); if (AFLAGS(&rtmap)) { mdb_printf(MSG_ORIG(MSG_RTMAP_LINE14), AFLAGS(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE20), AFLAGS(&rtmap), rtaflags_bits); } mdb_printf(MSG_ORIG(MSG_RTMAP_LINE15), MODE(&rtmap)); mdb_printf(MSG_ORIG(MSG_RTMAP_LINE20), MODE(&rtmap), rtmode_bits); return (DCMD_OK); } static int rtmap_format(uintptr_t addr, const void *data, void *private) { const Rt_map *lmp = (const Rt_map *)data; W_desc *wdp = (W_desc *)private; char *str; if (wdp && (wdp->w_flags & RTLD_FLG_VERBOSE)) { mdb_printf(MSG_ORIG(MSG_STR_DASHES)); (void) mdb_call_dcmd( MSG_ORIG(MSG_RTMAP_STR), addr, DCMD_ADDRSPEC, 0, NULL); return (0); } if ((str = String((uintptr_t)NAME(lmp), MSG_ORIG(MSG_STR_NAME))) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_FMT_RT), CNTL(lmp), addr, ADDR(lmp), str); return (0); } void dcmd_Rtmaps_help(void) { mdb_printf(MSG_ORIG(MSG_RTMAPS_HELP)); } static int dcmd_Rtmaps(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t flg = 0; GElf_Sym gsym; APlist *aplp, apl; uintptr_t datap, nitems; const char *str; W_desc wdesc; /* * '-v' - Verbose output of rtmap */ if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, RTLD_FLG_VERBOSE, &flg, NULL) != argc) return (DCMD_USAGE); /* * If an address was provided use it. */ if (flags & DCMD_ADDRSPEC) { if (((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) && !(flg & RTLD_FLG_VERBOSE)) { mdb_printf(MSG_ORIG(MSG_RTMAPS_TITLE0)); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); } wdesc.w_flags = flg; if (mdb_pwalk(MSG_ORIG(MSG_RTMAPS_STR), rtmap_format, (void *)&wdesc, addr) == -1) return (DCMD_ERR); return (DCMD_OK); } /* * Otherwise traverse the dynlm_list and display each link-map. */ if (mdb_lookup_by_obj(MSG_ORIG(MSG_STR_LDSO1), MSG_ORIG(MSG_STR_DYNLMLIST), &gsym) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_SYMFAILED), MSG_ORIG(MSG_STR_LDSO1), MSG_ORIG(MSG_STR_DYNLMLIST)); return (DCMD_ERR); } if (mdb_vread(&aplp, sizeof (APlist *), (uintptr_t)gsym.st_value) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_STR_APLIST), gsym.st_value); return (DCMD_ERR); } if (aplp == NULL) { mdb_printf(MSG_ORIG(MSG_LMLIST_TITLE0), MSG_ORIG(MSG_STR_DYNLMLIST)); return (DCMD_OK); } if (mdb_vread(&apl, sizeof (APlist), (uintptr_t)aplp) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_STR_APLIST), aplp); } mdb_printf(MSG_ORIG(MSG_LMLIST_TITLE1), MSG_ORIG(MSG_STR_DYNLMLIST), aplp, (size_t)apl.apl_nitems, (size_t)apl.apl_arritems); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); flags |= (DCMD_LOOP | DCMD_LOOPFIRST); for (datap = (uintptr_t)aplp + APLIST_OFF_DATA, nitems = 0; nitems < apl.apl_nitems; nitems++, datap += sizeof (void *)) { Lm_list *lml, lm; if (mdb_vread(&lml, sizeof (Lm_list *), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_LMLIST_STR), datap); return (DCMD_ERR); } if (mdb_vread(&lm, sizeof (Lm_list), (uintptr_t)lml) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_LMLIST_STR), lml); return (DCMD_ERR); } (void) mdb_inc_indent(2); if (lm.lm_flags & LML_FLG_BASELM) str = MSG_ORIG(MSG_LMLIST_BASE); else if (lm.lm_flags & LML_FLG_RTLDLM) str = MSG_ORIG(MSG_LMLIST_LDSO); else str = MSG_ORIG(MSG_LMLIST_NEWLM); if ((flags & DCMD_LOOP) && ((flags & DCMD_LOOPFIRST) == 0)) mdb_printf(MSG_ORIG(MSG_STR_DASHES)); mdb_printf(MSG_ORIG(MSG_LMLIST_TITLE2), datap, str); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); (void) mdb_inc_indent(2); if (((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) && !(flg & RTLD_FLG_VERBOSE)) { mdb_printf(MSG_ORIG(MSG_RTMAPS_TITLE0)); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); } wdesc.w_flags = flg; if (mdb_pwalk(MSG_ORIG(MSG_RTMAPS_STR), rtmap_format, (void *)&wdesc, (uintptr_t)lm.lm_head) == -1) { (void) mdb_dec_indent(4); return (DCMD_ERR); } (void) mdb_dec_indent(4); flags &= ~DCMD_LOOPFIRST; } return (DCMD_OK); } void dcmd_Setenv_help(void) { mdb_printf(MSG_ORIG(MSG_SETENV_HELP)); } /* * As of s10, mdb provides its own setenv command. This command allows the * environment of the process being controlled to be changed at any time. * Prior to this, ld.so.1 provided it's own, more primitive implementation. * This allowed for changing mdb's environment only, which if it was changed * before the application ws executed, would be copied to the applications * environment. Thus, we could start mdb, set an LD_ variable within its * environment (which it's own ld.so.1 had already finished processing), and * have this setting be inherited by the application. */ static int /* ARGSUSED */ dcmd_Setenv(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { char *str; if (mdb_call_dcmd(MSG_ORIG(MSG_STR_SETENV), addr, flags, argc, argv) == 0) return (DCMD_OK); if (((flags & DCMD_ADDRSPEC) != 0) || (argc == 0) || (argc > 1) || (argv->a_type != MDB_TYPE_STRING)) return (DCMD_USAGE); str = mdb_alloc((strlen(argv->a_un.a_str) + 1), UM_NOSLEEP); if (str == NULL) return (DCMD_ERR); (void) strcpy(str, argv->a_un.a_str); (void) putenv(str); return (DCMD_OK); } /* * Walk Rt_map lists */ static int walk_rtmap_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn(MSG_ORIG(MSG_ERR_NAS), MSG_ORIG(MSG_RTMAP_STR)); return (WALK_ERR); } return (WALK_NEXT); } static int walk_rtmap_step(mdb_walk_state_t *wsp) { int status; Rt_map lmp; if (wsp->walk_addr == 0) return (WALK_DONE); if (mdb_vread(&lmp, sizeof (Rt_map), wsp->walk_addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_RTMAP_STR), wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, &lmp, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(NEXT(&lmp)); return (status); } static const mdb_bitmask_t lml_flags_bit[] = { { MSG_ORIG(MSG_LFL_BASELM), LML_FLG_BASELM, LML_FLG_BASELM }, { MSG_ORIG(MSG_LFL_RTLDLM), LML_FLG_RTLDLM, LML_FLG_RTLDLM }, { MSG_ORIG(MSG_LFL_PLTREL), LML_FLG_PLTREL, LML_FLG_PLTREL }, { MSG_ORIG(MSG_LFL_HOLDLOCK), LML_FLG_HOLDLOCK, LML_FLG_HOLDLOCK }, { MSG_ORIG(MSG_LFL_ENVIRON), LML_FLG_ENVIRON, LML_FLG_ENVIRON }, { MSG_ORIG(MSG_LFL_INTRPOSE), LML_FLG_INTRPOSE, LML_FLG_INTRPOSE }, { MSG_ORIG(MSG_LFL_LOCAUDIT), LML_FLG_LOCAUDIT, LML_FLG_LOCAUDIT }, { MSG_ORIG(MSG_LFL_LOADAVAIL), LML_FLG_LOADAVAIL, LML_FLG_LOADAVAIL }, { MSG_ORIG(MSG_LFL_IGNRELERR), LML_FLG_IGNRELERR, LML_FLG_IGNRELERR }, { MSG_ORIG(MSG_LFL_STARTREL), LML_FLG_STARTREL, LML_FLG_STARTREL }, { MSG_ORIG(MSG_LFL_ATEXIT), LML_FLG_ATEXIT, LML_FLG_ATEXIT }, { MSG_ORIG(MSG_LFL_OBJADDED), LML_FLG_OBJADDED, LML_FLG_OBJADDED }, { MSG_ORIG(MSG_LFL_OBJDELETED), LML_FLG_OBJDELETED, LML_FLG_OBJDELETED }, { MSG_ORIG(MSG_LFL_OBJREEVAL), LML_FLG_OBJREEVAL, LML_FLG_OBJREEVAL }, { MSG_ORIG(MSG_LFL_INTRPOSETSORT), LML_FLG_INTRPOSETSORT, LML_FLG_INTRPOSETSORT }, { MSG_ORIG(MSG_LFL_AUDITNOTIFY), LML_FLG_AUDITNOTIFY, LML_FLG_AUDITNOTIFY }, { MSG_ORIG(MSG_LFL_GROUPSEXIST), LML_FLG_GROUPSEXIST, LML_FLG_GROUPSEXIST }, { MSG_ORIG(MSG_LFL_TRC_LDDSTUB), LML_FLG_TRC_LDDSTUB, LML_FLG_TRC_LDDSTUB }, { MSG_ORIG(MSG_LFL_TRC_ENABLE), LML_FLG_TRC_ENABLE, LML_FLG_TRC_ENABLE }, { MSG_ORIG(MSG_LFL_TRC_WARN), LML_FLG_TRC_WARN, LML_FLG_TRC_WARN }, { MSG_ORIG(MSG_LFL_TRC_VERBOSE), LML_FLG_TRC_VERBOSE, LML_FLG_TRC_VERBOSE }, { MSG_ORIG(MSG_LFL_TRC_SEARCH), LML_FLG_TRC_SEARCH, LML_FLG_TRC_SEARCH }, { MSG_ORIG(MSG_LFL_TRC_UNREF), LML_FLG_TRC_UNREF, LML_FLG_TRC_UNREF }, { MSG_ORIG(MSG_LFL_TRC_UNUSED), LML_FLG_TRC_UNUSED, LML_FLG_TRC_UNUSED }, { MSG_ORIG(MSG_LFL_TRC_INIT), LML_FLG_TRC_INIT, LML_FLG_TRC_INIT }, { MSG_ORIG(MSG_LFL_TRC_NOUNRESWEAK), LML_FLG_TRC_NOUNRESWEAK, LML_FLG_TRC_NOUNRESWEAK }, { MSG_ORIG(MSG_LFL_TRC_NOPAREXT), LML_FLG_TRC_NOPAREXT, LML_FLG_TRC_NOPAREXT }, { NULL, 0, 0} }; static const mdb_bitmask_t lml_tflags_bit[] = { { MSG_ORIG(MSG_LTFL_NOLAZYLD), LML_TFLG_NOLAZYLD, LML_TFLG_NOLAZYLD }, { MSG_ORIG(MSG_LTFL_NODIRECT), LML_TFLG_NODIRECT, LML_TFLG_NODIRECT }, { MSG_ORIG(MSG_LTFL_NOAUDIT), LML_TFLG_NOAUDIT, LML_TFLG_NOAUDIT }, { MSG_ORIG(MSG_LTFL_LOADFLTR), LML_TFLG_LOADFLTR, LML_TFLG_LOADFLTR }, { MSG_ORIG(MSG_LTFL_AUD_PREINIT), LML_TFLG_AUD_PREINIT, LML_TFLG_AUD_PREINIT }, { MSG_ORIG(MSG_LTFL_AUD_OBJSEARCH), LML_TFLG_AUD_OBJSEARCH, LML_TFLG_AUD_OBJSEARCH }, { MSG_ORIG(MSG_LTFL_AUD_OBJOPEN), LML_TFLG_AUD_OBJOPEN, LML_TFLG_AUD_OBJOPEN }, { MSG_ORIG(MSG_LTFL_AUD_OBJFILTER), LML_TFLG_AUD_OBJFILTER, LML_TFLG_AUD_OBJFILTER }, { MSG_ORIG(MSG_LTFL_AUD_OBJCLOSE), LML_TFLG_AUD_OBJCLOSE, LML_TFLG_AUD_OBJCLOSE }, { MSG_ORIG(MSG_LTFL_AUD_SYMBIND), LML_TFLG_AUD_SYMBIND, LML_TFLG_AUD_SYMBIND }, { MSG_ORIG(MSG_LTFL_AUD_PLTENTER), LML_TFLG_AUD_PLTENTER, LML_TFLG_AUD_PLTENTER }, { MSG_ORIG(MSG_LTFL_AUD_PLTEXIT), LML_TFLG_AUD_PLTEXIT, LML_TFLG_AUD_PLTEXIT }, { MSG_ORIG(MSG_LTFL_AUD_ACTIVITY), LML_TFLG_AUD_ACTIVITY, LML_TFLG_AUD_ACTIVITY }, { NULL, 0, 0} }; void dcmd_Lm_list_help(void) { mdb_printf(MSG_ORIG(MSG_LMLIST_HELP)); } static int /* ARGSUSED1 */ _dcmd_Lm_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Lm_list lml; const char *str; uint_t flg = 0; if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, RTLD_FLG_VERBOSE, &flg, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&lml, sizeof (lml), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_LMLIST_STR), addr); return (DCMD_ERR); } if (lml.lm_flags & LML_FLG_BASELM) str = MSG_ORIG(MSG_LMLIST_BASE); else if (lml.lm_flags & LML_FLG_RTLDLM) str = MSG_ORIG(MSG_LMLIST_LDSO); else str = MSG_ORIG(MSG_LMLIST_NEWLM); if ((flags & DCMD_LOOP) && ((flags & DCMD_LOOPFIRST) == 0)) mdb_printf(MSG_ORIG(MSG_STR_DASHES)); mdb_printf(MSG_ORIG(MSG_LMLIST_TITLE2), addr, str); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (lml.lm_lists) { Alist al; Lm_cntl lmc; uintptr_t datap; addr = (uintptr_t)lml.lm_lists; if (mdb_vread(&al, sizeof (Alist), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_STR_ALIST), addr); return (DCMD_ERR); } /* * Determine whether the Alist has been populated. Note, the * implementation first reserves an alist entry, and initializes * this element when the first link-map is processed. Thus, * there's a window when nitems is updated, but before the next * element has been initialized. */ if (al.al_nitems && (flg & RTLD_FLG_VERBOSE)) { datap = ALIST_OFF_DATA + (uintptr_t)addr; if (mdb_vread(&lmc, sizeof (Lm_cntl), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_LMLIST_STR), datap); return (DCMD_ERR); } } mdb_printf(MSG_ORIG(MSG_LMLIST_LINE0), addr, (size_t)al.al_nitems, (size_t)al.al_arritems); (void) mdb_inc_indent(2); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (al.al_nitems && (flg & RTLD_FLG_VERBOSE)) { uintptr_t nitems; (void) mdb_inc_indent(2); mdb_printf(MSG_ORIG(MSG_LMC_LINE1), datap); mdb_printf(MSG_ORIG(MSG_LMC_LINE2), lmc.lc_head, lmc.lc_tail); mdb_printf(MSG_ORIG(MSG_LMC_LINE3), lmc.lc_flags, lmc.lc_now); mdb_printf(MSG_ORIG(MSG_LMC_LINE4), lmc.lc_flags, lmc_bits); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); mdb_printf(MSG_ORIG(MSG_RTMAPS_TITLE0)); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (lmc.lc_head) { if (mdb_pwalk(MSG_ORIG(MSG_RTMAPS_STR), rtmap_format, (void *)0, (uintptr_t)lmc.lc_head) == -1) { (void) mdb_dec_indent(4); return (DCMD_ERR); } } else mdb_printf(MSG_ORIG(MSG_FMT_RT), 0, 0, 0, MSG_ORIG(MSG_STR_EMPTY)); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); for (nitems = 1; nitems < al.al_nitems; nitems++) { datap += al.al_size; if (mdb_vread(&lmc, sizeof (Lm_cntl), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_LMLIST_STR), datap); (void) mdb_dec_indent(4); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_STR_DASHES)); mdb_printf(MSG_ORIG(MSG_LMC_LINE1), datap); mdb_printf(MSG_ORIG(MSG_LMC_LINE2), lmc.lc_head, lmc.lc_tail); mdb_printf(MSG_ORIG(MSG_LMC_LINE3), lmc.lc_flags, lmc.lc_now); mdb_printf(MSG_ORIG(MSG_LMC_LINE4), lmc.lc_flags, lmc_bits); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); mdb_printf(MSG_ORIG(MSG_RTMAPS_TITLE0)); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (lmc.lc_head) { if (mdb_pwalk(MSG_ORIG(MSG_RTMAPS_STR), rtmap_format, (void *)0, (uintptr_t)lmc.lc_head) == -1) { (void) mdb_dec_indent(4); return (DCMD_ERR); } } else mdb_printf(MSG_ORIG(MSG_FMT_RT), 0, 0, 0, MSG_ORIG(MSG_STR_EMPTY)); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); } (void) mdb_dec_indent(2); } (void) mdb_dec_indent(2); } mdb_printf(MSG_ORIG(MSG_LMLIST_LINE1), lml.lm_head, lml.lm_tail); mdb_printf(MSG_ORIG(MSG_LMLIST_LINE2), lml.lm_alp, lml.lm_rti); mdb_printf(MSG_ORIG(MSG_LMLIST_LINE3), lml.lm_handle, lml.lm_obj, lml.lm_init, lml.lm_lazy); mdb_printf(MSG_ORIG(MSG_LMLIST_LINE4), lml.lm_flags); if (lml.lm_flags) mdb_printf(MSG_ORIG(MSG_LMLIST_LINE6), lml.lm_flags, lml_flags_bit); mdb_printf(MSG_ORIG(MSG_LMLIST_LINE5), lml.lm_tflags); if (lml.lm_tflags) mdb_printf(MSG_ORIG(MSG_LMLIST_LINE6), lml.lm_tflags, lml_tflags_bit); return (DCMD_OK); } static int /* ARGSUSED2 */ dcmd_Lm_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { GElf_Sym gsym; APlist *aplp, apl; uintptr_t datap, nitems; /* * If an address was provided us it. */ if (flags & DCMD_ADDRSPEC) return (_dcmd_Lm_list(addr, flags, argc, argv)); /* * Otherwise traverse the dynlm_list and display each link-map list. */ if (mdb_lookup_by_obj(MSG_ORIG(MSG_STR_LDSO1), MSG_ORIG(MSG_STR_DYNLMLIST), &gsym) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_SYMFAILED), MSG_ORIG(MSG_STR_LDSO1), MSG_ORIG(MSG_STR_DYNLMLIST)); return (DCMD_ERR); } if (mdb_vread(&aplp, sizeof (APlist *), (uintptr_t)gsym.st_value) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_STR_APLIST), gsym.st_value); return (DCMD_ERR); } if (aplp == NULL) { mdb_printf(MSG_ORIG(MSG_LMLIST_TITLE0), MSG_ORIG(MSG_STR_DYNLMLIST)); return (DCMD_OK); } if (mdb_vread(&apl, sizeof (APlist), (uintptr_t)aplp) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_STR_APLIST), aplp); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_LMLIST_TITLE1), MSG_ORIG(MSG_STR_DYNLMLIST), aplp, (size_t)apl.apl_nitems, (size_t)apl.apl_arritems); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); flags |= (DCMD_LOOP | DCMD_LOOPFIRST); for (datap = (uintptr_t)aplp + APLIST_OFF_DATA, nitems = 0; nitems < apl.apl_nitems; nitems++, datap += sizeof (void *)) { Lm_list *lml; if (mdb_vread(&lml, sizeof (Lm_list *), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_LMLIST_STR), datap); return (DCMD_ERR); } (void) mdb_inc_indent(2); if (_dcmd_Lm_list((uintptr_t)lml, flags, argc, argv) == DCMD_ERR) { (void) mdb_dec_indent(2); return (DCMD_ERR); } (void) mdb_dec_indent(2); flags &= ~DCMD_LOOPFIRST; } return (DCMD_OK); } void dcmd_GrpDesc_help(void) { mdb_printf(MSG_ORIG(MSG_GRPDESC_HELP)); } static int /* ARGSUSED2 */ dcmd_GrpDesc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Grp_desc gd; char *str; /* * Insure we have a valid address. */ if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn(MSG_ORIG(MSG_ERR_NAS), MSG_ORIG(MSG_GRPDESC_STR)); return (DCMD_USAGE); } mdb_printf(MSG_ORIG(MSG_GRPDESC_LINE1), addr); if (mdb_vread(&gd, sizeof (Grp_desc), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_GRPDESC_STR), addr); return (DCMD_ERR); } if ((str = Rtmap_Name((uintptr_t)gd.gd_depend)) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_GRPDESC_LINE2), gd.gd_depend, str); mdb_printf(MSG_ORIG(MSG_GRPDESC_LINE3), gd.gd_flags, gd.gd_flags, grdflags_bits); return (DCMD_OK); } void dcmd_GrpHdl_help(void) { mdb_printf(MSG_ORIG(MSG_GRPHDL_HELP)); } static int /* ARGSUSED2 */ dcmd_GrpHdl(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Grp_hdl gh; Alist al; uintptr_t datap, listidx; char *str; uint_t flg = 0; /* * Insure we have a valid address, and provide for a -v option. */ if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn(MSG_ORIG(MSG_ERR_NAS), MSG_ORIG(MSG_GRPHDL_STR)); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, RTLD_FLG_VERBOSE, &flg, NULL) != argc) return (DCMD_USAGE); mdb_printf(MSG_ORIG(MSG_GRPHDL_LINE1), addr); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (mdb_vread(&gh, sizeof (Grp_hdl), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_GRPHDL_STR), addr); return (DCMD_ERR); } /* * Determine the handles owner. Note that an orphaned handle may no * longer contain its originating owner. */ if (gh.gh_ownlmp) { if ((str = Rtmap_Name((uintptr_t)gh.gh_ownlmp)) == 0) return (DCMD_ERR); } else str = (char *)MSG_ORIG(MSG_STR_ORPHANED); mdb_printf(MSG_ORIG(MSG_GRPHDL_LINE2), str); mdb_printf(MSG_ORIG(MSG_GRPHDL_LINE3), gh.gh_flags, gh.gh_flags, grhflags_bits); if (gh.gh_depends == 0) { mdb_printf(MSG_ORIG(MSG_GRPHDL_LINE4), gh.gh_refcnt); return (DCMD_OK); } addr = (uintptr_t)gh.gh_depends; if (mdb_vread(&al, sizeof (Alist), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_STR_ALIST), addr); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_GRPHDL_LINE5), gh.gh_refcnt, addr, (size_t)al.al_nitems, (size_t)al.al_arritems); if (((flg & RTLD_FLG_VERBOSE) == 0) || (al.al_nitems == 0)) return (DCMD_OK); (void) mdb_inc_indent(4); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); /* * Under verbose mode print the name of each dependency. An Alist can * have a variable number of data items, so read each individual entry. */ datap = ALIST_OFF_DATA + (uintptr_t)addr; if (dcmd_GrpDesc(datap, flags, argc, argv) == DCMD_ERR) { (void) mdb_dec_indent(4); return (DCMD_ERR); } for (listidx = 1; listidx < al.al_nitems; listidx++) { datap += al.al_size; mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (dcmd_GrpDesc(datap, flags, argc, argv) == DCMD_ERR) { (void) mdb_dec_indent(4); return (DCMD_ERR); } } (void) mdb_dec_indent(4); return (DCMD_OK); } static void dcmd_Handles_help(void) { mdb_printf(MSG_ORIG(MSG_HANDLES_HELP)); } static int dcmd_Handles(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Rt_map rtmap; char *str; uint_t flg = 0; APlist apl; uintptr_t datap, nitems; Grp_hdl *ghp; /* * Insure we have a valid address, and provide for a -v option. */ if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn(MSG_ORIG(MSG_ERR_NAS), MSG_ORIG(MSG_HANDLES_STR)); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, RTLD_FLG_VERBOSE, &flg, NULL) != argc) return (DCMD_USAGE); /* * Read the Rt_map contents. */ if (mdb_vread(&rtmap, sizeof (Rt_map), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_RTMAP_STR), addr); return (DCMD_ERR); } if ((str = String((uintptr_t)NAME(&rtmap), MSG_ORIG(MSG_STR_NAME))) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_HANDLES_LINE1), str); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (HANDLES(&rtmap) == 0) return (DCMD_OK); addr = (uintptr_t)HANDLES(&rtmap); if (mdb_vread(&apl, sizeof (APlist), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_STR_APLIST), addr); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_HANDLES_LINE2), addr, (size_t)apl.apl_nitems, (size_t)apl.apl_arritems); if (((flg & RTLD_FLG_VERBOSE) == 0) || (apl.apl_nitems == 0)) return (DCMD_OK); /* * Under verbose mode print the name of each dependency. An APlist can * have a variable number of data items, so read each individual entry. */ datap = addr + APLIST_OFF_DATA; if (mdb_vread(&ghp, sizeof (Grp_hdl *), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_GRPHDL_STR), datap); return (DCMD_ERR); } (void) mdb_inc_indent(4); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (dcmd_GrpHdl((uintptr_t)ghp, flags, argc, argv) == DCMD_ERR) { (void) mdb_dec_indent(4); return (DCMD_ERR); } nitems = 1; for (nitems = 1; nitems < apl.apl_nitems; nitems++) { datap += sizeof (void *); if (mdb_vread(&ghp, sizeof (Grp_hdl *), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_GRPHDL_STR), datap); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (dcmd_GrpHdl((uintptr_t)ghp, flags, argc, argv) == DCMD_ERR) { (void) mdb_dec_indent(4); return (DCMD_ERR); } } (void) mdb_dec_indent(4); return (DCMD_OK); } static void dcmd_Groups_help(void) { mdb_printf(MSG_ORIG(MSG_GROUPS_HELP)); } static int dcmd_Groups(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Rt_map rtmap; char *str; APlist apl; uint_t flg = 0; uintptr_t datap, nitems; Grp_hdl *ghp; /* * Insure we have a valid address, and provide for a -v option. */ if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn(MSG_ORIG(MSG_ERR_NAS), MSG_ORIG(MSG_GROUPS_STR)); return (DCMD_USAGE); } if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, RTLD_FLG_VERBOSE, &flg, NULL) != argc) return (DCMD_USAGE); /* * Read the Rt_map contents. */ if (mdb_vread(&rtmap, sizeof (Rt_map), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_RTMAP_STR), addr); return (DCMD_ERR); } if ((str = String((uintptr_t)NAME(&rtmap), MSG_ORIG(MSG_STR_NAME))) == 0) return (DCMD_ERR); mdb_printf(MSG_ORIG(MSG_GROUPS_LINE1), str); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (GROUPS(&rtmap) == 0) return (DCMD_OK); addr = (uintptr_t)GROUPS(&rtmap); if (mdb_vread(&apl, sizeof (APlist), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_STR_APLIST), addr); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_GROUPS_LINE2), addr, (size_t)apl.apl_nitems, (size_t)apl.apl_arritems); if (((flg & RTLD_FLG_VERBOSE) == 0) || (apl.apl_nitems == 0)) return (DCMD_OK); /* * Under verbose mode print the name of each dependency. An APlist can * have a variable number of data items, so read each individual entry. */ datap = addr + APLIST_OFF_DATA; if (mdb_vread(&ghp, sizeof (Grp_hdl *), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_GRPHDL_STR), datap); return (DCMD_ERR); } (void) mdb_inc_indent(4); mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (dcmd_GrpHdl((uintptr_t)ghp, flags, argc, argv) == DCMD_ERR) { (void) mdb_dec_indent(4); return (DCMD_ERR); } for (nitems = 1; nitems < apl.apl_nitems; nitems++) { datap += sizeof (void *); if (mdb_vread(&ghp, sizeof (Grp_hdl *), datap) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_GRPHDL_STR), datap); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_STR_DASHES)); if (dcmd_GrpHdl((uintptr_t)ghp, flags, argc, argv) == DCMD_ERR) { (void) mdb_dec_indent(4); return (DCMD_ERR); } } (void) mdb_dec_indent(4); return (DCMD_OK); } static void dcmd_ElfDyn_help(void) { mdb_printf(MSG_ORIG(MSG_ELFDYN_HELP)); } static int /* ARGSUSED2 */ dcmd_ElfDyn(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Dyn dyn; const char *dynstr; Conv_inv_buf_t inv_buf; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (mdb_vread(&dyn, sizeof (dyn), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_ELFDYN_STR), addr); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_ELFDYN_TITLE), addr); dynstr = conv_dyn_tag(dyn.d_tag, ELFOSABI_SOLARIS, M_MACH, 0, &inv_buf); mdb_printf(MSG_ORIG(MSG_ELFDYN_LINE1), addr, dynstr, dyn.d_un.d_ptr); mdb_set_dot(addr + sizeof (Dyn)); return (DCMD_OK); } static void dcmd_ElfEhdr_help(void) { mdb_printf(MSG_ORIG(MSG_EHDR_HELP)); } static int /* ARGSUSED2 */ dcmd_ElfEhdr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Ehdr ehdr; Byte *byte; const char *flgs; Conv_inv_buf_t inv_buf1, inv_buf2; Conv_ehdr_flags_buf_t ehdr_flags_buf; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (mdb_vread(&ehdr, sizeof (ehdr), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_EHDR_STR), addr); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_EHDR_TITLE), addr); byte = &ehdr.e_ident[0]; mdb_printf(MSG_ORIG(MSG_EHDR_LINE1), byte[EI_MAG0], (byte[EI_MAG1] ? byte[EI_MAG1] : '0'), (byte[EI_MAG2] ? byte[EI_MAG2] : '0'), (byte[EI_MAG3] ? byte[EI_MAG3] : '0')); mdb_printf(MSG_ORIG(MSG_EHDR_LINE2), conv_ehdr_class(ehdr.e_ident[EI_CLASS], 0, &inv_buf1), conv_ehdr_data(ehdr.e_ident[EI_DATA], 0, &inv_buf2)); mdb_printf(MSG_ORIG(MSG_EHDR_LINE3), conv_ehdr_mach(ehdr.e_machine, 0, &inv_buf1), conv_ehdr_vers(ehdr.e_version, 0, &inv_buf2)); mdb_printf(MSG_ORIG(MSG_EHDR_LINE4), conv_ehdr_type(ehdr.e_ident[EI_OSABI], ehdr.e_type, 0, &inv_buf1)); /* * Line up the flags differently depending on whether we * received a numeric (e.g. "0x200") or text representation * (e.g. "[ EF_SPARC_SUN_US1 ]"). */ flgs = conv_ehdr_flags(ehdr.e_machine, ehdr.e_flags, 0, &ehdr_flags_buf); if (flgs[0] == '[') mdb_printf(MSG_ORIG(MSG_EHDR_LINE5), flgs); else mdb_printf(MSG_ORIG(MSG_EHDR_LINE6), flgs); mdb_printf(MSG_ORIG(MSG_EHDR_LINE7), ehdr.e_entry, ehdr.e_ehsize, ehdr.e_shstrndx); mdb_printf(MSG_ORIG(MSG_EHDR_LINE8), ehdr.e_shoff, ehdr.e_shentsize, ehdr.e_shnum); mdb_printf(MSG_ORIG(MSG_EHDR_LINE9), ehdr.e_phoff, ehdr.e_phentsize, ehdr.e_phnum); mdb_set_dot(addr + sizeof (Ehdr)); return (DCMD_OK); } static void dcmd_ElfPhdr_help(void) { mdb_printf(MSG_ORIG(MSG_EPHDR_HELP)); } static int /* ARGSUSED2 */ dcmd_ElfPhdr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { Phdr phdr; Conv_inv_buf_t inv_buf; Conv_phdr_flags_buf_t phdr_flags_buf; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (mdb_vread(&phdr, sizeof (phdr), addr) == -1) { mdb_warn(MSG_ORIG(MSG_ERR_READ), MSG_ORIG(MSG_EPHDR_STR), addr); return (DCMD_ERR); } mdb_printf(MSG_ORIG(MSG_EPHDR_TITLE), addr); mdb_printf(MSG_ORIG(MSG_EPHDR_LINE1), phdr.p_vaddr, conv_phdr_flags(ELFOSABI_SOLARIS, phdr.p_flags, 0, &phdr_flags_buf)); mdb_printf(MSG_ORIG(MSG_EPHDR_LINE2), phdr.p_paddr, conv_phdr_type(ELFOSABI_SOLARIS, M_MACH, phdr.p_type, 0, &inv_buf)); mdb_printf(MSG_ORIG(MSG_EPHDR_LINE3), phdr.p_filesz, phdr.p_memsz); mdb_printf(MSG_ORIG(MSG_EPHDR_LINE4), phdr.p_offset, phdr.p_align); mdb_set_dot(addr + sizeof (Phdr)); return (DCMD_OK); } static const mdb_dcmd_t dcmds[] = { { MSG_ORIG(MSG_BND_STR), MSG_ORIG(MSG_USG_ADDREQ_V), MSG_ORIG(MSG_BND_DCD), dcmd_Bind, dcmd_Bind_help}, { MSG_ORIG(MSG_DEPENDS_STR), MSG_ORIG(MSG_USG_ADDREQ_V), MSG_ORIG(MSG_DEPENDS_DCD), dcmd_Depends, dcmd_Depends_help}, { MSG_ORIG(MSG_CALLERS_STR), MSG_ORIG(MSG_USG_ADDREQ_V), MSG_ORIG(MSG_CALLERS_DCD), dcmd_Callers, dcmd_Callers_help}, { MSG_ORIG(MSG_GRPHDL_STR), MSG_ORIG(MSG_USG_ADDREQ_V), MSG_ORIG(MSG_GRPHDL_DCD), dcmd_GrpHdl, dcmd_GrpHdl_help}, { MSG_ORIG(MSG_GRPDESC_STR), MSG_ORIG(MSG_USG_ADDREQ_V), MSG_ORIG(MSG_GRPDESC_DCD), dcmd_GrpDesc, dcmd_GrpDesc_help}, { MSG_ORIG(MSG_HANDLES_STR), MSG_ORIG(MSG_USG_ADDREQ_V), MSG_ORIG(MSG_HANDLES_DCD), dcmd_Handles, dcmd_Handles_help}, { MSG_ORIG(MSG_GROUPS_STR), MSG_ORIG(MSG_USG_ADDREQ_V), MSG_ORIG(MSG_GROUPS_DCD), dcmd_Groups, dcmd_Groups_help}, { MSG_ORIG(MSG_ELFDYN_STR), MSG_ORIG(MSG_USG_ADDREQ), MSG_ORIG(MSG_ELFDYN_DCD), dcmd_ElfDyn, dcmd_ElfDyn_help}, { MSG_ORIG(MSG_EHDR_STR), MSG_ORIG(MSG_USG_ADDREQ), MSG_ORIG(MSG_EHDR_DCD), dcmd_ElfEhdr, dcmd_ElfEhdr_help}, { MSG_ORIG(MSG_EPHDR_STR), MSG_ORIG(MSG_USG_ADDREQ), MSG_ORIG(MSG_EPHDR_DCD), dcmd_ElfPhdr, dcmd_ElfPhdr_help}, { MSG_ORIG(MSG_LMLIST_STR), MSG_ORIG(MSG_USG_ADDREQ_V), MSG_ORIG(MSG_LMLIST_DCD), dcmd_Lm_list, dcmd_Lm_list_help}, { MSG_ORIG(MSG_RTMAPS_STR), MSG_ORIG(MSG_USG_ADDOPT_V), MSG_ORIG(MSG_RTMAPS_DCD), dcmd_Rtmaps, dcmd_Rtmaps_help}, { MSG_ORIG(MSG_RTMAP_STR), MSG_ORIG(MSG_USG_ADDREQ), MSG_ORIG(MSG_RTMAP_DCD), dcmd_rtmap, dcmd_rtmap_help}, { MSG_ORIG(MSG_SETENV_STR), MSG_ORIG(MSG_USG_SETENV), MSG_ORIG(MSG_SETENV_DCD), dcmd_Setenv, dcmd_Setenv_help}, { NULL } }; static const mdb_walker_t walkers[] = { { MSG_ORIG(MSG_RTMAPS_STR), MSG_ORIG(MSG_WWD_RTMAP), walk_rtmap_init, walk_rtmap_step, NULL, NULL }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. # @ _START_ @ _END_ # # Misc. Strings # @ MSG_STR_NAME "NAME" @ MSG_STR_PATHNAME "PATHNAME" @ MSG_STR_REFNAME "REFNAME" @ MSG_STR_RPATH "RPATH" @ MSG_STR_DASHES "----------------------------------------------\n" @ MSG_STR_LDSO1 "ld.so.1" @ MSG_STR_DYNLMLIST "dynlm_list" @ MSG_STR_ALIST "Alist" @ MSG_STR_APLIST "APlist" @ MSG_STR_EMPTY "" @ MSG_STR_ORPHANED "" @ MSG_STR_SETENV "setenv" # # mdb_dcmd_t.dc_usage's # @ MSG_USG_ADDREQ ":" # Address required @ MSG_USG_ADDREQ_V ":[-v]" # Address required plus -v @ MSG_USG_ADDOPT_V "?[-v]" # Address optional plus -v @ MSG_USG_SETENV "name[=value]" # Setenv argument # # mdb_walker.walk_descr's # @ MSG_WWD_RTMAP "Walk a List of Rt_maps" # # Flag Strings # @ MSG_FLG_ISMAIN "IS-MAIN" @ MSG_FLG_IMGALLOC "IMAGE-ALLOCATED" @ MSG_FLG_RELOCED "RELOCED" @ MSG_FLG_SETGROUP "SET-GROUP" @ MSG_FLG_CAP "CAP" @ MSG_FLG_OBJECT "OBJECT" @ MSG_FLG_NEWLOAD "NEWLOAD" @ MSG_FLG_NODUMP "NODUMP" @ MSG_FLG_DELETE "DELETE" @ MSG_FLG_ANALYZED "ANALYZED" @ MSG_FLG_INITDONE "INIT-DONE" @ MSG_FLG_TRANS "TRANS" @ MSG_FLG_FIXED "FIXED" @ MSG_FLG_PRELOAD "PRELOAD" @ MSG_FLG_ALTER "ALTERNATE" @ MSG_FLG_LOADFLTR "LOAD-FILTERS" @ MSG_FLG_AUDIT "AUDIT" @ MSG_FLG_MODESET "MODE-SET" @ MSG_FLG_ANALZING "ANALYZING" @ MSG_FLG_INITFRST "INIT-FIRST" @ MSG_FLG_NOOPEN "NO-OPEN" @ MSG_FLG_FINICLCT "FINI-COLLECTED" @ MSG_FLG_INITCALL "INIT-CALLED" @ MSG_FLG_OBJINTPO "OBJECT-INTERPOSE" @ MSG_FLG_SYMINTPO "SYMBOL-INTERPOSE" @ MSG_FLG_MOVE "MOVE" @ MSG_FLG_RELOCING "RELOCATING" @ MSG_FLG_REGSYMS "REGISTER-SYMS" @ MSG_FLG_INITCLCT "INIT-COLLECTED" @ MSG_FLG_PUBHDL "PUBLIC-HANDLE" @ MSG_FLG_PRIHDL "PRIVATE-HANDLE" @ MSG_FL1_COPYTOOK "COPYTOOK" @ MSG_FL1_ALTCHECK "ALT-CAP-CHECKED" @ MSG_FL1_ALTCAP "ALT-CAP-REQUIRED" @ MSG_FL1_CONFSET "CONFSET" @ MSG_FL1_NODEFLIB "NO-DEFAULT-LIBPATH" @ MSG_FL1_ENDFILTE "END-FILTEE" @ MSG_FL1_DISPREL "DISPLACEMENT-RELOCATED" @ MSG_FL1_DTFLAGS "DTFLAGS-AVAILABLE" @ MSG_FL1_LDDSTUB "LDDSTUB" @ MSG_FL1_NOINIFIN "NO-INITFINI" @ MSG_FL1_USED "USED" @ MSG_FL1_SYMBOLIC "SYMBOLIC" @ MSG_FL1_OBJSFLTR "OBJ-STD-FILTER" @ MSG_FL1_OBJAFLTR "OBJ-AUX-FILTER" @ MSG_FL1_SYMSFLTR "SYM-STD-FILTER" @ MSG_FL1_SYMAFLTR "SYM-AUX-FILTER" @ MSG_FL1_TLSADD "TLS-ADD" @ MSG_FL1_TLSSTAT "TLS-STAT" @ MSG_FL1_DIRECT "DIRECT" @ MSG_FL1_GLOBAUD "GLOBAL-AUDITING" @ MSG_FL1_DEPAUD "DT-DEPAUDIT" @ MSG_MODE_LAZY "LAZY" @ MSG_MODE_NOW "NOW" @ MSG_MODE_NOLOAD "NOLOAD" @ MSG_MODE_GLOBAL "GLOBAL" @ MSG_MODE_PARENT "PARENT" @ MSG_MODE_GROUP "GROUP" @ MSG_MODE_WORLD "WORLD" @ MSG_MODE_NODELETE "NODELETE" @ MSG_MODE_FIRST "FIRST" @ MSG_MODE_CONFGEN "CONFGEN" @ MSG_BFL_NEEDED "NEEDED" @ MSG_BFL_REFER "REFERENCED" @ MSG_BFL_FILTER "FILTER" @ MSG_GPH_PUBLIC "PUBLIC-HANDLE" @ MSG_GPH_PRIVATE "PRIVATE-HANDLE" @ MSG_GPH_ZERO "ZERO" @ MSG_GPH_LDSO "LD.SO.1" @ MSG_GPH_FIRST "FIRST-ONLY" @ MSG_GPH_FILTEE "FILTEE" @ MSG_GPH_INITIAL "INITIALIZED" @ MSG_GPD_DLSYM "AVAIL-TO-DLSYM" @ MSG_GPD_RELOC "AVAIL-FOR-RELOCATION" @ MSG_GPD_ADDEPS "ADD-DEPENDENCIES" @ MSG_GPD_PARENT "PARENT" @ MSG_GPD_FILTER "FILTER" @ MSG_GPD_REMOVE "REMOVAL-CANDIDATE" @ MSG_LFL_BASELM "BASELM" @ MSG_LFL_RTLDLM "RTLDLM" @ MSG_LFL_PLTREL "DEFERRED-PLT-RELOCATION" @ MSG_LFL_HOLDLOCK "HOLD-LOCK" @ MSG_LFL_ENVIRON "ENVIRON" @ MSG_LFL_INTRPOSE "INTERPOSE" @ MSG_LFL_LOCAUDIT "LOCAL-AUDIT" @ MSG_LFL_LOADAVAIL "LOAD-AVAILABLE" @ MSG_LFL_IGNRELERR "IGNORE-RELOCATION-ERROR" @ MSG_LFL_STARTREL "START-RELOCATION" @ MSG_LFL_ATEXIT "ATEXIT" @ MSG_LFL_OBJADDED "OBJECT-ADDED" @ MSG_LFL_OBJDELETED "OBJECT-DELETED" @ MSG_LFL_OBJREEVAL "OBJECT-REEVALUATE" @ MSG_LFL_INTRPOSETSORT "INTERPOSE-TSORTING-DONE" @ MSG_LFL_AUDITNOTIFY "AUDIT-CONSISTENT-REQUIRED" @ MSG_LFL_GROUPSEXIST "LOCAL-GROUPS-EXIST" @ MSG_LFL_TRC_LDDSTUB "LDDSTUB" @ MSG_LFL_TRC_ENABLE "TRACING-ENABLED" @ MSG_LFL_TRC_WARN "TRACE-WARNINGS" @ MSG_LFL_TRC_VERBOSE "TRACE-VERBOSE" @ MSG_LFL_TRC_SEARCH "TRACE-SEARCH" @ MSG_LFL_TRC_UNREF "TRACE-UNREFERENCED" @ MSG_LFL_TRC_UNUSED "TRACE-UNUSED" @ MSG_LFL_TRC_INIT "TRACE-INIT" @ MSG_LFL_TRC_NOUNRESWEAK "TRACE-NO-UNRESOLVED-WEAKS" @ MSG_LFL_TRC_NOPAREXT "TRACE-NO-PARENT-OR-EXTERN" @ MSG_LTFL_NOLAZYLD "NO-LAZY-LOADING" @ MSG_LTFL_NODIRECT "NO-DIRECT" @ MSG_LTFL_NOAUDIT "NO-AUDIT" @ MSG_LTFL_LOADFLTR "LOAD-FILTER" @ MSG_LTFL_AUD_PREINIT "AUD_PREINIT" @ MSG_LTFL_AUD_OBJSEARCH "AUD_OBJSEARCH" @ MSG_LTFL_AUD_OBJOPEN "AUD_OBJOPEN" @ MSG_LTFL_AUD_OBJFILTER "AUD_OBJFILTER" @ MSG_LTFL_AUD_OBJCLOSE "AUD_OBJCLOSE" @ MSG_LTFL_AUD_SYMBIND "AUD_SYMBIND" @ MSG_LTFL_AUD_PLTENTER "AUD_PLTENTER" @ MSG_LTFL_AUD_PLTEXIT "AUD_PLTEXIT" @ MSG_LTFL_AUD_ACTIVITY "AUD_ACTIVITY" @ MSG_LMC_ANALYZING "ANALYZING" @ MSG_LMC_RELOCATING "RELOCATING" @ MSG_LMC_REANALYZE "REANALYZE" # # Error messages # @ MSG_ERR_NAS "%s:: no address specified\n" @ MSG_ERR_READ "%s:: failed to read from: 0x%p\n" @ MSG_ERR_SYMFAILED "rtmaps:: lookup of %s`%s failed\n" # # Various generic format strings # @ MSG_FMT_RT "[0x%x]\t0x%0?p 0x%0?p %s\n" # # Elf header messages. # @ MSG_EHDR_STR "ElfEhdr" @ MSG_EHDR_DCD "Display Elf_Ehdr entry" @ MSG_EHDR_TITLE "Elf Header located at: 0x%p\n" @ MSG_EHDR_LINE1 " ei_magic: { 0x%x, %c, %c, %c }\n" @ MSG_EHDR_LINE2 " ei_class: %-18s ei_data: %s\n" @ MSG_EHDR_LINE3 " e_machine: %-18s e_version: %s\n" @ MSG_EHDR_LINE4 " e_type: %s\n" @ MSG_EHDR_LINE5 " e_flags: %s\n" @ MSG_EHDR_LINE6 " e_flags: %18s\n" @ MSG_EHDR_LINE7 " e_entry: %#18lx e_ehsize: %2d \ e_shstrndx: %2d\n" @ MSG_EHDR_LINE8 " e_shoff: %#18lx e_shentsize: %2d \ e_shnum: %2d\n" @ MSG_EHDR_LINE9 " e_phoff: %#18lx e_phentsize: %2d \ e_phnum: %2d\n" @ MSG_EHDR_HELP "\ Decode an ELF Header (Elf_Ehdr) structure.\n\ \n\ Example:\n\n\ \t> 0x10000::ElfEhdr \n\ \tElf Header located at: 0x10000\n\ \t ei_magic: { 0x7f, E, L, F }\n\ \t ei_class: ELFCLASS32 ei_data: ELFDATA2MSB\n\ \t e_machine: EM_SPARC e_version: EV_CURRENT\n\ \t e_type: ET_EXEC\n\ \t e_flags: 0\n\ \t e_entry: 0x10640 e_ehsize: 52 e_shstrndx: 27\n\ \t e_shoff: 0x1390 e_shentsize: 40 e_shnum: 29\n\ \t e_phoff: 0x34 e_phentsize: 32 e_phnum: 5\n" # # Elf Program Header messages. # @ MSG_EPHDR_STR "ElfPhdr" @ MSG_EPHDR_DCD "Display Elf_Phdr entry" @ MSG_EPHDR_TITLE "Program Header located at: 0x%p\n" @ MSG_EPHDR_LINE1 " p_vaddr: %#-14lx p_flags: %s\n" @ MSG_EPHDR_LINE2 " p_paddr: %#-14lx p_type: %s\n" @ MSG_EPHDR_LINE3 " p_filesz: %#-14lx p_memsz: %#lx\n" @ MSG_EPHDR_LINE4 " p_offset: %#-14lx p_align: %#lx\n" @ MSG_EPHDR_HELP "\ Decode an ELF Program Header (Elf_Phdr) structure.\n\ \n\ Example 1 - a single entry:\n\n\ \t> 0x10000+0x34::ElfPhdr\n\ \tProgram Header located at: 0x10034\n\ \t p_vaddr: 0x10034 p_flags: [ PF_X PF_R ]\n\ \t p_paddr: 0 p_type: [ PT_PHDR ]\n\ \t p_filesz: 0xa0 p_memsz: 0xa0\n\ \t p_offset: 0x34 p_align: 0\n\ \n\ Example 2 - multiple entries:\n\n\ \t> 0x10034,2::ElfPhdr\n\ \tProgram Header located at: 0x10034\n\ \t p_vaddr: 0x10034 p_flags: [ PF_X PF_R ]\n\ \t p_paddr: 0 p_type: [ PT_PHDR ]\n\ \t p_filesz: 0xa0 p_memsz: 0xa0\n\ \t p_offset: 0x34 p_align: 0\n\ \tProgram Header located at: 0x10054\n\ \t p_vaddr: 0 p_flags: [ PF_R ]\n\ \t p_paddr: 0 p_type: [ PT_INTERP ]\n\ \t p_filesz: 0x11 p_memsz: 0\n\ \t p_offset: 0xd4 p_align: 0\n" # # Elf_Dyn messages. # @ MSG_ELFDYN_STR "ElfDyn" @ MSG_ELFDYN_DCD "Display Elf_Dyn entry" @ MSG_ELFDYN_TITLE "Elf_Dyn located at: 0x%p\n" @ MSG_ELFDYN_LINE1 " %a %s\t 0x%0?p\n" @ MSG_ELFDYN_HELP "\ Decode an ELF dynamic section (Elf_Dyn) entry.\n\ \n\ Example 1 - a single entry:\n\n\ \t> 0xff33a478::ElfDyn\n\ \tElf_Dyn located at: 0xff33a478\n\ \t 0xff33a478 NEEDED 0x00004013\n\ \n\ Example 2 - multiple entries:\n\n\ \t> 0xff33a478,4::ElfDyn\n\ \tElf_Dyn located at: 0xff33a478\n\ \t 0xff33a478 NEEDED 0x00004013\n\ \tElf_Dyn located at: 0xff33a480\n\ \t 0xff33a480 INIT 0x0009b484\n\ \tElf_Dyn located at: 0xff33a488\n\ \t 0xff33a488 FINI 0x0009b4dc\n\ \tElf_Dyn located at: 0xff33a490\n\ \t 0xff33a490 SONAME 0x00004009\n" # # Lm_list messages. # @ MSG_LMLIST_STR "Lm_list" @ MSG_LMLIST_DCD "Display Lm_list structure" @ MSG_LMLIST_BASE "(LM_ID_BASE)" @ MSG_LMLIST_LDSO "(LM_ID_LDSO)" @ MSG_LMLIST_NEWLM "(LM_ID_NEWLM)" @ MSG_LMLIST_TITLE0 "Link-map lists (%s): 0x0\n"; @ MSG_LMLIST_TITLE1 "Link-map lists (%s): 0x%p Alist[used %u: total %u]\n"; @ MSG_LMLIST_TITLE2 "Lm_list: 0x%p %s\n" @ MSG_LMLIST_LINE0 " lists: 0x%0?p Alist[used %u: total %u]\n" @ MSG_LMC_LINE1 "Lm_cntl: 0x%p\n" @ MSG_LMC_LINE2 " head: 0x%0?p\t tail: 0x%0?p\n" @ MSG_LMC_LINE3 " flags: 0x%08x\t now: 0x%0?p\n" @ MSG_LMC_LINE4 " [ %#b ]\n" @ MSG_LMLIST_LINE1 " head: 0x%0?p\t tail: 0x%0?p\n" @ MSG_LMLIST_LINE2 " audit: 0x%0?p\t rtldinfo: 0x%0?p\n" @ MSG_LMLIST_LINE3 " handle: 0x%0?p\t obj: %4d init: %4d \ lazy: %4d\n" @ MSG_LMLIST_LINE4 " flags: 0x%08x\n" @ MSG_LMLIST_LINE5 " tflags: 0x%08x\n" @ MSG_LMLIST_LINE6 " [ %#b ]\n" @ MSG_LMLIST_HELP "\ Display a link-map list header. If no address is specified, the \ dynlm_list\nmaster link-map list is searched and all link-map headers \ displayed.\n\ \n\ A link-map list header points to a family of link-map control lists, \ together\nwith other state information. The initial control list represents \ those\nobjects that have been completely analyzed and relocated, and are \ presently in\nuse. \ Additional control lists may exist as families of objects are processed\nin \ preparation for joining the main control list. These families are created\n\ from events such as dlopen(3c), lazy loading, and filtee processing, once\n\ the initial process that started the application has been relocated.\n\ \n\ The -v option walks all link-map control lists displaying each member.\n\ \n\ Example 1 - a single, verbose entry, starting from a specified address:\n\n\ \n\ \t> 0xff3ee0f0::Lm_list -v\n\ \tLm_list: 0xff3ee0f0 (LM_ID_BASE)\n\ \t----------------------------------------------\n\ \t lists: 0xff3f0b00 Alist[used 1: total 4]\n\ \t ----------------------------------------------\n\ \t Lm_cntl: 0xff3f0b0c\n\ \t head: 0xff3f0fdc tail: 0xff3f13bc\n\ \t flags: 0x00000000 now: 0x00000000\n\ \t [ 0 ]\n\ \t ----------------------------------------------\n\ \t lmco rtmap ADDR() NAME()\n\ \t ----------------------------------------------\n\ \t [0xc] 0xff3f0fdc 0x00010000 main\n\ \t [0xc] 0xff3f13bc 0xff280000 /lib/libc.so.1\n\ \t ----------------------------------------------\n\ \t head: 0xff3f0f84 tail: 0xff3f1794\n\ \t audit: 0x00000000 preexec: 0xff3f1794\n\ \t handle: 0x00000000 obj: 3 init: 0 lazy: 0\n\ \t flags: 0x00008041\n\ \t [ BASELM,ENVIRON,STARTREL ]\n\ \t tflags: 0x00000000\n\ \n\ Example 2 - all link-map headers:\n\n\ \t> ::Lm_list\n\ \tLink-map lists (dynlm_list): 0xffbfdbf8\n\ \t----------------------------------------------\n\ \t Lm_list: 0xff3ee0f0 (LM_ID_BASE)\n\ \t ----------------------------------------------\n\ \t lists: 0xff3f0b68 Alist[used 1: total 4]\n\ \t ----------------------------------------------\n\ \t head: 0xff3f0f84 tail: 0xff3f1794\n\ \t audit: 0x00000000 preexec: 0xff3f1794\n\ \t handle: 0x00000000 obj: 3 init: 0 lazy: 0\n\ \t flags: 0x00008041\n\ \t [ BASELM,ENVIRON,STARTREL ]\n\ \t tflags: 0x00000000\n\ \t ----------------------------------------------\n\ \t Lm_list: 0xff3ee134 (LM_ID_LDSO)\n\ \t ----------------------------------------------\n\ \t lists: 0xff3f0ba4 Alist[used 1: total 4]\n\ \t ----------------------------------------------\n\ \t head: 0xff3f0c20 tail: 0xff3f0c20\n\ \t audit: 0x00000000 preexec: 0x00000000\n\ \t handle: 0x00000000 obj: 0 init: 0 lazy: 0\n\ \t flags: 0x00000006\n\ \t [ RTLDLM,NOAUDIT ]\n\ \t tflags: 0x00000000\n" # # Rt_maps messages. # @ MSG_RTMAPS_STR "Rt_maps" @ MSG_RTMAPS_DCD "Display list of Rt_map structures" @ MSG_RTMAPS_TITLE0 "lmco \trtmap ADDR() NAME()\n" @ MSG_RTMAPS_HELP "\ Walk a list of Rt_map's starting with a specified Rt_map entry. If no\n\ address is specified, the dynlm_list master Rt_map list is searched and\n\ all objects listed.\n\ \n\ The -v option expands each Rt_map in detail. See Rt_map.\n\ \n\ Example 1 - a single entry, starting from a specified address:\n\n\ \t> 0xff3b0214::Rt_maps\n\ \tlmco rtmap ADDR() NAME()\n\ \t---------------------------------------------\n\ \t[0xc] 0xff3b0214 0x00010000 prog\n\ \t[0xc] 0xff3b065c 0xff3a0000 /lib/libdl.so.1\n\ \t[0xc] 0xff3b0a04 0xff280000 /lib/libc.so.1\n\ \n\ Example 2 - all Rt_map entries:\n\n\ \t> ::Rt_maps\n\ \tLink-map lists (dynlm_list): 0xffbfe080\n\ \t---------------------------------------------\n\ \t Lm_list: 0xff3f60c8 (LM_ID_BASE)\n\ \t ---------------------------------------------\n\ \t lmco rtmap ADDR() NAME()\n\ \t ---------------------------------------------\n\ \t [0xc] 0xff3b0214 0x00010000 prog\n\ \t [0xc] 0xff3b065c 0xff3a0000 /lib/libdl.so.1\n\ \t [0xc] 0xff3b0a04 0xff280000 /lib/libc.so.1\n\ \t ---------------------------------------------\n\ \t Lm_list: 0xff3f60e8 (LM_ID_LDSO)\n\ \t ---------------------------------------------\n\ \t lmco rtmap ADDR() NAME()\n\ \t ---------------------------------------------\n\ \t [0xc] 0xff3f7cc0 0xff3c0000 /lib/ld.so.1\n" # # Rt_map messages. # @ MSG_RTMAP_STR "Rt_map" @ MSG_RTMAP_DCD "Display Rt_map structure" @ MSG_RTMAP_TITLE "Rt_map located at: 0x%0?p\n" @ MSG_RTMAP_LINE1 " NAME: %s\n" @ MSG_RTMAP_LINE2 " PATHNAME: %s\n" @ MSG_RTMAP_LINE3 " ADDR: 0x%0?p\t DYN: 0x%0?p\n" @ MSG_RTMAP_LINE4 " NEXT: 0x%0?p\t PREV: 0x%0?p\n" @ MSG_RTMAP_LINE5 " FCT: 0x%0?p\t TLSMODID: %?ld\n" @ MSG_RTMAP_LINE6 " INIT: 0x%0?p\t FINI: 0x%0?p\n" @ MSG_RTMAP_LINE7 " GROUPS: 0x%0?p\t HANDLES: 0x%0?p\n" @ MSG_RTMAP_LINE8 " DEPENDS: 0x%0?p\t CALLERS: 0x%0?p\n" @ MSG_RTMAP_LINE9 " DYNINFO: 0x%0?p\t REFNAME: %s\n" @ MSG_RTMAP_LINE10 " RLIST: 0x%0?p\t RPATH: %s\n" @ MSG_RTMAP_LINE11 " LIST: 0x%0?p [%a]\n" @ MSG_RTMAP_LINE12 " FLAGS: 0x%08x\n" @ MSG_RTMAP_LINE13 " FLAGS1: 0x%08x\n" @ MSG_RTMAP_LINE14 " AFLAGS: 0x%08x\n" @ MSG_RTMAP_LINE15 " MODE: 0x%08x\n" @ MSG_RTMAP_LINE20 " [ %#b ]\n" @ MSG_RTMAP_HELP "\ Display the contents of selected fields of an Rt_map structure.\n\ \n\ Example:\n\n\ \t0xff3b0214::Rt_map\n\ \tRt_map located at: 0xff3b0214\n\ \t NAME: prog\n\ \t PATHNAME: /home/developer/bin/prog\n\ \t ADDR: 0x00010000 DYN: 0x00020b1\n\ \t NEXT: 0xff3b065c PREV: 0x0000000\n\ \t FCT: 0xff3f6080 TLSMODID: 0\n\ \t INIT: 0x00010960 FINI: 0x000109b\n\ \t .....\n" # # Bind messages. # @ MSG_BND_STR "Bind" @ MSG_BND_DCD "Display a Binding Descriptor" @ MSG_BND_TITLE "Binding descriptor located at: 0x%0?p\n" @ MSG_BND_LINE1 " caller: 0x%0?p %s\n" @ MSG_BND_LINE2 " depend: 0x%0?p %s\n" @ MSG_BND_LINE3 " flags: 0x%08x [ %#b ]\n" @ MSG_BND_HELP "\ Decode the binding descriptor specified by addr.\n\ \n\ Relationships between Rt_map objects are maintained by a binding dis-\n\ cripter. The descriptor contains a pointer to the Rt_map of the caller,\n\ a pointer to the Rt_map of the dependency, and a flags field indicating\n\ the relationship between the two Rt_map objects.\n\ \n\ The -v option expands each Rt_map.\n\ \n\ Example:\n\n\ \t> 0xff3b0410::Bind\n\ \tBinding Descriptor located at: 0xff3b0410\n\ \t caller: 0xff3b0030 main\n\ \t depend: 0xff3b0460 /lib/libelf.so.1\n\ \t flags: 0x00000001 [ NEEDED ]\n" # # Depends messages. # @ MSG_DEPENDS_STR "Depends" @ MSG_DEPENDS_DCD "Display Rt_map DEPENDS binding descriptors" @ MSG_DEPENDS_LINE1 "DEPENDS for %s\n" @ MSG_DEPENDS_LINE2 " Depends: 0x%0?p APlist[used %u: total %u]\n" @ MSG_DEPENDS_HELP "\ Display the binding descriptor list of DEPENDS() of the Rt_map\n\ specified by addr. A DEPENDS() entry consists of an APlist\n\ defining each dependency.\n\ \n\ The -v option walks the APlist descriptor displaying each dependency.\n\ \n\ Example:\n\n\ \t> 0xff3b0554::Depends -v\n\ \tDEPENDS for /lib/libc.so.1\n\ \t----------------------------------------------\n\ \t Depends: 0xff3b0bc4 APlist[used 2: total 4]\n\ \t ----------------------------------------------\n\ \t Binding descriptor located at: 0xff3b08f8\n\ \t caller: 0xff3b0554 /lib/libc.so.1\n\ \t depend: 0xff3b0938 /lib/libdl.so.1\n\ \t flags: 0x00000003 [ NEEDED,REFERENCED ]\n\ \t ----------------------------------------------\n\ \t Binding descriptor located at: 0xff3b07a8\n\ \t caller: 0xff3b0554 /lib/libc.so.1\n\ \t depend: 0xff3f7cc0 /lib/ld.so.1\n\ \t flags: 0x00000002 [ REFERENCED ]\n" # # Callers messages. # @ MSG_CALLERS_STR "Callers" @ MSG_CALLERS_DCD "Display Rt_map CALLERS binding descriptors" @ MSG_CALLERS_LINE1 "CALLERS for %s\n" @ MSG_CALLERS_LINE2 " Callers: 0x%0?p APlist[used %u: total %u]\n" @ MSG_CALLERS_HELP "\ Display the binding descriptor list of CALLERS() of the Rt_map\n\ specified by addr. A CALLERS() entry consists of an APlist\n\ defining each caller.\n\ \n\ The -v option walks the APlist descriptor displaying each caller.\n\ \n\ Example:\n\n\ \t> 0xff3b0554::Callers -v\n\ \tCALLERS for /lib/libc.so.1\n\ \t----------------------------------------------\n\ \t Callers: 0xff3b08cc APlist[used 1: total 4]\n\ \t ----------------------------------------------\n\ \t Binding descriptor located at: 0xff3b0514\n\ \t caller: 0xff3b0214 executable\n\ \t depend: 0xff3b0554 /lib/libc.so.1\n\ \t flags: 0x00000003 [ NEEDED,REFERENCED ]\n" # # Handle messages. # @ MSG_HANDLES_STR "Handles" @ MSG_HANDLES_DCD "Display Rt_map HANDLES group descriptors" @ MSG_HANDLES_LINE1 "HANDLES for %s\n" @ MSG_HANDLES_LINE2 " HANDLE: 0x%0?p APlist[used %u: total %u]\n" @ MSG_HANDLES_HELP "\ Display the list of HANDLES() that the Rt_map, specified by addr, is the\n\ owner of. A HANDLES() entry consists of an APlist of Grp_hdl descriptors.\n\ See GrpHdl. Each Grp_hdl() consists of an APlist of Grp_desc descriptors\n\ that define each caller. See GrpDesc.\n\ \n\ The -v option walks all APlists displaying each member of the handle.\n\ \n\ Example:\n\n\ \t> 0xff3b0f6c::Handles -v\n\ \tHANDLES for ./sub.so\n\ \t----------------------------------------------\n\ \t HANDLE: 0xff3b1310 APlist[used 1: total 1]\n\ \t ----------------------------------------------\n\ \t Group Handle located at: 0xff3b1270\n\ \t ----------------------------------------------\n\ \t owner: ./sub.so\n\ \t flags: 0x00000000 [ 0 ]\n\ \t refcnt: 1 depends: 0xff3b12c8 Alist[used 3: total 4]\n\ \t ----------------------------------------------\n\ \t Group Descriptor located at: 0xff3b12d4\n\ \t depend: 0xff3b0f6c ./sub.so\n\ \t flags: 0x00000003 [ AVAIL-TO-DLSYM,ADD-DEPENDENCIES ]\n\ \t ----------------------------------------------\n\ \t Group Descriptor located at: 0xff3b12dc\n\ \t depend: 0xff3b0a04 /lib/libc.so.1\n\ \t flags: 0x00000003 [ AVAIL-TO-DLSYM,ADD-DEPENDENCIES ]\n\ \t ----------------------------------------------\n\ \t Group Descriptor located at: 0xff3b12e4\n\ \t depend: 0xff3b065c /lib/libdl.so.1\n\ \t flags: 0x00000003 [ AVAIL-TO-DLSYM,ADD-DEPENDENCIES ]\n" # # Group messages. # @ MSG_GROUPS_STR "Groups" @ MSG_GROUPS_DCD "Display Rt_map GROUPS group handles" @ MSG_GROUPS_LINE1 "GROUPS for %s\n" @ MSG_GROUPS_LINE2 " Groups: 0x%0?p APlist[used %u: total %u]\n" @ MSG_GROUPS_HELP "\ Display the list of GROUPS() that the Rt_map, specified by addr, is a \ member\nof. A GROUPS() entry consists of an APlist of Grp_hdl entries. \ See GrpHdl.\n\ \n\ The -v option walks the APlist descriptor displaying each member of the\n\ handle.\n\ \n\ Example:\n\n\ \t> 0xff3b0f6c::Groups -v\n\ \tGROUPS for ./sub.so\n\ \t----------------------------------------------\n\ \t Groups: 0xff3b12a8 APlist[used 1: total 1]\n\ \t ----------------------------------------------\n\ \t Group Handle located at: 0xff3b1270\n\ \t ----------------------------------------------\n\ \t owner: ./sub.so\n\ \t flags: 0x00000000 [ 0 ]\n\ \t refcnt: 1 depends: 0xff3b12c8 Alist[used 3: total 4]\n\ \t ----------------------------------------------\n\ \t Group Descriptor located at: 0xff3b12d4\n\ \t depend: 0xff3b0f6c ./sub.so\n\ \t flags: 0x00000003 [ AVAIL-TO-DLSYM,ADD-DEPENDENCIES ]\n\ \t ----------------------------------------------\n\ \t Group Descriptor located at: 0xff3b12dc\n\ \t depend: 0xff3b0a04 /lib/libc.so.1\n\ \t flags: 0x00000004 [ AVAIL-TO-DLSYM,ADD-DEPENDENCIES ]\n\ \t ----------------------------------------------\n\ \t Group Descriptor located at: 0xff3b12e4\n\ \t depend: 0xff3b065c /lib/libdl.so.1\n\ \t flags: 0x00000004 [ AVAIL-TO-DLSYM,ADD-DEPENDENCIES ]\n" # # Group handle and group descriptor messages. # @ MSG_GRPHDL_STR "GrpHdl" @ MSG_GRPHDL_DCD "Display a Group Handle" @ MSG_GRPHDL_LINE1 "Group Handle located at: 0x%0?p\n" @ MSG_GRPHDL_LINE2 " owner: %s\n" @ MSG_GRPHDL_LINE3 " flags: 0x%08x [ %#b ]\n" @ MSG_GRPHDL_LINE4 " refcnt: %8d depends: 0\n" @ MSG_GRPHDL_LINE5 " refcnt: %8d depends: 0x%0?p \ Alist[used %u: total %u]\n" @ MSG_GRPHDL_HELP "\ Decode a group handle. A successful dlopen() of an object returns a\n\ handle applicable for subsequent dlsym() requests. The handle maintains\n\ a reference count, flags and a table of group descriptors that define the\n\ dependencies associated with this group of objects.\n\ \n\ The -v option expands each group descriptor.\n\ \n\ Example:\n\n\ \t> 0xff3a0c58::GrpHdl\n\ \tGroup Handle located at: 0xff3a0c58\n\ \t----------------------------------------------\n\ \t refcnt: 1 main\n\ \t flags: 0x00000001 [ DLOPEN-ZERO ]\n\ \t depends: 0xff3a0c89 Alist[next 1: cnt 2]\n" @ MSG_GRPDESC_STR "GrpDesc" @ MSG_GRPDESC_DCD "Display a Group Descriptor" @ MSG_GRPDESC_LINE1 "Group Descriptor located at: 0x%0?p\n" @ MSG_GRPDESC_LINE2 " depend: 0x%0?p %s\n" @ MSG_GRPDESC_LINE3 " flags: 0x%08x [ %#b ]\n" @ MSG_GRPDESC_HELP "\ Decode a group descriptor. A GrpHdl created by dlopen() maintains a\n\ list of group descriptors for each dependency. See GrpHdl.\n\ \n\ Example\n\n\ \t> 0xff3b12d4::GrpDesc -v\n\ \tGroup Descriptor located at: 0xff3b12d4\n\ \t depend: 0xff3b0f6c ./sub.so\n\ \t flags: 0x00000003 [ AVAIL-TO-DLSYM,ADD-DEPENDENCIES ]\n" @ MSG_BNDDESC_STR "BndDesc" # # General purpose environment addition. # @ MSG_SETENV_STR "Setenv" @ MSG_SETENV_DCD "Set an environment variable" @ MSG_SETENV_HELP "\ Set the associated environment variable. This can be useful to establish\n\ runtime linker environment variables without affecting the debugger itself.\n\ \n\ Note, newer versions of mdb(1) provide setenv. The mdb(1) implementation\n\ provides the ability to make environment changes at any point during\n\ application monitoring. Where available, Setenv calls through to setenv.\n\ On older environments, Setenv only provides for changing the applications\n\ environment before it is invoked.\n\ \n\ Example\n\n\ \t> ::Setenv LD_DEBUG=files\n"