/*
 * 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.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Object file dependent support for a.out format objects.
 */
#include	"_synonyms.h"

#include	<sys/mman.h>
#include	<unistd.h>
#include	<string.h>
#include	<limits.h>
#include	<stdio.h>
#include	<dlfcn.h>
#include	<errno.h>
#include	"_a.out.h"
#include	"cache_a.out.h"
#include	"msg.h"
#include	"_rtld.h"
#include	"debug.h"

/*
 * Default and secure dependency search paths.
 */
static Pnode		aout_dflt_dirs[] = {
	{ MSG_ORIG(MSG_PTH_USR4LIB),	0,	MSG_PTH_USR4LIB_SIZE,
		LA_SER_DEFAULT,		0,	&aout_dflt_dirs[1] },
	{ MSG_ORIG(MSG_PTH_USRLIB),	0,	MSG_PTH_USRLIB_SIZE,
		LA_SER_DEFAULT,		0,	&aout_dflt_dirs[2] },
	{ MSG_ORIG(MSG_PTH_USRLCLIB),	0,	MSG_PTH_USRLCLIB_SIZE,
		LA_SER_DEFAULT,		0, 0 }
};

static Pnode		aout_secure_dirs[] = {
	{ MSG_ORIG(MSG_PTH_USR4LIB),	0,	MSG_PTH_USR4LIB_SIZE,
		LA_SER_SECURE,		0,	&aout_secure_dirs[1] },
	{ MSG_ORIG(MSG_PTH_USRLIB),	0,	MSG_PTH_USRLIB_SIZE,
		LA_SER_SECURE,		0,	&aout_secure_dirs[2] },
	{ MSG_ORIG(MSG_PTH_USRUCBLIB),	0,	MSG_PTH_USRUCBLIB_SIZE,
		LA_SER_SECURE,		0,	&aout_secure_dirs[3] },
	{ MSG_ORIG(MSG_PTH_USRLCLIB),	0,	MSG_PTH_USRLCLIB_SIZE,
		LA_SER_SECURE,		0, 0 }
};

/*
 * Defines for local functions.
 */
static int		aout_are_u();
static ulong_t		aout_entry_pt();
static Rt_map		*aout_map_so();
static void		aout_unmap_so();
static int		aout_needed();
extern Sym		*aout_lookup_sym();
static Sym		*aout_find_sym();
static char		*aout_get_so();
static Pnode		*aout_fix_name();
static void		aout_dladdr();
static Sym		*aout_dlsym_handle();
static int		aout_verify_vers();

/*
 * Functions and data accessed through indirect pointers.
 */
Fct aout_fct = {
	aout_are_u,
	aout_entry_pt,
	aout_map_so,
	aout_unmap_so,
	aout_needed,
	aout_lookup_sym,
	aout_reloc,
	aout_dflt_dirs,
	aout_secure_dirs,
	aout_fix_name,
	aout_get_so,
	aout_dladdr,
	aout_dlsym_handle,
	aout_verify_vers,
	aout_set_prot
};


/*
 * In 4.x, a needed file or a dlopened file that was a simple file name implied
 * that the file be found in the present working directory.  To simulate this
 * lookup within the elf rules it is necessary to add a proceeding `./' to the
 * filename.
 */
static Pnode *
aout_fix_name(const char *name)
{
	size_t	len;
	Pnode	*pnp;

	if ((pnp = calloc(1, sizeof (Pnode))) == 0)
		return (0);

	/*
	 * Check for slash in name, if none, prepend "./", otherwise just
	 * return name given.
	 */
	if (strchr(name, '/')) {
		len = strlen(name) + 1;
		if ((pnp->p_name = malloc(len)) != 0)
			(void) strcpy((char *)pnp->p_name, name);
	} else {
		len = strlen(name) + 3;
		if ((pnp->p_name = malloc(len)) != 0)
			(void) snprintf((char *)pnp->p_name, len,
			    MSG_ORIG(MSG_FMT_4XPATH), name);
	}

	if (pnp->p_name) {
		pnp->p_len = len;
		pnp->p_orig = PN_SER_NEEDED;
		DBG_CALL(Dbg_file_fixname(pnp->p_name, name));
		return (pnp);
	}
	free(pnp);
	return (0);
}

/*
 * Determine if we have been given an A_OUT file.  Returns 1 if true.
 */
static int
aout_are_u()
{
	struct exec *exec;

	/* LINTED */
	exec = (struct exec *)fmap->fm_maddr;
	if (fmap->fm_fsize < sizeof (exec) || (exec->a_machtype != M_SPARC) ||
	    (N_BADMAG(*exec))) {
		return (0);
	}
	return (1);
}

/*
 * Return the entry point the A_OUT executable. This is always zero.
 */
static ulong_t
aout_entry_pt()
{
	return (0);
}

/*
 * Unmap a given A_OUT shared object from the address space.
 */
static void
aout_unmap_so(Rt_map *lmp)
{
	Mmap	*immap = MMAPS(lmp);

	(void) munmap(immap->m_vaddr, immap->m_msize);
}

/*
 * Dummy versioning interface - real functionality is only applicable to elf.
 */
static int
aout_verify_vers()
{
	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
aout_needed(Lm_list *lml, Aliste lmco, Rt_map *clmp)
{
	void	*need;

	for (need = &TEXTBASE(clmp)[AOUTDYN(clmp)->v2->ld_need];
	    need != &TEXTBASE(clmp)[0];
	    need = &TEXTBASE(clmp)[((Lnk_obj *)(need))->lo_next]) {
		Rt_map	*nlmp;
		char	*name;
		Pnode	*pnp;

		name = &TEXTBASE(clmp)[((Lnk_obj *)(need))->lo_name];

		if (((Lnk_obj *)(need))->lo_library) {
			/*
			 * If lo_library field is not NULL then this needed
			 * library was linked in using the "-l" option.
			 * Thus we need to rebuild the library name before
			 * trying to load it.
			 */
			Pnode	*dir, *dirlist = (Pnode *)0;
			char	*file;
			size_t	len;

			/*
			 * Allocate name length plus 20 for full library name.
			 * lib.so.. = 7 + (2 * short) + NULL = 7 + 12 + 1 = 20
			 */
			len = strlen(name) + 20;
			if ((file = malloc(len)) == 0)
				return (0);
			(void) snprintf(file, len, MSG_ORIG(MSG_FMT_4XLIB),
			    name, ((Lnk_obj *)(need))->lo_major,
			    ((Lnk_obj *)(need))->lo_minor);

			DBG_CALL(Dbg_libs_find(file));

			/*
			 * We need to determine what filename will match the
			 * the filename specified (ie, a libc.so.1.2 may match
			 * to a libc.so.1.3).  It's the real pathname that is
			 * recorded in the link maps.  If we are presently
			 * being traced, skip this pathname generation so
			 * that we fall through into load_so() to print the
			 * appropriate diagnostics.  I don't like this at all.
			 */
			if (lml->lm_flags & LML_FLG_TRC_ENABLE)
				name = file;
			else {
				char	*path = (char *)0;

				for (dir = get_next_dir(&dirlist, clmp, 0); dir;
				    dir = get_next_dir(&dirlist, clmp, 0)) {
					if (dir->p_name == 0)
						continue;

					if (path =
					    aout_get_so(dir->p_name, file))
						break;
				}
				if (!path) {
					eprintf(ERR_FATAL,
					    MSG_INTL(MSG_SYS_OPEN), file,
						strerror(ENOENT));
					return (0);
				}
				name = path;
			}
			if ((pnp = expand_paths(clmp, name,
			    PN_SER_NEEDED, 0)) == 0)
				return (0);
		} else {
			/*
			 * If the library is specified as a pathname, see if
			 * it must be fixed to specify the current working
			 * directory (ie. libc.so.1.2 -> ./libc.so.1.2).
			 */
			if ((pnp = aout_fix_name(name)) == 0)
				return (0);
		}
		DBG_CALL(Dbg_file_needed(name, NAME(clmp)));

		nlmp = load_one(lml, lmco, pnp, clmp, MODE(clmp), 0, 0);
		remove_pnode(pnp);
		if (((nlmp == 0) || (bind_one(clmp, nlmp, BND_NEEDED) == 0)) &&
		    ((lml->lm_flags & LML_FLG_TRC_ENABLE) == 0))
			return (0);
	}

	return (1);
}

static Sym *
aout_symconvert(struct nlist *sp)
{
	static Sym	sym;

	sym.st_value = sp->n_value;
	sym.st_size = 0;
	sym.st_info = 0;
	sym.st_other = 0;
	switch (sp->n_type) {
		case N_EXT + N_ABS:
			sym.st_shndx = SHN_ABS;
			break;
		case N_COMM:
			sym.st_shndx = SHN_COMMON;
			break;
		case N_EXT + N_UNDF:
			sym.st_shndx = SHN_UNDEF;
			break;
		default:
			sym.st_shndx = 0;
			break;
	}
	return (&sym);
}

/*
 * Process a.out format commons.
 */
static struct nlist *
aout_find_com(struct nlist *sp, const char *name)
{
	static struct rtc_symb	*rtcp = 0;
	struct rtc_symb		*rs, * trs;
	const char		*sl;
	char			*cp;

	/*
	 * See if common is already allocated.
	 */
	trs = rtcp;
	while (trs) {
		sl = name;
		cp = trs->rtc_sp->n_un.n_name;
		while (*sl == *cp++)
			if (*sl++ == '\0')
				return (trs->rtc_sp);
		trs = trs->rtc_next;
	}

	/*
	 * If we got here, common is not already allocated so allocate it.
	 */
	if ((rs = malloc(sizeof (struct rtc_symb))) == 0)
		return (0);
	if ((rs->rtc_sp = malloc(sizeof (struct nlist))) == 0)
		return (0);
	trs = rtcp;
	rtcp = rs;
	rs->rtc_next = trs;
	*(rs->rtc_sp) = *sp;
	if ((rs->rtc_sp->n_un.n_name = malloc(strlen(name) + 1)) == 0)
		return (0);
	(void) strcpy(rs->rtc_sp->n_un.n_name, name);
	rs->rtc_sp->n_type = N_COMM;
	if ((rs->rtc_sp->n_value = (long)calloc(rs->rtc_sp->n_value, 1)) == 0)
		return (0);
	return (rs->rtc_sp);
}

/*
 * Find a.out format symbol in the specified link map.  Unlike the sister
 * elf routine we re-calculate the symbols hash value for each link map
 * we're looking at.
 */
static struct nlist *
aout_findsb(const char *aname, Rt_map *lmp, int flag)
{
	const char	*name = aname;
	char		*cp;
	struct fshash	*p;
	int		i;
	struct nlist	*sp;
	ulong_t		hval = 0;

#define	HASHMASK	0x7fffffff
#define	RTHS		126

	/*
	 * The name passed to us is in ELF format, thus it is necessary to
	 * map this back to the A_OUT format to compute the hash value (see
	 * mapping rules in aout_lookup_sym()).  Basically the symbols are
	 * mapped according to whether a leading `.' exists.
	 *
	 *	elf symbol		a.out symbol
	 * i.	   .bar		->	   .bar		(LKUP_LDOT)
	 * ii.	   .nuts	->	    nuts
	 * iii.	    foo		->	   _foo
	 */
	if (*name == '.') {
		if (!(flag & LKUP_LDOT))
			name++;
	} else
		hval = '_';

	while (*name)
		hval = (hval << 1) + *name++;
	hval = hval & HASHMASK;

	i = hval % (AOUTDYN(lmp)->v2->ld_buckets == 0 ? RTHS :
		AOUTDYN(lmp)->v2->ld_buckets);
	p = LM2LP(lmp)->lp_hash + i;

	if (p->fssymbno != -1)
		do {
			sp = &LM2LP(lmp)->lp_symtab[p->fssymbno];
			cp = &LM2LP(lmp)->lp_symstr[sp->n_un.n_strx];
			name = aname;
			if (*name == '.') {
				if (!(flag & LKUP_LDOT))
					name++;
			} else {
				cp++;
			}
			while (*name == *cp++) {
				if (*name++ == '\0')
					return (sp);	/* found */
			}
			if (p->next == 0)
				return (0);		/* not found */
			else
				continue;
		} while ((p = &LM2LP(lmp)->lp_hash[p->next]) != 0);
	return (0);
}

/*
 * The symbol name we have been asked to look up is in A_OUT format, this
 * symbol is mapped to the appropriate ELF format which is the standard by
 * which symbols are passed around ld.so.1.  The symbols are mapped
 * according to whether a leading `_' or `.' exists.
 *
 *	a.out symbol		elf symbol
 * i.	   _foo		->	    foo
 * ii.	   .bar		->	   .bar		(LKUP_LDOT)
 * iii.	    nuts	->	   .nuts
 */
Sym *
aout_lookup_sym(Slookup *slp, Rt_map **dlmp, uint_t *binfo)
{
	char	name[PATH_MAX];
	Slookup	sl = *slp;

	DBG_CALL(Dbg_syms_lookup_aout(slp->sl_name));

	if (*sl.sl_name == '_')
		++sl.sl_name;
	else if (*sl.sl_name == '.')
		sl.sl_flags |= LKUP_LDOT;
	else {
		name[0] = '.';
		(void) strcpy(&name[1], sl.sl_name);
		sl.sl_name = name;
	}

	/*
	 * Call the generic lookup routine to cycle through the specified
	 * link maps.
	 */
	return (lookup_sym(&sl, dlmp, binfo));
}

/*
 * Symbol lookup for an a.out format module.
 */
static Sym *
aout_find_sym(Slookup *slp, Rt_map **dlmp, uint_t *binfo)
{
	const char	*name = slp->sl_name;
	Rt_map		*ilmp = slp->sl_imap;
	struct nlist	*sp;

	DBG_CALL(Dbg_syms_lookup(name, NAME(ilmp), MSG_ORIG(MSG_STR_AOUT)));

	if (sp = aout_findsb(name, ilmp, slp->sl_flags)) {
		if (sp->n_value != 0) {
			/*
			 * is it a common?
			 */
			if (sp->n_type == (N_EXT + N_UNDF)) {
				if ((sp = aout_find_com(sp, name)) == 0)
					return ((Sym *)0);
			}
			*dlmp = ilmp;
			*binfo |= DBG_BINFO_FOUND;
			return (aout_symconvert(sp));
		}
	}
	return ((Sym *)0);
}

/*
 * Map in an a.out format object.
 * Takes an open file descriptor for the object to map and
 * its pathname; returns a pointer to a Rt_map structure
 * for this object, or 0 on error.
 */
static Rt_map *
aout_map_so(Lm_list *lml, Aliste lmco, const char *pname, const char *oname,
    int fd)
{
	struct exec	*exec;		/* working area for object headers */
	caddr_t		addr;		/* mmap result temporary */
	struct link_dynamic *ld;	/* dynamic pointer of object mapped */
	size_t		size;		/* size of object */
	Rt_map		*lmp;		/* link map created */
	int		err;
	struct nlist	*nl;

	/*
	 * Map text and allocate enough address space to fit the whole
	 * library.  Note that we map enough to catch the first symbol
	 * in the symbol table and thereby avoid an "lseek" & "read"
	 * pair to pick it up.
	 */
	/* LINTED */
	exec = (struct exec *)fmap->fm_maddr;
	size = max(SIZE(*exec), N_SYMOFF(*exec) + sizeof (struct nlist));
	if ((addr = mmap(0, size, (PROT_READ | PROT_EXEC), MAP_PRIVATE,
	    fd, 0)) == MAP_FAILED) {
		err = errno;
		eprintf(ERR_FATAL, MSG_INTL(MSG_SYS_MMAP), pname,
		    strerror(err));
		return (0);
	}

	/*
	 * Grab the first symbol entry while we've got it mapped aligned
	 * to file addresses.  We assume that this symbol describes the
	 * object's link_dynamic.
	 */
	/* LINTED */
	nl = (struct nlist *)&addr[N_SYMOFF(*exec)];
	/* LINTED */
	ld = (struct link_dynamic *)&addr[nl->n_value];

	/*
	 * Map the initialized data portion of the file to the correct
	 * point in the range of allocated addresses.  This will leave
	 * some portion of the data segment "doubly mapped" on machines
	 * where the text/data relocation alignment is not on a page
	 * boundaries.  However, leaving the file mapped has the double
	 * advantage of both saving the munmap system call and of leaving
	 * us a contiguous chunk of address space devoted to the object --
	 * in case we need to unmap it all later.
	 */
	if (mmap((caddr_t)(addr + M_SROUND(exec->a_text)),
	    (int)exec->a_data, (PROT_READ | PROT_WRITE | PROT_EXEC),
	    (MAP_FIXED | MAP_PRIVATE), fd, (off_t)exec->a_text) == MAP_FAILED) {
		err = errno;
		eprintf(ERR_FATAL, MSG_INTL(MSG_SYS_MMAP), pname,
		    strerror(err));
		return (0);
	}

	/*
	 * Allocate pages for the object's bss, if necessary.
	 */
	if (exec->a_bss != 0) {
		if (dz_map(addr + M_SROUND(exec->a_text) + exec->a_data,
		    (int)exec->a_bss, PROT_READ | PROT_WRITE | PROT_EXEC,
		    MAP_FIXED | MAP_PRIVATE) == MAP_FAILED)
			goto error;
	}

	/*
	 * Create link map structure for newly mapped shared object.
	 */
	ld->v2 = (struct link_dynamic_2 *)((int)ld->v2 + (int)addr);
	if (!(lmp = aout_new_lm(lml, pname, oname, ld, addr, size, lmco)))
		goto error;

	return (lmp);

	/*
	 * Error returns: close off file and free address space.
	 */
error:
	(void) munmap((caddr_t)addr, size);
	return (0);
}

/*
 * Create a new Rt_map structure for an a.out format object and
 * initializes all values.
 */
Rt_map *
aout_new_lm(Lm_list *lml, const char *pname, const char *oname,
    struct link_dynamic *ld, caddr_t addr, size_t size, Aliste lmco)
{
	Rt_map	*lmp;
	caddr_t offset;

	DBG_CALL(Dbg_file_aout(pname, (ulong_t)ld, (ulong_t)addr,
	    (ulong_t)size));

	/*
	 * Allocate space for the link-map and private a.out information.  Once
	 * these are allocated and initialized, we can use remove_so(0, lmp) to
	 * tear down the link-map should any failures occur.
	 */
	if ((lmp = calloc(sizeof (Rt_map), 1)) == 0)
		return (0);
	if ((AOUTPRV(lmp) = calloc(sizeof (Rt_aoutp), 1)) == 0) {
		free(lmp);
		return (0);
	}
	if ((((Rt_aoutp *)AOUTPRV(lmp))->lm_lpd =
	    calloc(sizeof (struct ld_private), 1)) == 0) {
		free(AOUTPRV(lmp));
		free(lmp);
		return (0);
	}

	/*
	 * All fields not filled in were set to 0 by calloc.
	 */
	ORIGNAME(lmp) = PATHNAME(lmp) = NAME(lmp) = (char *)pname;
	ADDR(lmp) = (ulong_t)addr;
	MSIZE(lmp) = (ulong_t)size;
	SYMINTP(lmp) = aout_find_sym;
	FCT(lmp) = &aout_fct;
	LIST(lmp) = lml;
	THREADID(lmp) = rt_thr_self();
	OBJFLTRNDX(lmp) = FLTR_DISABLED;

	/*
	 * Specific settings for a.out format.
	 */
	if (lml->lm_head == 0) {
		offset = (caddr_t)MAIN_BASE;
		FLAGS(lmp) |= FLG_RT_FIXED;
	} else
		offset = addr;

	ETEXT(lmp) = (ulong_t)&offset[ld->v2->ld_text];

	/*
	 * Create a mapping descriptor to describe the whole object as a single
	 * mapping.
	 */
	if ((MMAPS(lmp) = calloc(2, sizeof (Mmap))) == 0)
		return (0);
	MMAPS(lmp)->m_vaddr = offset;
	/* LINTED */
	MMAPS(lmp)->m_msize = max(SIZE(*(struct exec *)offset),
	    N_SYMOFF((*(struct exec *)offset)) + sizeof (struct nlist));
	MMAPS(lmp)->m_fsize = MMAPS(lmp)->m_msize;
	MMAPCNT(lmp) = 1;

	/*
	 * Fill in all AOUT information.
	 */
	AOUTDYN(lmp) = ld;
	if ((RPATH(lmp) = (char *)&offset[ld->v2->ld_rules]) == offset)
		RPATH(lmp) = 0;
	LM2LP(lmp)->lp_symbol_base = addr;
	/* LINTED */
	LM2LP(lmp)->lp_plt = (struct jbind *)(&addr[JMPOFF(ld)]);
	LM2LP(lmp)->lp_rp =
	/* LINTED */
	    (struct relocation_info *)(&offset[RELOCOFF(ld)]);
	/* LINTED */
	LM2LP(lmp)->lp_hash = (struct fshash *)(&offset[HASHOFF(ld)]);
	/* LINTED */
	LM2LP(lmp)->lp_symtab = (struct nlist *)(&offset[SYMOFF(ld)]);
	LM2LP(lmp)->lp_symstr = &offset[STROFF(ld)];
	LM2LP(lmp)->lp_textbase = offset;
	LM2LP(lmp)->lp_refcnt++;
	LM2LP(lmp)->lp_dlp = NULL;

	if (rtld_flags & RT_FL_RELATIVE)
		FLAGS1(lmp) |= FL1_RT_RELATIVE;

	if ((CONDVAR(lmp) = rt_cond_create()) == 0) {
		remove_so(0, lmp);
		return (0);
	}
	if (oname && ((append_alias((lmp), oname, 0)) == 0)) {
		remove_so(0, lmp);
		return (0);
	}

	/*
	 * Add the mapped object to the end of the link map list.
	 */
	lm_append(lml, lmco, lmp);
	return (lmp);
}

/*
 * Function to correct protection settings.
 * Segments are all mapped initially with permissions as given in
 * the segment header, but we need to turn on write permissions
 * on a text segment if there are any relocations against that segment,
 * and them turn write permission back off again before returning control
 * to the program.  This function turns the permission on or off depending
 * on the value of the argument.
 */
int
aout_set_prot(Rt_map *lm, int permission)
{
	int		prot;		/* protection setting */
	caddr_t		et;		/* cached _etext of object */
	size_t		size;		/* size of text segment */

	DBG_CALL(Dbg_file_prot(NAME(lm), permission));

	et = (caddr_t)ETEXT(lm);
	size = M_PROUND((ulong_t)(et - TEXTBASE(lm)));
	prot = PROT_READ | PROT_EXEC | permission;
	if (mprotect((caddr_t)TEXTBASE(lm), size, prot) == -1) {
		int	err = errno;

		eprintf(ERR_FATAL, MSG_INTL(MSG_SYS_MPROT), NAME(lm),
		    strerror(err));
		return (0);
	}
	return (1);
}

/*
 * Build full pathname of shared object from the given directory name and
 * filename.
 */
static char *
aout_get_so(const char *dir, const char *file)
{
	struct db	*dbp;
	char		*path = NULL;

	if (dbp = lo_cache(dir)) {
		path = ask_db(dbp, file);
	}
	return (path);
}

/*
 * Determine the symbol location of an address within a link-map.  Look for
 * the nearest symbol (whoes value is less than or equal to the required
 * address).  This is the object specific part of dladdr().
 */
static void
aout_dladdr(ulong_t addr, Rt_map *lmp, Dl_info *dlip, void **info,
    int flags)
{
	ulong_t		ndx, cnt, base, _value;
	struct nlist	*sym, *_sym;

	cnt = ((int)LM2LP(lmp)->lp_symstr - (int)LM2LP(lmp)->lp_symtab) /
		sizeof (struct nlist);
	sym = LM2LP(lmp)->lp_symtab;

	if (FLAGS(lmp) & FLG_RT_FIXED)
		base = 0;
	else
		base = ADDR(lmp);

	for (_sym = 0, _value = 0, ndx = 0; ndx < cnt; ndx++, sym++) {
		ulong_t	value;

		if (sym->n_type == (N_EXT + N_UNDF))
			continue;

		value = sym->n_value + base;
		if (value > addr)
			continue;
		if (value < _value)
			continue;

		_sym = sym;
		_value = value;

		if (value == addr)
			break;
	}

	if (_sym) {
		int	_flags = flags & RTLD_DL_MASK;

		/*
		 * The only way we can create a symbol entry is to use
		 * aout_symconvert(), however this results in us pointing to
		 * static data that could be overridden.  In addition the AOUT
		 * symbol format doesn't give us everything an ELF symbol does.
		 * So, unless we get convinced otherwise, don't bother returning
		 * a symbol entry for AOUT's.
		 */
		if (_flags == RTLD_DL_SYMENT)
			*info = 0;
		else if (_flags == RTLD_DL_LINKMAP)
			*info = (void *)lmp;

		dlip->dli_sname = &LM2LP(lmp)->lp_symstr[_sym->n_un.n_strx];
		dlip->dli_saddr = (void *)_value;
	}
}

/*
 * Continue processing a dlsym request.  Lookup the required symbol in each
 * link-map specified by the handle.  Note, that because this lookup is against
 * individual link-maps we don't need to supply a starting link-map to the
 * lookup routine (see lookup_sym():analyze.c).
 */
Sym *
aout_dlsym_handle(Grp_hdl * ghp, Slookup *slp, Rt_map **_lmp, uint_t *binfo)
{
	Sym	*sym;
	char	buffer[PATH_MAX];
	Slookup	sl;

	buffer[0] = '_';
	(void) strcpy(&buffer[1], slp->sl_name);

	if ((sym = dlsym_handle(ghp, slp, _lmp, binfo)) != 0)
		return (sym);

	/*
	 * Symbol not found as supplied.  However, most of our symbols will
	 * be in the "C" name space, where the implementation prepends a "_"
	 * to the symbol as it emits it.  Therefore, attempt to find the
	 * symbol with the "_" prepend.
	 */
	sl = *slp;
	sl.sl_name = (const char *)buffer;

	return (dlsym_handle(ghp, &sl, _lmp, binfo));
}