/*
 * 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	<stdlib.h>
#include	<stdio.h>
#include	<proc_service.h>
#include	<link.h>
#include	<rtld_db.h>
#include	<rtld.h>
#include	<alist.h>
#include	<list.h>
#include	<_rtld_db.h>
#include	<msg.h>
#include	<limits.h>
#include	<string.h>
#include	<sys/param.h>

/*
 * 64-bit builds are going to compile this module twice, the
 * second time with _ELF64 defined.  These defines should make
 * all the necessary adjustments to the code.
 */
#ifdef _LP64
#ifdef _ELF64
#define	_rd_event_enable32	_rd_event_enable64
#define	_rd_event_getmsg32	_rd_event_getmsg64
#define	_rd_get_dyns32		_rd_get_dyns64
#define	_rd_get_ehdr32		_rd_get_ehdr64
#define	_rd_objpad_enable32	_rd_objpad_enable64
#define	_rd_loadobj_iter32	_rd_loadobj_iter64
#define	_rd_reset32		_rd_reset64
#define	find_dynamic_ent32	find_dynamic_ent64
#define	validate_rdebug32	validate_rdebug64
#define	TAPlist			APlist
#define	TLm_list		Lm_list
#define	TList			List
#define	TListnode		Listnode
#define	MSG_SYM_BRANDOPS	MSG_SYM_BRANDOPS_64
#else	/* ELF32 */
#define	Rt_map			Rt_map32
#define	Rtld_db_priv		Rtld_db_priv32
#define	TAPlist			APlist32
#define	TLm_list		Lm_list32
#define	TList			List32
#define	TListnode		Listnode32
#define	Lm_list			Lm_list32
#define	MSG_SYM_BRANDOPS	MSG_SYM_BRANDOPS_32
#endif	/* _ELF64 */
#else	/* _LP64 */
#define	TAPlist			APlist
#define	TLm_list		Lm_list
#define	TList			List
#define	TListnode		Listnode
#define	MSG_SYM_BRANDOPS	MSG_SYM_BRANDOPS_32
#endif	/* _LP64 */

/*
 * BrandZ added ps_pbrandname().  Many debuggers that link directly
 * against librtld_db.so may not implement this interface.  Hence
 * we won't call the function directly, instead we'll try to look it
 * up using the linker first and only invoke it if we find it.
 */
typedef ps_err_e (*ps_pbrandname_fp_t)(struct ps_prochandle *,
    char *, size_t);

rd_err_e
validate_rdebug32(struct rd_agent *rap)
{
	struct ps_prochandle	*php = rap->rd_psp;
	psaddr_t		db_privp;
	Rtld_db_priv		db_priv;

	if (rap->rd_rdebug == 0)
		return (RD_ERR);

	/*
	 * The rtld_db_priv structure contains both the traditional (exposed)
	 * r_debug structure as well as private data only available to
	 * this library.
	 */
	db_privp = rap->rd_rdebug;

	/*
	 * Verify that librtld_db & rtld are at the proper revision
	 * levels.
	 */
	if (ps_pread(php, db_privp, (char *)&db_priv,
	    sizeof (Rtld_db_priv)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_READPRIVFAIL_1),
		    EC_ADDR(db_privp)));
		return (RD_DBERR);
	}

	if ((db_priv.rtd_version < R_RTLDDB_VERSION1) ||
	    (db_priv.rtd_version > R_RTLDDB_VERSION)) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_BADPVERS),
		    db_priv.rtd_version, R_RTLDDB_VERSION));
		return (RD_NOCAPAB);
	}

	/*
	 * Is the image being examined from a core file or not.
	 * If it is a core file then the following write will fail.
	 */
	if (ps_pwrite(php, db_privp, (char *)&db_priv,
	    sizeof (Rtld_db_priv)) != PS_OK)
		rap->rd_flags |= RDF_FL_COREFILE;

	rap->rd_rdebugvers = db_priv.rtd_version;
	rap->rd_rtlddbpriv = db_privp;

	LOG(ps_plog(MSG_ORIG(MSG_DB_VALIDRDEBUG), EC_ADDR(rap->rd_rdebug),
	    R_RTLDDB_VERSION, rap->rd_rdebugvers,
	    rap->rd_flags & RDF_FL_COREFILE));
	return (RD_OK);
}


rd_err_e
find_dynamic_ent32(struct rd_agent *rap, psaddr_t dynaddr,
	Xword dyntag, Dyn *dyn)
{
	struct ps_prochandle	*php = rap->rd_psp;
	Dyn			d;

	d.d_tag = DT_NULL;
	do {
		if (ps_pread(php, dynaddr, (void *)(&d), sizeof (d)) !=
		    PS_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_READFAIL_4),
			    EC_ADDR(dynaddr)));
			return (RD_DBERR);
		}
		dynaddr += sizeof (d);
		if (d.d_tag == dyntag)
			break;
	} while (d.d_tag != DT_NULL);
	if (d.d_tag == dyntag) {
		*dyn = d;
		LOG(ps_plog(MSG_ORIG(MSG_DB_FINDDYNAMIC), EC_ADDR(dyntag),
		    EC_ADDR(d.d_un.d_val)));
		return (RD_OK);
	}
	LOG(ps_plog(MSG_ORIG(MSG_DB_NODYNDEBUG), EC_ADDR(dyntag)));
	return (RD_DBERR);
}

extern char rtld_db_helper_path[MAXPATHLEN];

rd_err_e
_rd_reset32(struct rd_agent *rap)
{
	psaddr_t		symaddr;
	struct ps_prochandle	*php = rap->rd_psp;
	const auxv_t		*auxvp = NULL;
	rd_err_e		rc = RD_OK;
	char			brandname[MAXPATHLEN];
	char			brandlib[MAXPATHLEN];
	ps_pbrandname_fp_t	ps_pbrandname;

	/*
	 * librtld_db attempts three different methods to find
	 * the r_debug structure which is required to
	 * initialize itself.  The methods are:
	 *	method1:
	 *		entirely independent of any text segment
	 *		and relies on the AT_SUN_LDDATA auxvector
	 *		to find the ld.so.1::rdebug structure.
	 *	method2:
	 *		lookup symbols in ld.so.1's symbol table
	 *		to find the r_debug symbol.
	 *	method3:
	 *		(old dbx method) dependent upon the
	 *		text segment/symbol table of the
	 *		executable and not ld.so.1.  We lookup the
	 *		_DYNAMIC symbol in the executable and look for
	 *		the DT_DEBUG entry in the .dynamic table.  This
	 *		points to rdebug.
	 *
	 * If none of that works - we fail.
	 */
	LOG(ps_plog(MSG_ORIG(MSG_DB_RDRESET), rap->rd_dmodel));
	/*
	 * Method1
	 *
	 * Scan the aux vector looking for AT_BASE & AT_SUN_LDDATA
	 */

	if (ps_pauxv(php, &auxvp) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_NOAUXV)));
		rc = RD_ERR;
	}

	rap->rd_rdebug = 0;

	if (auxvp != NULL) {
		rc = RD_ERR;
		while (auxvp->a_type != AT_NULL) {
			if (auxvp->a_type == AT_SUN_LDDATA) {
				/* LINTED */
				rap->rd_rdebug = (uintptr_t)auxvp->a_un.a_ptr;
				LOG(ps_plog(MSG_ORIG(MSG_DB_FLDDATA),
				    rap->rd_rdebug));
				rc = validate_rdebug32(rap);
				break;
			}
			auxvp++;
		}
	}

	/*
	 * method2 - look for r_rdebug symbol in ld.so.1
	 */
	if (rc != RD_OK) {
		/*
		 * If the AT_SUN_LDDATA auxv vector is not present
		 * fall back on doing a symlookup of
		 * the r_debug symbol.  This is for backward
		 * compatiblity with older OS's
		 */
		LOG(ps_plog(MSG_ORIG(MSG_DB_NOLDDATA)));
		if (ps_pglobal_lookup(php, PS_OBJ_LDSO, MSG_ORIG(MSG_SYM_DEBUG),
		    &symaddr) != PS_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_LOOKFAIL),
			    MSG_ORIG(MSG_SYM_DEBUG)));
			rc = RD_DBERR;
		} else {
			rap->rd_rdebug = symaddr;
			LOG(ps_plog(MSG_ORIG(MSG_DB_SYMRDEBUG),
			    EC_ADDR(symaddr)));
			rc = validate_rdebug32(rap);
		}
	}


	/*
	 * method3 - find DT_DEBUG in the executables .dynamic section.
	 */
	if (rc != RD_OK) {
		Dyn	dyn;
		if (ps_pglobal_lookup(php, PS_OBJ_EXEC,
		    MSG_ORIG(MSG_SYM_DYNAMIC), &symaddr) != PS_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_NODYNAMIC)));
			LOG(ps_plog(MSG_ORIG(MSG_DB_INITFAILED)));
			return (rc);
		}
		rc = find_dynamic_ent32(rap, symaddr, DT_DEBUG, &dyn);
		if (rc != RD_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_INITFAILED)));
			return (rc);
		}
		rap->rd_rdebug = dyn.d_un.d_ptr;
		rc = validate_rdebug32(rap);
		if (rc != RD_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_INITFAILED)));
			return (rc);
		}
	}

	/*
	 * If we are debugging a branded executable, load the appropriate
	 * helper library, and call its initialization routine.  Being unable
	 * to load the helper library is not a critical error.  (Hopefully
	 * we'll still be able to access some objects in the target.)
	 */
	ps_pbrandname = (ps_pbrandname_fp_t)dlsym(RTLD_PROBE, "ps_pbrandname");
	while ((ps_pbrandname != NULL) &&
	    (ps_pbrandname(php, brandname, MAXPATHLEN) == PS_OK)) {
		const char *isa = "";

#ifdef _LP64
		isa = MSG_ORIG(MSG_DB_64BIT_PREFIX);
#endif /* _LP64 */

		if (rtld_db_helper_path[0] != '\0')
			(void) snprintf(brandlib, MAXPATHLEN,
			    MSG_ORIG(MSG_DB_BRAND_HELPERPATH_PREFIX),
			    rtld_db_helper_path,
			    MSG_ORIG(MSG_DB_HELPER_PREFIX), brandname, isa,
			    brandname);
		else
			(void) snprintf(brandlib, MAXPATHLEN,
			    MSG_ORIG(MSG_DB_BRAND_HELPERPATH),
			    MSG_ORIG(MSG_DB_HELPER_PREFIX), brandname, isa,
			    brandname);

		rap->rd_helper.rh_dlhandle = dlopen(brandlib,
		    RTLD_LAZY | RTLD_LOCAL);
		if (rap->rd_helper.rh_dlhandle == NULL) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_HELPERLOADFAILED),
			    brandlib));
			break;
		}

		rap->rd_helper.rh_ops = dlsym(rap->rd_helper.rh_dlhandle,
		    MSG_ORIG(MSG_SYM_BRANDOPS));
		if (rap->rd_helper.rh_ops == NULL) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_HELPERNOOPS),
			    brandlib));
			(void) dlclose(rap->rd_helper.rh_dlhandle);
			rap->rd_helper.rh_dlhandle = NULL;
			break;
		}

		rap->rd_helper.rh_data = rap->rd_helper.rh_ops->rho_init(rap,
		    php);
		if (rap->rd_helper.rh_data == NULL) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_HELPERINITFAILED)));
			(void) dlclose(rap->rd_helper.rh_dlhandle);
			rap->rd_helper.rh_dlhandle = NULL;
			rap->rd_helper.rh_ops = NULL;
			break;
		}

		LOG(ps_plog(MSG_ORIG(MSG_DB_HELPERLOADED), brandname));
		break;

		/* NOTREACHED */
	}

	if ((rap->rd_flags & RDF_FL_COREFILE) == 0) {
		if (ps_pglobal_lookup(php, PS_OBJ_LDSO,
		    MSG_ORIG(MSG_SYM_PREINIT), &symaddr) != PS_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_LOOKFAIL),
			    MSG_ORIG(MSG_SYM_PREINIT)));
			return (RD_DBERR);
		}
		rap->rd_preinit = symaddr;

		if (ps_pglobal_lookup(php, PS_OBJ_LDSO,
		    MSG_ORIG(MSG_SYM_POSTINIT), &symaddr) != PS_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_LOOKFAIL),
			    MSG_ORIG(MSG_SYM_POSTINIT)));
			return (RD_DBERR);
		}
		rap->rd_postinit = symaddr;

		if (ps_pglobal_lookup(php, PS_OBJ_LDSO,
		    MSG_ORIG(MSG_SYM_DLACT), &symaddr) != PS_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_LOOKFAIL),
			    MSG_ORIG(MSG_SYM_DLACT)));
			return (RD_DBERR);
		}
		rap->rd_dlact = symaddr;
		rap->rd_tbinder = 0;
	}

	return (RD_OK);
}

rd_err_e
_rd_get_ehdr32(struct rd_agent *rap,
    psaddr_t addr, Ehdr *ehdr, uint_t *phnum)
{
	struct ps_prochandle	*php = rap->rd_psp;
	Shdr			shdr;

	if (ps_pread(php, addr, ehdr, sizeof (*ehdr)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_READFAIL_5), EC_ADDR(addr)));
		return (RD_ERR);
	}
	if (phnum == NULL)
		return (RD_OK);

	if (ehdr->e_phnum != PN_XNUM) {
		*phnum = ehdr->e_phnum;
		return (RD_OK);
	}

	/* deal with elf extended program headers */
	if ((ehdr->e_shoff == 0) || (ehdr->e_shentsize < sizeof (shdr)))
		return (RD_ERR);

	addr += ehdr->e_shoff;
	if (ps_pread(php, addr, &shdr, sizeof (shdr)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_READFAIL_5), EC_ADDR(addr)));
		return (RD_ERR);
	}

	if (shdr.sh_info == 0)
		return (RD_ERR);

	*phnum = shdr.sh_info;
	return (RD_OK);
}

rd_err_e
_rd_get_dyns32(rd_agent_t *rap, psaddr_t addr, Dyn **dynpp, size_t *dynpp_sz)
{
	struct ps_prochandle	*php = rap->rd_psp;
	rd_err_e		err;
	uint_t			phnum;
	Ehdr			ehdr;
	Phdr			phdr;
	Dyn			*dynp;
	int			i;

	/* We only need to muck with dyn elements for ET_DYN objects */
	if ((err = _rd_get_ehdr32(rap, addr, &ehdr, &phnum)) != RD_OK)
		return (err);

	for (i = 0; i < phnum; i++) {
		psaddr_t a = addr + ehdr.e_phoff + (i * ehdr.e_phentsize);
		if (ps_pread(php, a, &phdr, sizeof (phdr)) != PS_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_READFAIL_6), EC_ADDR(a)));
			return (RD_ERR);
		}
		if (phdr.p_type == PT_DYNAMIC)
			break;
	}
	if (i == phnum)
		return (RD_ERR);

	if ((dynp = malloc(phdr.p_filesz)) == NULL)
		return (RD_ERR);
	if (ehdr.e_type == ET_DYN)
		phdr.p_vaddr += addr;
	if (ps_pread(php, phdr.p_vaddr, dynp, phdr.p_filesz) != PS_OK) {
		free(dynp);
		LOG(ps_plog(MSG_ORIG(MSG_DB_READFAIL_6),
		    EC_ADDR(phdr.p_vaddr)));
		return (RD_ERR);
	}

	*dynpp = dynp;
	if (dynpp_sz != NULL)
		*dynpp_sz = phdr.p_filesz;
	return (RD_OK);
}

rd_err_e
_rd_event_enable32(rd_agent_t *rap, int onoff)
{
	struct ps_prochandle	*php = rap->rd_psp;
	Rtld_db_priv		rdb;

	LOG(ps_plog(MSG_ORIG(MSG_DB_RDEVENTENABLE), rap->rd_dmodel, onoff));
	/*
	 * Tell the debugged process that debugging is occuring
	 * This will enable the storing of event messages so that
	 * the can be gathered by the debugger.
	 */
	if (ps_pread(php, rap->rd_rdebug, (char *)&rdb,
	    sizeof (Rtld_db_priv)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_READFAIL_1),
		    EC_ADDR((uintptr_t)&rdb)));
		return (RD_DBERR);
	}

	if (onoff)
		rdb.rtd_rdebug.r_flags |= RD_FL_DBG;
	else
		rdb.rtd_rdebug.r_flags &= ~RD_FL_DBG;

	if (ps_pwrite(php, rap->rd_rdebug, (char *)&rdb,
	    sizeof (Rtld_db_priv)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_WRITEFAIL_1),
		    EC_ADDR((uintptr_t)&rdb)));
		return (RD_DBERR);
	}

	return (RD_OK);
}


rd_err_e
_rd_event_getmsg32(rd_agent_t *rap, rd_event_msg_t *emsg)
{
	Rtld_db_priv	rdb;

	if (ps_pread(rap->rd_psp, rap->rd_rdebug, (char *)&rdb,
	    sizeof (Rtld_db_priv)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_READDBGFAIL_2),
		    EC_ADDR(rap->rd_rdebug)));
		return (RD_DBERR);
	}
	emsg->type = rdb.rtd_rdebug.r_rdevent;
	if (emsg->type == RD_DLACTIVITY) {
		switch (rdb.rtd_rdebug.r_state) {
			case RT_CONSISTENT:
				emsg->u.state = RD_CONSISTENT;
				break;
			case RT_ADD:
				emsg->u.state = RD_ADD;
				break;
			case RT_DELETE:
				emsg->u.state = RD_DELETE;
				break;
		}
	} else
		emsg->u.state = RD_NOSTATE;

	LOG(ps_plog(MSG_ORIG(MSG_DB_RDEVENTGETMSG), rap->rd_dmodel,
	    emsg->type, emsg->u.state));

	return (RD_OK);
}


rd_err_e
_rd_objpad_enable32(struct rd_agent *rap, size_t padsize)
{
	Rtld_db_priv		db_priv;
	struct ps_prochandle	*php = rap->rd_psp;

	LOG(ps_plog(MSG_ORIG(MSG_DB_RDOBJPADE), EC_ADDR(padsize)));

	if (ps_pread(php, rap->rd_rtlddbpriv, (char *)&db_priv,
	    sizeof (Rtld_db_priv)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_READFAIL_3),
		    EC_ADDR(rap->rd_rtlddbpriv)));
		return (RD_DBERR);
	}
#if	defined(_LP64) && !defined(_ELF64)
	/*LINTED*/
	db_priv.rtd_objpad = (uint32_t)padsize;
#else
	db_priv.rtd_objpad = padsize;
#endif
	if (ps_pwrite(php, rap->rd_rtlddbpriv, (char *)&db_priv,
	    sizeof (Rtld_db_priv)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_WRITEFAIL_2),
		    EC_ADDR(rap->rd_rtlddbpriv)));
		return (RD_DBERR);
	}
	return (RD_OK);
}

static rd_err_e
iter_map(rd_agent_t *rap, unsigned long ident, psaddr_t lmaddr,
	rl_iter_f *cb, void *client_data, uint_t *abort_iterp)
{
	while (lmaddr) {
		Rt_map		rmap;
		rd_loadobj_t	lobj;
		int		i;
		ulong_t		off;
		Ehdr		ehdr;
		Phdr		phdr;

		if (ps_pread(rap->rd_psp, lmaddr, (char *)&rmap,
		    sizeof (Rt_map)) != PS_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_LKMAPFAIL)));
			return (RD_DBERR);
		}

		/*
		 * As of 'VERSION5' we only report objects
		 * which have been fully relocated.  While the maps
		 * might be in a consistent state - if a object hasn't
		 * been relocated - it's not really ready for the debuggers
		 * to examine.  This is mostly due to the fact that we
		 * might still be mucking with the text-segment, if
		 * we are - we could conflict with any break-points
		 * the debuggers might have set.
		 */
		if (rap->rd_rdebugvers >= R_RTLDDB_VERSION5) {
			if ((FLAGS(&rmap) & FLG_RT_RELOCED) == 0) {
				lmaddr = (psaddr_t)NEXT(&rmap);
				continue;
			}
		}

		lobj.rl_base = (psaddr_t)ADDR(&rmap);
		lobj.rl_flags = 0;
		lobj.rl_refnameaddr = (psaddr_t)REFNAME(&rmap);
		if ((rap->rd_helper.rh_ops != NULL) &&
		    (rap->rd_helper.rh_ops->rho_lmid != LM_ID_NONE))
			lobj.rl_lmident =
			    rap->rd_helper.rh_ops->rho_lmid;
		else
			lobj.rl_lmident = ident;

		/*
		 * refnameaddr is only valid from a core file
		 * which is VERSION3 or greater.
		 */
		if (rap->rd_rdebugvers < R_RTLDDB_VERSION3) {
			lobj.rl_nameaddr = (psaddr_t)NAME(&rmap);
			lobj.rl_bend = 0;
			lobj.rl_padstart = 0;
			lobj.rl_padend = 0;
		} else {
			lobj.rl_nameaddr = (psaddr_t)PATHNAME(&rmap);
			lobj.rl_bend = ADDR(&rmap) + MSIZE(&rmap);
			lobj.rl_padstart = PADSTART(&rmap);
			lobj.rl_padend = PADSTART(&rmap) + PADIMLEN(&rmap);

		}

		if (rtld_db_version >= RD_VERSION2)
			if (FLAGS(&rmap) & FLG_RT_IMGALLOC)
				lobj.rl_flags |= RD_FLG_MEM_OBJECT;
		if (rtld_db_version >= RD_VERSION2) {
			lobj.rl_dynamic = (psaddr_t)DYN(&rmap);
		}

		if (rtld_db_version >= RD_VERSION4)
			lobj.rl_tlsmodid = TLSMODID(&rmap);

		/*
		 * Look for beginning of data segment.
		 *
		 * NOTE: the data segment can only be found for full
		 *	processes and not from core images.
		 */
		lobj.rl_data_base = 0;
		if (rap->rd_flags & RDF_FL_COREFILE)
			lobj.rl_data_base = 0;
		else {
			off = ADDR(&rmap);
			if (ps_pread(rap->rd_psp, off, (char *)&ehdr,
			    sizeof (Ehdr)) != PS_OK) {
				LOG(ps_plog(MSG_ORIG(MSG_DB_LKMAPFAIL)));
				return (RD_DBERR);
			}
			off += sizeof (Ehdr);
			for (i = 0; i < ehdr.e_phnum; i++) {
				if (ps_pread(rap->rd_psp, off, (char *)&phdr,
				    sizeof (Phdr)) != PS_OK) {
					LOG(ps_plog(MSG_ORIG(
					    MSG_DB_LKMAPFAIL)));
					return (RD_DBERR);
				}
				if ((phdr.p_type == PT_LOAD) &&
				    (phdr.p_flags & PF_W)) {
					lobj.rl_data_base = phdr.p_vaddr;
					if (ehdr.e_type == ET_DYN)
						lobj.rl_data_base +=
						    ADDR(&rmap);
					break;
				}
				off += ehdr.e_phentsize;
			}
		}

		/*
		 * When we transfer control to the client we free the
		 * lock and re-atain it after we've returned from the
		 * client.  This is to avoid any deadlock situations.
		 */
		LOG(ps_plog(MSG_ORIG(MSG_DB_ITERMAP), cb, client_data,
		    EC_ADDR(lobj.rl_base), EC_ADDR(lobj.rl_lmident)));
		RDAGUNLOCK(rap);
		if ((*cb)(&lobj, client_data) == 0) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_CALLBACKR0)));
			RDAGLOCK(rap);
			*abort_iterp = 1;
			break;
		}
		RDAGLOCK(rap);
		lmaddr = (psaddr_t)NEXT(&rmap);
	}
	return (RD_OK);
}


static rd_err_e
_rd_loadobj_iter32_native(rd_agent_t *rap, rl_iter_f *cb, void *client_data,
    uint_t *abort_iterp)
{
	Rtld_db_priv	db_priv;
	TAPlist		apl;
	uintptr_t	datap, nitems;
	Addr		addr;
	rd_err_e	rc;

	LOG(ps_plog(MSG_ORIG(MSG_DB_LOADOBJITER), rap->rd_dmodel, cb,
	    client_data));

	/*
	 * First, determine whether the link-map information has been
	 * established.  Some debuggers have made an initial call to this
	 * function with a null call back function (cb), but expect a
	 * RD_NOMAPS error return rather than a RD_ERR return when the
	 * link-maps aren't available.
	 */
	if (ps_pread(rap->rd_psp, rap->rd_rtlddbpriv, (char *)&db_priv,
	    sizeof (Rtld_db_priv)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_READDBGFAIL_1),
		    EC_ADDR(rap->rd_rtlddbpriv)));
		return (RD_DBERR);
	}

	if (db_priv.rtd_dynlmlst == NULL) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_LKMAPNOINIT),
		    EC_ADDR((uintptr_t)db_priv.rtd_dynlmlst)));
		return (RD_NOMAPS);
	}

	if (ps_pread(rap->rd_psp, (psaddr_t)db_priv.rtd_dynlmlst, (char *)&addr,
	    sizeof (Addr)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_READDBGFAIL_3),
		    EC_ADDR((uintptr_t)db_priv.rtd_dynlmlst)));
		return (RD_DBERR);
	}

	if (addr == NULL) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_LKMAPNOINIT_1),
		    EC_ADDR((uintptr_t)db_priv.rtd_dynlmlst)));
		return (RD_NOMAPS);
	}

	/*
	 * Having determined we have link-maps, ensure we have an iterator
	 * call back function.
	 */
	if (cb == NULL) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_NULLITER)));
		return (RD_ERR);
	}

	/*
	 * As of VERSION6, rtd_dynlmlst points to an APlist.  Prior to VERSION6
	 * rtd_dynlmlst pointed to a List.  But, there was a window where the
	 * version was not incremented, and this must be worked around by
	 * interpreting the APlist data.  Read the initial APlist information.
	 */
	if (ps_pread(rap->rd_psp, (psaddr_t)addr, (char *)&apl,
	    sizeof (TAPlist)) != PS_OK) {
		LOG(ps_plog(MSG_ORIG(MSG_DB_READDBGFAIL_4),
		    EC_ADDR((uintptr_t)addr)));
		return (RD_DBERR);
	}

	/*
	 * The rtd_dynlmlst change from a List to an APlist occurred under
	 * 6801536 in snv_112.  However, this change neglected to preserve
	 * backward compatibility by maintaining List processing and using a
	 * version increment to detect the change.  6862967, intergrated in
	 * snv_121 corrects the version detection.  However, to catch objects
	 * built between these releases, we look at the first element of the
	 * APlist.  apl_arritems indicates the number of APlist items that are
	 * available.  This was originally initialized with a AL_CNT_DYNLIST
	 * value of 2 (one entry for LM_ID_BASE and one entry for LM_ID_LDSO).
	 * It is possible that the use of an auditor results in an additional
	 * link-map list, in which case the original apl_arritems would have
	 * been doubled.
	 *
	 * Therefore, if the debugging verion is VERSION6, or the apl_arritems
	 * entry has a value less than or equal to 4 and the debugging version
	 * is VERSION5, then we process APlists.  Otherwise, fall back to List
	 * processing.
	 */
	if ((rap->rd_rdebugvers >= R_RTLDDB_VERSION6) ||
	    ((rap->rd_rdebugvers == R_RTLDDB_VERSION5) &&
	    (apl.apl_arritems <= 4))) {
		/*
		 * Iterate through each apl.ap_data[] entry.
		 */
		for (datap = (uintptr_t)((char *)(uintptr_t)addr +
		    ((size_t)(((TAPlist *)0)->apl_data))), nitems = 0;
		    nitems < apl.apl_nitems; nitems++, datap += sizeof (Addr)) {
			TLm_list	lm;
			ulong_t		ident;

			/*
			 * Obtain the Lm_list address for this apl.ap_data[]
			 * entry.
			 */
			if (ps_pread(rap->rd_psp, (psaddr_t)datap,
			    (char *)&addr, sizeof (Addr)) != PS_OK) {
				LOG(ps_plog(MSG_ORIG(MSG_DB_READDBGFAIL_5),
				    EC_ADDR(datap)));
				return (RD_DBERR);
			}

			/*
			 * Obtain the Lm_list data for this Lm_list address.
			 */
			if (ps_pread(rap->rd_psp, (psaddr_t)addr, (char *)&lm,
			    sizeof (TLm_list)) != PS_OK) {
				LOG(ps_plog(MSG_ORIG(MSG_DB_READDBGFAIL_6),
				    EC_ADDR((uintptr_t)addr)));
				return (RD_DBERR);
			}

			/*
			 * Determine IDENT of current LM_LIST
			 */
			if (lm.lm_flags & LML_FLG_BASELM)
				ident = LM_ID_BASE;
			else if (lm.lm_flags & LML_FLG_RTLDLM)
				ident = LM_ID_LDSO;
			else
				ident = (ulong_t)addr;

			if ((rc = iter_map(rap, ident, (psaddr_t)lm.lm_head,
			    cb, client_data, abort_iterp)) != RD_OK)
				return (rc);

			if (*abort_iterp != 0)
				break;
		}
	} else {
		TList		list;
		TListnode	lnode;
		Addr		lnp;

		/*
		 * Re-read the dynlmlst address to obtain a List structure.
		 */
		if (ps_pread(rap->rd_psp, (psaddr_t)db_priv.rtd_dynlmlst,
		    (char *)&list, sizeof (TList)) != PS_OK) {
			LOG(ps_plog(MSG_ORIG(MSG_DB_READDBGFAIL_3),
			    EC_ADDR((uintptr_t)db_priv.rtd_dynlmlst)));
			return (RD_DBERR);
		}

		/*
		 * Iterate through the link-map list.
		 */
		for (lnp = (Addr)list.head; lnp; lnp = (Addr)lnode.next) {
			Lm_list	lml;
			ulong_t	ident;

			/*
			 * Iterate through the List of Lm_list's.
			 */
			if (ps_pread(rap->rd_psp, (psaddr_t)lnp, (char *)&lnode,
			    sizeof (TListnode)) != PS_OK) {
				LOG(ps_plog(MSG_ORIG(MSG_DB_READDBGFAIL_4),
				    EC_ADDR(lnp)));
					return (RD_DBERR);
			}

			if (ps_pread(rap->rd_psp, (psaddr_t)lnode.data,
			    (char *)&lml, sizeof (Lm_list)) != PS_OK) {
				LOG(ps_plog(MSG_ORIG(MSG_DB_READDBGFAIL_5),
				    EC_ADDR((uintptr_t)lnode.data)));
					return (RD_DBERR);
			}

			/*
			 * Determine IDENT of current LM_LIST
			 */
			if (lml.lm_flags & LML_FLG_BASELM)
				ident = LM_ID_BASE;
			else if (lml.lm_flags & LML_FLG_RTLDLM)
				ident = LM_ID_LDSO;
			else
				ident = (unsigned long)lnode.data;

			if ((rc = iter_map(rap, ident, (psaddr_t)lml.lm_head,
			    cb, client_data, abort_iterp)) != RD_OK)
				return (rc);

			if (*abort_iterp != 0)
				break;
		}
	}

	return (rc);
}

rd_err_e
_rd_loadobj_iter32(rd_agent_t *rap, rl_iter_f *cb, void *client_data)
{
	rd_err_e	rc, rc_brand = RD_OK;
	uint_t		abort_iter = 0;

	/* First iterate over the native target objects */
	rc = _rd_loadobj_iter32_native(rap, cb, client_data, &abort_iter);
	if (abort_iter != 0)
		return (rc);

	/* Then iterate over any branded objects. */
	if ((rap->rd_helper.rh_ops != NULL) &&
	    (rap->rd_helper.rh_ops->rho_loadobj_iter != NULL))
		rc_brand = rap->rd_helper.rh_ops->rho_loadobj_iter(
		    rap->rd_helper.rh_data, cb, client_data);

	rc = (rc != RD_OK) ? rc : rc_brand;
	return (rc);
}