view usr/src/cmd/mdb/common/mdb/mdb_target.c @ 14167:7ac6fb740bcf

3946 ::gcore (fix sparc build)
author Christopher Siden <chris.siden@delphix.com>
date Tue, 27 Aug 2013 10:51:34 -0800
parents 1b86d65a4f9e
children
line wrap: on
line source

/*
 * 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.
 */

/*
 * MDB Target Layer
 *
 * The *target* is the program being inspected by the debugger.  The MDB target
 * layer provides a set of functions that insulate common debugger code,
 * including the MDB Module API, from the implementation details of how the
 * debugger accesses information from a given target.  Each target exports a
 * standard set of properties, including one or more address  spaces, one or
 * more symbol tables, a set of load objects, and a set of threads that can be
 * examined using the interfaces in <mdb/mdb_target.h>.  This technique has
 * been employed successfully in other debuggers, including [1], primarily
 * to improve portability, although the term "target" often refers to the
 * encapsulation of architectural or operating system-specific details.  The
 * target abstraction is useful for MDB because it allows us to easily extend
 * the debugger to examine a variety of different program forms.  Primarily,
 * the target functions validate input arguments and then call an appropriate
 * function in the target ops vector, defined in <mdb/mdb_target_impl.h>.
 * However, this interface layer provides a very high level of flexibility for
 * separating the debugger interface from instrumentation details.  Experience
 * has shown this kind of design can facilitate separating out debugger
 * instrumentation into an external agent [2] and enable the development of
 * advanced instrumentation frameworks [3].  We want MDB to be an ideal
 * extensible framework for the development of such applications.
 *
 * Aside from a set of wrapper functions, the target layer also provides event
 * management for targets that represent live executing programs.  Our model of
 * events is also extensible, and is based upon work in [3] and [4].  We define
 * a *software event* as a state transition in the target program (for example,
 * the transition of the program counter to a location of interest) that is
 * observed by the debugger or its agent.  A *software event specifier* is a
 * description of a class of software events that is used by the debugger to
 * instrument the target so that the corresponding software events can be
 * observed.  In MDB, software event specifiers are represented by the
 * mdb_sespec_t structure, defined in <mdb/mdb_target_impl.h>.  As the user,
 * the internal debugger code, and MDB modules may all wish to observe software
 * events and receive appropriate notification and callbacks, we do not expose
 * software event specifiers directly as part of the user interface.  Instead,
 * clients of the target layer request that events be observed by creating
 * new *virtual event specifiers*.  Each virtual specifier is named by a unique
 * non-zero integer (the VID), and is represented by a mdb_vespec_t structure.
 * One or more virtual specifiers are then associated with each underlying
 * software event specifier.  This design enforces the constraint that the
 * target must only insert one set of instrumentation, regardless of how many
 * times the target layer was asked to trace a given event.  For example, if
 * multiple clients request a breakpoint at a particular address, the virtual
 * specifiers will map to the same sespec, ensuring that only one breakpoint
 * trap instruction is actually planted at the given target address.  When no
 * virtual specifiers refer to an sespec, it is no longer needed and can be
 * removed, along with the corresponding instrumentation.
 *
 * The following state transition diagram illustrates the life cycle of a
 * software event specifier and example transitions:
 *
 *                                         cont/
 *     +--------+   delete   +--------+    stop    +-------+
 *    (|( DEAD )|) <------- (  ACTIVE  ) <------> (  ARMED  )
 *     +--------+            +--------+            +-------+
 *          ^   load/unload  ^        ^   failure/     |
 *   delete |        object /          \  reset        | failure
 *          |              v            v              |
 *          |      +--------+          +-------+       |
 *          +---- (   IDLE   )        (   ERR   ) <----+
 *          |      +--------+          +-------+
 *          |                              |
 *          +------------------------------+
 *
 * The MDB execution control model is based upon the synchronous debugging
 * model exported by Solaris proc(4).  A target program is set running or the
 * debugger is attached to a running target.  On ISTOP (stop on event of
 * interest), one target thread is selected as the representative.  The
 * algorithm for selecting the representative is target-specific, but we assume
 * that if an observed software event has occurred, the target will select the
 * thread that triggered the state transition of interest.  The other threads
 * are stopped in sympathy with the representative as soon as possible.  Prior
 * to continuing the target, we plant our instrumentation, transitioning event
 * specifiers from the ACTIVE to the ARMED state, and then back again when the
 * target stops.  We then query each active event specifier to learn which ones
 * are matched, and then invoke the callbacks associated with their vespecs.
 * If an OS error occurs while attempting to arm or disarm a specifier, the
 * specifier is transitioned to the ERROR state; we will attempt to arm it
 * again at the next continue.  If no target process is under our control or
 * if an event is not currently applicable (e.g. a deferred breakpoint on an
 * object that is not yet loaded), it remains in the IDLE state.  The target
 * implementation should intercept object load events and then transition the
 * specifier to the ACTIVE state when the corresponding object is loaded.
 *
 * To simplify the debugger implementation and allow targets to easily provide
 * new types of observable events, most of the event specifier management is
 * done by the target layer.  Each software event specifier provides an ops
 * vector of subroutines that the target layer can call to perform the
 * various state transitions described above.  The target maintains two lists
 * of mdb_sespec_t's: the t_idle list (IDLE state) and the t_active list
 * (ACTIVE, ARMED, and ERROR states).  Each mdb_sespec_t maintains a list of
 * associated mdb_vespec_t's.  If an sespec is IDLE or ERROR, its se_errno
 * field will have an errno value specifying the reason for its inactivity.
 * The vespec stores the client's callback function and private data, and the
 * arguments used to construct the sespec.  All objects are reference counted
 * so we can destroy an object when it is no longer needed.  The mdb_sespec_t
 * invariants for the respective states are as follows:
 *
 *   IDLE: on t_idle list, se_data == NULL, se_errno != 0, se_ctor not called
 * ACTIVE: on t_active list, se_data valid, se_errno == 0, se_ctor called
 *  ARMED: on t_active list, se_data valid, se_errno == 0, se_ctor called
 *  ERROR: on t_active list, se_data valid, se_errno != 0, se_ctor called
 *
 * Additional commentary on specific state transitions and issues involving
 * event management can be found below near the target layer functions.
 *
 * References
 *
 * [1] John Gilmore, "Working in GDB", Technical Report, Cygnus Support,
 *     1.84 edition, 1994.
 *
 * [2] David R. Hanson and Mukund Raghavachari, "A Machine-Independent
 *     Debugger", Software--Practice and Experience, 26(11), 1277-1299(1996).
 *
 * [3] Michael W. Shapiro, "RDB: A System for Incremental Replay Debugging",
 *     Technical Report CS-97-12, Department of Computer Science,
 *     Brown University.
 *
 * [4] Daniel B. Price, "New Techniques for Replay Debugging", Technical
 *     Report CS-98-05, Department of Computer Science, Brown University.
 */

#include <mdb/mdb_target_impl.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_callb.h>
#include <mdb/mdb_gelf.h>
#include <mdb/mdb_io_impl.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_signal.h>
#include <mdb/mdb_frame.h>
#include <mdb/mdb.h>

#include <sys/stat.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <strings.h>
#include <stdlib.h>
#include <errno.h>

/*
 * Define convenience macros for referencing the set of vespec flag bits that
 * are preserved by the target implementation, and the set of bits that
 * determine automatic ve_hits == ve_limit behavior.
 */
#define	T_IMPL_BITS	\
	(MDB_TGT_SPEC_INTERNAL | MDB_TGT_SPEC_SILENT | MDB_TGT_SPEC_MATCHED | \
	MDB_TGT_SPEC_DELETED)

#define	T_AUTO_BITS	\
	(MDB_TGT_SPEC_AUTOSTOP | MDB_TGT_SPEC_AUTODEL | MDB_TGT_SPEC_AUTODIS)

/*
 * Define convenience macro for referencing target flag pending continue bits.
 */
#define	T_CONT_BITS	\
	(MDB_TGT_F_STEP | MDB_TGT_F_STEP_OUT | MDB_TGT_F_STEP_BRANCH | \
	MDB_TGT_F_NEXT | MDB_TGT_F_CONT)

mdb_tgt_t *
mdb_tgt_create(mdb_tgt_ctor_f *ctor, int flags, int argc, const char *argv[])
{
	mdb_module_t *mp;
	mdb_tgt_t *t;

	if (flags & ~MDB_TGT_F_ALL) {
		(void) set_errno(EINVAL);
		return (NULL);
	}

	t = mdb_zalloc(sizeof (mdb_tgt_t), UM_SLEEP);
	mdb_list_append(&mdb.m_tgtlist, t);

	t->t_module = &mdb.m_rmod;
	t->t_matched = T_SE_END;
	t->t_flags = flags;
	t->t_vepos = 1;
	t->t_veneg = 1;

	for (mp = mdb.m_mhead; mp != NULL; mp = mp->mod_next) {
		if (ctor == mp->mod_tgt_ctor) {
			t->t_module = mp;
			break;
		}
	}

	if (ctor(t, argc, argv) != 0) {
		mdb_list_delete(&mdb.m_tgtlist, t);
		mdb_free(t, sizeof (mdb_tgt_t));
		return (NULL);
	}

	mdb_dprintf(MDB_DBG_TGT, "t_create %s (%p)\n",
	    t->t_module->mod_name, (void *)t);

	(void) t->t_ops->t_status(t, &t->t_status);
	return (t);
}

int
mdb_tgt_getflags(mdb_tgt_t *t)
{
	return (t->t_flags);
}

int
mdb_tgt_setflags(mdb_tgt_t *t, int flags)
{
	if (flags & ~MDB_TGT_F_ALL)
		return (set_errno(EINVAL));

	return (t->t_ops->t_setflags(t, flags));
}

int
mdb_tgt_setcontext(mdb_tgt_t *t, void *context)
{
	return (t->t_ops->t_setcontext(t, context));
}

/*ARGSUSED*/
static int
tgt_delete_vespec(mdb_tgt_t *t, void *private, int vid, void *data)
{
	(void) mdb_tgt_vespec_delete(t, vid);
	return (0);
}

void
mdb_tgt_destroy(mdb_tgt_t *t)
{
	mdb_xdata_t *xdp, *nxdp;

	if (mdb.m_target == t) {
		mdb_dprintf(MDB_DBG_TGT, "t_deactivate %s (%p)\n",
		    t->t_module->mod_name, (void *)t);
		t->t_ops->t_deactivate(t);
		mdb.m_target = NULL;
	}

	mdb_dprintf(MDB_DBG_TGT, "t_destroy %s (%p)\n",
	    t->t_module->mod_name, (void *)t);

	for (xdp = mdb_list_next(&t->t_xdlist); xdp != NULL; xdp = nxdp) {
		nxdp = mdb_list_next(xdp);
		mdb_list_delete(&t->t_xdlist, xdp);
		mdb_free(xdp, sizeof (mdb_xdata_t));
	}

	mdb_tgt_sespec_idle_all(t, EBUSY, TRUE);
	(void) mdb_tgt_vespec_iter(t, tgt_delete_vespec, NULL);
	t->t_ops->t_destroy(t);

	mdb_list_delete(&mdb.m_tgtlist, t);
	mdb_free(t, sizeof (mdb_tgt_t));

	if (mdb.m_target == NULL)
		mdb_tgt_activate(mdb_list_prev(&mdb.m_tgtlist));
}

void
mdb_tgt_activate(mdb_tgt_t *t)
{
	mdb_tgt_t *otgt = mdb.m_target;

	if (mdb.m_target != NULL) {
		mdb_dprintf(MDB_DBG_TGT, "t_deactivate %s (%p)\n",
		    mdb.m_target->t_module->mod_name, (void *)mdb.m_target);
		mdb.m_target->t_ops->t_deactivate(mdb.m_target);
	}

	if ((mdb.m_target = t) != NULL) {
		const char *v = strstr(mdb.m_root, "%V");

		mdb_dprintf(MDB_DBG_TGT, "t_activate %s (%p)\n",
		    t->t_module->mod_name, (void *)t);

		/*
		 * If the root was explicitly set with -R and contains %V,
		 * expand it like a path.  If the resulting directory is
		 * not present, then replace %V with "latest" and re-evaluate.
		 */
		if (v != NULL) {
			char old_root[MAXPATHLEN];
			const char **p;
#ifndef _KMDB
			struct stat s;
#endif
			size_t len;

			p = mdb_path_alloc(mdb.m_root, &len);
			(void) strcpy(old_root, mdb.m_root);
			(void) strncpy(mdb.m_root, p[0], MAXPATHLEN);
			mdb.m_root[MAXPATHLEN - 1] = '\0';
			mdb_path_free(p, len);

#ifndef _KMDB
			if (stat(mdb.m_root, &s) == -1 && errno == ENOENT) {
				mdb.m_flags |= MDB_FL_LATEST;
				p = mdb_path_alloc(old_root, &len);
				(void) strncpy(mdb.m_root, p[0], MAXPATHLEN);
				mdb.m_root[MAXPATHLEN - 1] = '\0';
				mdb_path_free(p, len);
			}
#endif
		}

		/*
		 * Re-evaluate the macro and dmod paths now that we have the
		 * new target set and m_root figured out.
		 */
		if (otgt == NULL) {
			mdb_set_ipath(mdb.m_ipathstr);
			mdb_set_lpath(mdb.m_lpathstr);
		}

		t->t_ops->t_activate(t);
	}
}

void
mdb_tgt_periodic(mdb_tgt_t *t)
{
	t->t_ops->t_periodic(t);
}

const char *
mdb_tgt_name(mdb_tgt_t *t)
{
	return (t->t_ops->t_name(t));
}

const char *
mdb_tgt_isa(mdb_tgt_t *t)
{
	return (t->t_ops->t_isa(t));
}

const char *
mdb_tgt_platform(mdb_tgt_t *t)
{
	return (t->t_ops->t_platform(t));
}

int
mdb_tgt_uname(mdb_tgt_t *t, struct utsname *utsp)
{
	return (t->t_ops->t_uname(t, utsp));
}

int
mdb_tgt_dmodel(mdb_tgt_t *t)
{
	return (t->t_ops->t_dmodel(t));
}

int
mdb_tgt_auxv(mdb_tgt_t *t, const auxv_t **auxvp)
{
	return (t->t_ops->t_auxv(t, auxvp));
}

ssize_t
mdb_tgt_aread(mdb_tgt_t *t, mdb_tgt_as_t as,
	void *buf, size_t n, mdb_tgt_addr_t addr)
{
	if (t->t_flags & MDB_TGT_F_ASIO)
		return (t->t_ops->t_aread(t, as, buf, n, addr));

	switch ((uintptr_t)as) {
	case (uintptr_t)MDB_TGT_AS_VIRT:
		return (t->t_ops->t_vread(t, buf, n, addr));
	case (uintptr_t)MDB_TGT_AS_PHYS:
		return (t->t_ops->t_pread(t, buf, n, addr));
	case (uintptr_t)MDB_TGT_AS_FILE:
		return (t->t_ops->t_fread(t, buf, n, addr));
	case (uintptr_t)MDB_TGT_AS_IO:
		return (t->t_ops->t_ioread(t, buf, n, addr));
	}
	return (t->t_ops->t_aread(t, as, buf, n, addr));
}

ssize_t
mdb_tgt_awrite(mdb_tgt_t *t, mdb_tgt_as_t as,
	const void *buf, size_t n, mdb_tgt_addr_t addr)
{
	if (!(t->t_flags & MDB_TGT_F_RDWR))
		return (set_errno(EMDB_TGTRDONLY));

	if (t->t_flags & MDB_TGT_F_ASIO)
		return (t->t_ops->t_awrite(t, as, buf, n, addr));

	switch ((uintptr_t)as) {
	case (uintptr_t)MDB_TGT_AS_VIRT:
		return (t->t_ops->t_vwrite(t, buf, n, addr));
	case (uintptr_t)MDB_TGT_AS_PHYS:
		return (t->t_ops->t_pwrite(t, buf, n, addr));
	case (uintptr_t)MDB_TGT_AS_FILE:
		return (t->t_ops->t_fwrite(t, buf, n, addr));
	case (uintptr_t)MDB_TGT_AS_IO:
		return (t->t_ops->t_iowrite(t, buf, n, addr));
	}
	return (t->t_ops->t_awrite(t, as, buf, n, addr));
}

ssize_t
mdb_tgt_vread(mdb_tgt_t *t, void *buf, size_t n, uintptr_t addr)
{
	return (t->t_ops->t_vread(t, buf, n, addr));
}

ssize_t
mdb_tgt_vwrite(mdb_tgt_t *t, const void *buf, size_t n, uintptr_t addr)
{
	if (t->t_flags & MDB_TGT_F_RDWR)
		return (t->t_ops->t_vwrite(t, buf, n, addr));

	return (set_errno(EMDB_TGTRDONLY));
}

ssize_t
mdb_tgt_pread(mdb_tgt_t *t, void *buf, size_t n, physaddr_t addr)
{
	return (t->t_ops->t_pread(t, buf, n, addr));
}

ssize_t
mdb_tgt_pwrite(mdb_tgt_t *t, const void *buf, size_t n, physaddr_t addr)
{
	if (t->t_flags & MDB_TGT_F_RDWR)
		return (t->t_ops->t_pwrite(t, buf, n, addr));

	return (set_errno(EMDB_TGTRDONLY));
}

ssize_t
mdb_tgt_fread(mdb_tgt_t *t, void *buf, size_t n, uintptr_t addr)
{
	return (t->t_ops->t_fread(t, buf, n, addr));
}

ssize_t
mdb_tgt_fwrite(mdb_tgt_t *t, const void *buf, size_t n, uintptr_t addr)
{
	if (t->t_flags & MDB_TGT_F_RDWR)
		return (t->t_ops->t_fwrite(t, buf, n, addr));

	return (set_errno(EMDB_TGTRDONLY));
}

ssize_t
mdb_tgt_ioread(mdb_tgt_t *t, void *buf, size_t n, uintptr_t addr)
{
	return (t->t_ops->t_ioread(t, buf, n, addr));
}

ssize_t
mdb_tgt_iowrite(mdb_tgt_t *t, const void *buf, size_t n, uintptr_t addr)
{
	if (t->t_flags & MDB_TGT_F_RDWR)
		return (t->t_ops->t_iowrite(t, buf, n, addr));

	return (set_errno(EMDB_TGTRDONLY));
}

int
mdb_tgt_vtop(mdb_tgt_t *t, mdb_tgt_as_t as, uintptr_t va, physaddr_t *pap)
{
	return (t->t_ops->t_vtop(t, as, va, pap));
}

ssize_t
mdb_tgt_readstr(mdb_tgt_t *t, mdb_tgt_as_t as, char *buf,
	size_t nbytes, mdb_tgt_addr_t addr)
{
	ssize_t n, nread = mdb_tgt_aread(t, as, buf, nbytes, addr);
	char *p;

	if (nread >= 0) {
		if ((p = memchr(buf, '\0', nread)) != NULL)
			nread = (size_t)(p - buf);
		goto done;
	}

	nread = 0;
	p = &buf[0];

	while (nread < nbytes && (n = mdb_tgt_aread(t, as, p, 1, addr)) == 1) {
		if (*p == '\0')
			return (nread);
		nread++;
		addr++;
		p++;
	}

	if (nread == 0 && n == -1)
		return (-1); /* If we can't even read a byte, return -1 */

done:
	if (nbytes != 0)
		buf[MIN(nread, nbytes - 1)] = '\0';

	return (nread);
}

ssize_t
mdb_tgt_writestr(mdb_tgt_t *t, mdb_tgt_as_t as,
	const char *buf, mdb_tgt_addr_t addr)
{
	ssize_t nwritten = mdb_tgt_awrite(t, as, buf, strlen(buf) + 1, addr);
	return (nwritten > 0 ? nwritten - 1 : nwritten);
}

int
mdb_tgt_lookup_by_name(mdb_tgt_t *t, const char *obj,
	const char *name, GElf_Sym *symp, mdb_syminfo_t *sip)
{
	mdb_syminfo_t info;
	GElf_Sym sym;
	uint_t id;

	if (name == NULL || t == NULL)
		return (set_errno(EINVAL));

	if (obj == MDB_TGT_OBJ_EVERY &&
	    mdb_gelf_symtab_lookup_by_name(mdb.m_prsym, name, &sym, &id) == 0) {
		info.sym_table = MDB_TGT_PRVSYM;
		info.sym_id = id;
		goto found;
	}

	if (t->t_ops->t_lookup_by_name(t, obj, name, &sym, &info) == 0)
		goto found;

	return (-1);

found:
	if (symp != NULL)
		*symp = sym;
	if (sip != NULL)
		*sip = info;
	return (0);
}

int
mdb_tgt_lookup_by_addr(mdb_tgt_t *t, uintptr_t addr, uint_t flags,
	char *buf, size_t len, GElf_Sym *symp, mdb_syminfo_t *sip)
{
	mdb_syminfo_t info;
	GElf_Sym sym;

	if (t == NULL)
		return (set_errno(EINVAL));

	if (t->t_ops->t_lookup_by_addr(t, addr, flags,
	    buf, len, &sym, &info) == 0) {
		if (symp != NULL)
			*symp = sym;
		if (sip != NULL)
			*sip = info;
		return (0);
	}

	return (-1);
}

/*
 * The mdb_tgt_lookup_by_scope function is a convenience routine for code that
 * wants to look up a scoped symbol name such as "object`symbol".  It is
 * implemented as a simple wrapper around mdb_tgt_lookup_by_name.  Note that
 * we split on the *last* occurrence of "`", so the object name itself may
 * contain additional scopes whose evaluation is left to the target.  This
 * allows targets to implement additional scopes, such as source files,
 * function names, link map identifiers, etc.
 */
int
mdb_tgt_lookup_by_scope(mdb_tgt_t *t, const char *s, GElf_Sym *symp,
	mdb_syminfo_t *sip)
{
	const char *object = MDB_TGT_OBJ_EVERY;
	const char *name = s;
	char buf[MDB_TGT_SYM_NAMLEN];

	if (t == NULL)
		return (set_errno(EINVAL));

	if (strchr(name, '`') != NULL) {

		(void) strncpy(buf, s, sizeof (buf));
		buf[sizeof (buf) - 1] = '\0';
		name = buf;

		if ((s = strrsplit(buf, '`')) != NULL) {
			object = buf;
			name = s;
			if (*object == '\0')
				return (set_errno(EMDB_NOOBJ));
			if (*name == '\0')
				return (set_errno(EMDB_NOSYM));
		}
	}

	return (mdb_tgt_lookup_by_name(t, object, name, symp, sip));
}

int
mdb_tgt_symbol_iter(mdb_tgt_t *t, const char *obj, uint_t which,
	uint_t type, mdb_tgt_sym_f *cb, void *p)
{
	if ((which != MDB_TGT_SYMTAB && which != MDB_TGT_DYNSYM) ||
	    (type & ~(MDB_TGT_BIND_ANY | MDB_TGT_TYPE_ANY)) != 0)
		return (set_errno(EINVAL));

	return (t->t_ops->t_symbol_iter(t, obj, which, type, cb, p));
}

ssize_t
mdb_tgt_readsym(mdb_tgt_t *t, mdb_tgt_as_t as, void *buf, size_t nbytes,
	const char *obj, const char *name)
{
	GElf_Sym sym;

	if (mdb_tgt_lookup_by_name(t, obj, name, &sym, NULL) == 0)
		return (mdb_tgt_aread(t, as, buf, nbytes, sym.st_value));

	return (-1);
}

ssize_t
mdb_tgt_writesym(mdb_tgt_t *t, mdb_tgt_as_t as, const void *buf,
	size_t nbytes, const char *obj, const char *name)
{
	GElf_Sym sym;

	if (mdb_tgt_lookup_by_name(t, obj, name, &sym, NULL) == 0)
		return (mdb_tgt_awrite(t, as, buf, nbytes, sym.st_value));

	return (-1);
}

int
mdb_tgt_mapping_iter(mdb_tgt_t *t, mdb_tgt_map_f *cb, void *p)
{
	return (t->t_ops->t_mapping_iter(t, cb, p));
}

int
mdb_tgt_object_iter(mdb_tgt_t *t, mdb_tgt_map_f *cb, void *p)
{
	return (t->t_ops->t_object_iter(t, cb, p));
}

const mdb_map_t *
mdb_tgt_addr_to_map(mdb_tgt_t *t, uintptr_t addr)
{
	return (t->t_ops->t_addr_to_map(t, addr));
}

const mdb_map_t *
mdb_tgt_name_to_map(mdb_tgt_t *t, const char *name)
{
	return (t->t_ops->t_name_to_map(t, name));
}

struct ctf_file *
mdb_tgt_addr_to_ctf(mdb_tgt_t *t, uintptr_t addr)
{
	return (t->t_ops->t_addr_to_ctf(t, addr));
}

struct ctf_file *
mdb_tgt_name_to_ctf(mdb_tgt_t *t, const char *name)
{
	return (t->t_ops->t_name_to_ctf(t, name));
}

/*
 * Return the latest target status.  We just copy out our cached copy.  The
 * status only needs to change when the target is run, stepped, or continued.
 */
int
mdb_tgt_status(mdb_tgt_t *t, mdb_tgt_status_t *tsp)
{
	uint_t dstop = (t->t_status.st_flags & MDB_TGT_DSTOP);
	uint_t istop = (t->t_status.st_flags & MDB_TGT_ISTOP);
	uint_t state = t->t_status.st_state;

	if (tsp == NULL)
		return (set_errno(EINVAL));

	/*
	 * If we're called with the address of the target's internal status,
	 * then call down to update it; otherwise copy out the saved status.
	 */
	if (tsp == &t->t_status && t->t_ops->t_status(t, &t->t_status) != 0)
		return (-1); /* errno is set for us */

	/*
	 * Assert that our state is valid before returning it.  The state must
	 * be valid, and DSTOP and ISTOP cannot be set simultaneously.  ISTOP
	 * is only valid when stopped.  DSTOP is only valid when running or
	 * stopped.  If any test fails, abort the debugger.
	 */
	if (state > MDB_TGT_LOST)
		fail("invalid target state (%u)\n", state);
	if (state != MDB_TGT_STOPPED && istop)
		fail("target state is (%u) and ISTOP is set\n", state);
	if (state != MDB_TGT_STOPPED && state != MDB_TGT_RUNNING && dstop)
		fail("target state is (%u) and DSTOP is set\n", state);
	if (istop && dstop)
		fail("target has ISTOP and DSTOP set simultaneously\n");

	if (tsp != &t->t_status)
		bcopy(&t->t_status, tsp, sizeof (mdb_tgt_status_t));

	return (0);
}

/*
 * For the given sespec, scan its list of vespecs for ones that are marked
 * temporary and delete them.  We use the same method as vespec_delete below.
 */
/*ARGSUSED*/
void
mdb_tgt_sespec_prune_one(mdb_tgt_t *t, mdb_sespec_t *sep)
{
	mdb_vespec_t *vep, *nvep;

	for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) {
		nvep = mdb_list_next(vep);

		if ((vep->ve_flags & (MDB_TGT_SPEC_DELETED |
		    MDB_TGT_SPEC_TEMPORARY)) == MDB_TGT_SPEC_TEMPORARY) {
			vep->ve_flags |= MDB_TGT_SPEC_DELETED;
			mdb_tgt_vespec_rele(t, vep);
		}
	}
}

/*
 * Prune each sespec on the active list of temporary vespecs.  This function
 * is called, for example, after the target finishes a continue operation.
 */
void
mdb_tgt_sespec_prune_all(mdb_tgt_t *t)
{
	mdb_sespec_t *sep, *nsep;

	for (sep = mdb_list_next(&t->t_active); sep != NULL; sep = nsep) {
		nsep = mdb_list_next(sep);
		mdb_tgt_sespec_prune_one(t, sep);
	}
}

/*
 * Transition the given sespec to the IDLE state.  We invoke the destructor,
 * and then move the sespec from the active list to the idle list.
 */
void
mdb_tgt_sespec_idle_one(mdb_tgt_t *t, mdb_sespec_t *sep, int reason)
{
	ASSERT(sep->se_state != MDB_TGT_SPEC_IDLE);

	if (sep->se_state == MDB_TGT_SPEC_ARMED)
		(void) sep->se_ops->se_disarm(t, sep);

	sep->se_ops->se_dtor(t, sep);
	sep->se_data = NULL;

	sep->se_state = MDB_TGT_SPEC_IDLE;
	sep->se_errno = reason;

	mdb_list_delete(&t->t_active, sep);
	mdb_list_append(&t->t_idle, sep);

	mdb_tgt_sespec_prune_one(t, sep);
}

/*
 * Transition each sespec on the active list to the IDLE state.  This function
 * is called, for example, after the target terminates execution.
 */
void
mdb_tgt_sespec_idle_all(mdb_tgt_t *t, int reason, int clear_matched)
{
	mdb_sespec_t *sep, *nsep;
	mdb_vespec_t *vep;

	while ((sep = t->t_matched) != T_SE_END && clear_matched) {
		for (vep = mdb_list_next(&sep->se_velist); vep != NULL; ) {
			vep->ve_flags &= ~MDB_TGT_SPEC_MATCHED;
			vep = mdb_list_next(vep);
		}

		t->t_matched = sep->se_matched;
		sep->se_matched = NULL;
		mdb_tgt_sespec_rele(t, sep);
	}

	for (sep = mdb_list_next(&t->t_active); sep != NULL; sep = nsep) {
		nsep = mdb_list_next(sep);
		mdb_tgt_sespec_idle_one(t, sep, reason);
	}
}

/*
 * Attempt to transition the given sespec from the IDLE to ACTIVE state.  We
 * do this by invoking se_ctor -- if this fails, we save the reason in se_errno
 * and return -1 with errno set.  One strange case we need to deal with here is
 * the possibility that a given vespec is sitting on the idle list with its
 * corresponding sespec, but it is actually a duplicate of another sespec on the
 * active list.  This can happen if the sespec is associated with a
 * MDB_TGT_SPEC_DISABLED vespec that was just enabled, and is now ready to be
 * activated.  A more interesting reason this situation might arise is the case
 * where a virtual address breakpoint is set at an address just mmap'ed by
 * dlmopen.  Since no symbol table information is available for this mapping
 * yet, a pre-existing deferred symbolic breakpoint may already exist for this
 * address, but it is on the idle list.  When the symbol table is ready and the
 * DLACTIVITY event occurs, we now discover that the virtual address obtained by
 * evaluating the symbolic breakpoint matches the explicit virtual address of
 * the active virtual breakpoint.  To resolve this conflict in either case, we
 * destroy the idle sespec, and attach its list of vespecs to the existing
 * active sespec.
 */
int
mdb_tgt_sespec_activate_one(mdb_tgt_t *t, mdb_sespec_t *sep)
{
	mdb_vespec_t *vep = mdb_list_next(&sep->se_velist);

	mdb_vespec_t *nvep;
	mdb_sespec_t *dup;

	ASSERT(sep->se_state == MDB_TGT_SPEC_IDLE);
	ASSERT(vep != NULL);

	if (vep->ve_flags & MDB_TGT_SPEC_DISABLED)
		return (0); /* cannot be activated while disabled bit set */

	/*
	 * First search the active list for an existing, duplicate sespec to
	 * handle the special case described above.
	 */
	for (dup = mdb_list_next(&t->t_active); dup; dup = mdb_list_next(dup)) {
		if (dup->se_ops == sep->se_ops &&
		    dup->se_ops->se_secmp(t, dup, vep->ve_args)) {
			ASSERT(dup != sep);
			break;
		}
	}

	/*
	 * If a duplicate is found, destroy the existing, idle sespec, and
	 * attach all of its vespecs to the duplicate sespec.
	 */
	if (dup != NULL) {
		for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) {
			mdb_dprintf(MDB_DBG_TGT, "merge [ %d ] to sespec %p\n",
			    vep->ve_id, (void *)dup);

			if (dup->se_matched != NULL)
				vep->ve_flags |= MDB_TGT_SPEC_MATCHED;

			nvep = mdb_list_next(vep);
			vep->ve_hits = 0;

			mdb_list_delete(&sep->se_velist, vep);
			mdb_tgt_sespec_rele(t, sep);

			mdb_list_append(&dup->se_velist, vep);
			mdb_tgt_sespec_hold(t, dup);
			vep->ve_se = dup;
		}

		mdb_dprintf(MDB_DBG_TGT, "merged idle sespec %p with %p\n",
		    (void *)sep, (void *)dup);
		return (0);
	}

	/*
	 * If no duplicate is found, call the sespec's constructor.  If this
	 * is successful, move the sespec to the active list.
	 */
	if (sep->se_ops->se_ctor(t, sep, vep->ve_args) < 0) {
		sep->se_errno = errno;
		sep->se_data = NULL;

		return (-1);
	}

	for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) {
		nvep = mdb_list_next(vep);
		vep->ve_hits = 0;
	}
	mdb_list_delete(&t->t_idle, sep);
	mdb_list_append(&t->t_active, sep);
	sep->se_state = MDB_TGT_SPEC_ACTIVE;
	sep->se_errno = 0;

	return (0);
}

/*
 * Transition each sespec on the idle list to the ACTIVE state.  This function
 * is called, for example, after the target's t_run() function returns.  If
 * the se_ctor() function fails, the specifier is not yet applicable; it will
 * remain on the idle list and can be activated later.
 *
 * Returns 1 if there weren't any unexpected activation failures; 0 if there
 * were.
 */
int
mdb_tgt_sespec_activate_all(mdb_tgt_t *t)
{
	mdb_sespec_t *sep, *nsep;
	int rc = 1;

	for (sep = mdb_list_next(&t->t_idle); sep != NULL; sep = nsep) {
		nsep = mdb_list_next(sep);

		if (mdb_tgt_sespec_activate_one(t, sep) < 0 &&
		    sep->se_errno != EMDB_NOOBJ)
			rc = 0;
	}

	return (rc);
}

/*
 * Transition the given sespec to the ARMED state.  Note that we attempt to
 * re-arm sespecs previously in the ERROR state.  If se_arm() fails the sespec
 * transitions to the ERROR state but stays on the active list.
 */
void
mdb_tgt_sespec_arm_one(mdb_tgt_t *t, mdb_sespec_t *sep)
{
	ASSERT(sep->se_state != MDB_TGT_SPEC_IDLE);

	if (sep->se_state == MDB_TGT_SPEC_ARMED)
		return; /* do not arm sespecs more than once */

	if (sep->se_ops->se_arm(t, sep) == -1) {
		sep->se_state = MDB_TGT_SPEC_ERROR;
		sep->se_errno = errno;
	} else {
		sep->se_state = MDB_TGT_SPEC_ARMED;
		sep->se_errno = 0;
	}
}

/*
 * Transition each sespec on the active list (except matched specs) to the
 * ARMED state.  This function is called prior to continuing the target.
 */
void
mdb_tgt_sespec_arm_all(mdb_tgt_t *t)
{
	mdb_sespec_t *sep, *nsep;

	for (sep = mdb_list_next(&t->t_active); sep != NULL; sep = nsep) {
		nsep = mdb_list_next(sep);
		if (sep->se_matched == NULL)
			mdb_tgt_sespec_arm_one(t, sep);
	}
}

/*
 * Transition each sespec on the active list that is in the ARMED state to
 * the ACTIVE state.  If se_disarm() fails, the sespec is transitioned to
 * the ERROR state instead, but left on the active list.
 */
static void
tgt_disarm_sespecs(mdb_tgt_t *t)
{
	mdb_sespec_t *sep;

	for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) {
		if (sep->se_state != MDB_TGT_SPEC_ARMED)
			continue; /* do not disarm if in ERROR state */

		if (sep->se_ops->se_disarm(t, sep) == -1) {
			sep->se_state = MDB_TGT_SPEC_ERROR;
			sep->se_errno = errno;
		} else {
			sep->se_state = MDB_TGT_SPEC_ACTIVE;
			sep->se_errno = 0;
		}
	}
}

/*
 * Determine if the software event that triggered the most recent stop matches
 * any of the active event specifiers.  If 'all' is TRUE, we consider all
 * sespecs in our search.   If 'all' is FALSE, we only consider ARMED sespecs.
 * If we successfully match an event, we add it to the t_matched list and
 * place an additional hold on it.
 */
static mdb_sespec_t *
tgt_match_sespecs(mdb_tgt_t *t, int all)
{
	mdb_sespec_t *sep;

	for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) {
		if (all == FALSE && sep->se_state != MDB_TGT_SPEC_ARMED)
			continue; /* restrict search to ARMED sespecs */

		if (sep->se_state != MDB_TGT_SPEC_ERROR &&
		    sep->se_ops->se_match(t, sep, &t->t_status)) {
			mdb_dprintf(MDB_DBG_TGT, "match se %p\n", (void *)sep);
			mdb_tgt_sespec_hold(t, sep);
			sep->se_matched = t->t_matched;
			t->t_matched = sep;
		}
	}

	return (t->t_matched);
}

/*
 * This function provides the low-level target continue algorithm.  We proceed
 * in three phases: (1) we arm the active sespecs, except the specs matched at
 * the time we last stopped, (2) we call se_cont() on any matched sespecs to
 * step over these event transitions, and then arm the corresponding sespecs,
 * and (3) we call the appropriate low-level continue routine.  Once the
 * target stops again, we determine which sespecs were matched, and invoke the
 * appropriate vespec callbacks and perform other vespec maintenance.
 */
static int
tgt_continue(mdb_tgt_t *t, mdb_tgt_status_t *tsp,
    int (*t_cont)(mdb_tgt_t *, mdb_tgt_status_t *))
{
	mdb_var_t *hitv = mdb_nv_lookup(&mdb.m_nv, "hits");
	uintptr_t pc = t->t_status.st_pc;
	int error = 0;

	mdb_sespec_t *sep, *nsep, *matched;
	mdb_vespec_t *vep, *nvep;
	uintptr_t addr;

	uint_t cbits = 0;	/* union of pending continue bits */
	uint_t ncont = 0;	/* # of callbacks that requested cont */
	uint_t n = 0;		/* # of callbacks */

	/*
	 * If the target is undead, dead, or lost, we no longer allow continue.
	 * This effectively forces the user to use ::kill or ::run after death.
	 */
	if (t->t_status.st_state == MDB_TGT_UNDEAD)
		return (set_errno(EMDB_TGTZOMB));
	if (t->t_status.st_state == MDB_TGT_DEAD)
		return (set_errno(EMDB_TGTCORE));
	if (t->t_status.st_state == MDB_TGT_LOST)
		return (set_errno(EMDB_TGTLOST));

	/*
	 * If any of single-step, step-over, or step-out is pending, it takes
	 * precedence over an explicit or pending continue, because these are
	 * all different specialized forms of continue.
	 */
	if (t->t_flags & MDB_TGT_F_STEP)
		t_cont = t->t_ops->t_step;
	else if (t->t_flags & MDB_TGT_F_NEXT)
		t_cont = t->t_ops->t_step;
	else if (t->t_flags & MDB_TGT_F_STEP_BRANCH)
		t_cont = t->t_ops->t_cont;
	else if (t->t_flags & MDB_TGT_F_STEP_OUT)
		t_cont = t->t_ops->t_cont;

	/*
	 * To handle step-over, we ask the target to find the address past the
	 * next control transfer instruction.  If an address is found, we plant
	 * a temporary breakpoint there and continue; otherwise just step.
	 */
	if ((t->t_flags & MDB_TGT_F_NEXT) && !(t->t_flags & MDB_TGT_F_STEP)) {
		if (t->t_ops->t_next(t, &addr) == -1 || mdb_tgt_add_vbrkpt(t,
		    addr, MDB_TGT_SPEC_HIDDEN | MDB_TGT_SPEC_TEMPORARY,
		    no_se_f, NULL) == 0) {
			mdb_dprintf(MDB_DBG_TGT, "next falling back to step: "
			    "%s\n", mdb_strerror(errno));
		} else
			t_cont = t->t_ops->t_cont;
	}

	/*
	 * To handle step-out, we ask the target to find the return address of
	 * the current frame, plant a temporary breakpoint there, and continue.
	 */
	if (t->t_flags & MDB_TGT_F_STEP_OUT) {
		if (t->t_ops->t_step_out(t, &addr) == -1)
			return (-1); /* errno is set for us */

		if (mdb_tgt_add_vbrkpt(t, addr, MDB_TGT_SPEC_HIDDEN |
		    MDB_TGT_SPEC_TEMPORARY, no_se_f, NULL) == 0)
			return (-1); /* errno is set for us */
	}

	/*
	 * To handle step-branch, we ask the target to enable it for the coming
	 * continue.  Step-branch is incompatible with step, so don't enable it
	 * if we're going to be stepping.
	 */
	if (t->t_flags & MDB_TGT_F_STEP_BRANCH && t_cont == t->t_ops->t_cont) {
		if (t->t_ops->t_step_branch(t) == -1)
			return (-1); /* errno is set for us */
	}

	(void) mdb_signal_block(SIGHUP);
	(void) mdb_signal_block(SIGTERM);
	mdb_intr_disable();

	t->t_flags &= ~T_CONT_BITS;
	t->t_flags |= MDB_TGT_F_BUSY;
	mdb_tgt_sespec_arm_all(t);

	ASSERT(t->t_matched != NULL);
	matched = t->t_matched;
	t->t_matched = T_SE_END;

	if (mdb.m_term != NULL)
		IOP_SUSPEND(mdb.m_term);

	/*
	 * Iterate over the matched sespec list, performing autostop processing
	 * and clearing the matched bit for each associated vespec.  We then
	 * invoke each sespec's se_cont callback in order to continue past
	 * the corresponding event.  If the matched list has more than one
	 * sespec, we assume that the se_cont callbacks are non-interfering.
	 */
	for (sep = matched; sep != T_SE_END; sep = sep->se_matched) {
		for (vep = mdb_list_next(&sep->se_velist); vep != NULL; ) {
			if ((vep->ve_flags & MDB_TGT_SPEC_AUTOSTOP) &&
			    (vep->ve_limit && vep->ve_hits == vep->ve_limit))
				vep->ve_hits = 0;

			vep->ve_flags &= ~MDB_TGT_SPEC_MATCHED;
			vep = mdb_list_next(vep);
		}

		if (sep->se_ops->se_cont(t, sep, &t->t_status) == -1) {
			error = errno ? errno : -1;
			tgt_disarm_sespecs(t);
			break;
		}

		if (!(t->t_status.st_flags & MDB_TGT_ISTOP)) {
			tgt_disarm_sespecs(t);
			if (t->t_status.st_state == MDB_TGT_UNDEAD)
				mdb_tgt_sespec_idle_all(t, EMDB_TGTZOMB, TRUE);
			else if (t->t_status.st_state == MDB_TGT_LOST)
				mdb_tgt_sespec_idle_all(t, EMDB_TGTLOST, TRUE);
			break;
		}
	}

	/*
	 * Clear the se_matched field for each matched sespec, and drop the
	 * reference count since the sespec is no longer on the matched list.
	 */
	for (sep = matched; sep != T_SE_END; sep = nsep) {
		nsep = sep->se_matched;
		sep->se_matched = NULL;
		mdb_tgt_sespec_rele(t, sep);
	}

	/*
	 * If the matched list was non-empty, see if we hit another event while
	 * performing se_cont() processing.  If so, don't bother continuing any
	 * further.  If not, arm the sespecs on the old matched list by calling
	 * mdb_tgt_sespec_arm_all() again and then continue by calling t_cont.
	 */
	if (matched != T_SE_END) {
		if (error != 0 || !(t->t_status.st_flags & MDB_TGT_ISTOP))
			goto out; /* abort now if se_cont() failed */

		if ((t->t_matched = tgt_match_sespecs(t, FALSE)) != T_SE_END) {
			tgt_disarm_sespecs(t);
			goto out;
		}

		mdb_tgt_sespec_arm_all(t);
	}

	if (t_cont != t->t_ops->t_step || pc == t->t_status.st_pc) {
		if (t_cont(t, &t->t_status) != 0)
			error = errno ? errno : -1;
	}

	tgt_disarm_sespecs(t);

	if (t->t_flags & MDB_TGT_F_UNLOAD)
		longjmp(mdb.m_frame->f_pcb, MDB_ERR_QUIT);

	if (t->t_status.st_state == MDB_TGT_UNDEAD)
		mdb_tgt_sespec_idle_all(t, EMDB_TGTZOMB, TRUE);
	else if (t->t_status.st_state == MDB_TGT_LOST)
		mdb_tgt_sespec_idle_all(t, EMDB_TGTLOST, TRUE);
	else if (t->t_status.st_flags & MDB_TGT_ISTOP)
		t->t_matched = tgt_match_sespecs(t, TRUE);
out:
	if (mdb.m_term != NULL)
		IOP_RESUME(mdb.m_term);

	(void) mdb_signal_unblock(SIGTERM);
	(void) mdb_signal_unblock(SIGHUP);
	mdb_intr_enable();

	for (sep = t->t_matched; sep != T_SE_END; sep = sep->se_matched) {
		/*
		 * When we invoke a ve_callback, it may in turn request that the
		 * target continue immediately after callback processing is
		 * complete.  We only allow this to occur if *all* callbacks
		 * agree to continue.  To implement this behavior, we keep a
		 * count (ncont) of such requests, and only apply the cumulative
		 * continue bits (cbits) to the target if ncont is equal to the
		 * total number of callbacks that are invoked (n).
		 */
		for (vep = mdb_list_next(&sep->se_velist);
		    vep != NULL; vep = nvep, n++) {
			/*
			 * Place an extra hold on the current vespec and pick
			 * up the next pointer before invoking the callback: we
			 * must be prepared for the vespec to be deleted or
			 * moved to a different list by the callback.
			 */
			mdb_tgt_vespec_hold(t, vep);
			nvep = mdb_list_next(vep);

			vep->ve_flags |= MDB_TGT_SPEC_MATCHED;
			vep->ve_hits++;

			mdb_nv_set_value(mdb.m_dot, t->t_status.st_pc);
			mdb_nv_set_value(hitv, vep->ve_hits);

			ASSERT((t->t_flags & T_CONT_BITS) == 0);
			vep->ve_callback(t, vep->ve_id, vep->ve_data);

			ncont += (t->t_flags & T_CONT_BITS) != 0;
			cbits |= (t->t_flags & T_CONT_BITS);
			t->t_flags &= ~T_CONT_BITS;

			if (vep->ve_limit && vep->ve_hits == vep->ve_limit) {
				if (vep->ve_flags & MDB_TGT_SPEC_AUTODEL)
					(void) mdb_tgt_vespec_delete(t,
					    vep->ve_id);
				else if (vep->ve_flags & MDB_TGT_SPEC_AUTODIS)
					(void) mdb_tgt_vespec_disable(t,
					    vep->ve_id);
			}

			if (vep->ve_limit && vep->ve_hits < vep->ve_limit) {
				if (vep->ve_flags & MDB_TGT_SPEC_AUTOSTOP)
					(void) mdb_tgt_continue(t, NULL);
			}

			mdb_tgt_vespec_rele(t, vep);
		}
	}

	if (t->t_matched != T_SE_END && ncont == n)
		t->t_flags |= cbits; /* apply continues (see above) */

	mdb_tgt_sespec_prune_all(t);

	t->t_status.st_flags &= ~MDB_TGT_BUSY;
	t->t_flags &= ~MDB_TGT_F_BUSY;

	if (tsp != NULL)
		bcopy(&t->t_status, tsp, sizeof (mdb_tgt_status_t));

	if (error != 0)
		return (set_errno(error));

	return (0);
}

/*
 * This function is the common glue that connects the high-level target layer
 * continue functions (e.g. step and cont below) with the low-level
 * tgt_continue() function above.  Since vespec callbacks may perform any
 * actions, including attempting to continue the target itself, we must be
 * prepared to be called while the target is still marked F_BUSY.  In this
 * case, we just set a pending bit and return.  When we return from the call
 * to tgt_continue() that made us busy into the tgt_request_continue() call
 * that is still on the stack, we will loop around and call tgt_continue()
 * again.  This allows vespecs to continue the target without recursion.
 */
static int
tgt_request_continue(mdb_tgt_t *t, mdb_tgt_status_t *tsp, uint_t tflag,
    int (*t_cont)(mdb_tgt_t *, mdb_tgt_status_t *))
{
	mdb_tgt_spec_desc_t desc;
	mdb_sespec_t *sep;
	char buf[BUFSIZ];
	int status;

	if (t->t_flags & MDB_TGT_F_BUSY) {
		t->t_flags |= tflag;
		return (0);
	}

	do {
		status = tgt_continue(t, tsp, t_cont);
	} while (status == 0 && (t->t_flags & T_CONT_BITS));

	if (status == 0) {
		for (sep = t->t_matched; sep != T_SE_END;
		    sep = sep->se_matched) {
			mdb_vespec_t *vep;

			for (vep = mdb_list_next(&sep->se_velist); vep;
			    vep = mdb_list_next(vep)) {
				if (vep->ve_flags & MDB_TGT_SPEC_SILENT)
					continue;
				warn("%s\n", sep->se_ops->se_info(t, sep,
				    vep, &desc, buf, sizeof (buf)));
			}
		}

		mdb_callb_fire(MDB_CALLB_STCHG);
	}

	t->t_flags &= ~T_CONT_BITS;
	return (status);
}

/*
 * Restart target execution: we rely upon the underlying target implementation
 * to do most of the work for us.  In particular, we assume it will properly
 * preserve the state of our event lists if the run fails for some reason,
 * and that it will reset all events to the IDLE state if the run succeeds.
 * If it is successful, we attempt to activate all of the idle sespecs.  The
 * t_run() operation is defined to leave the target stopped at the earliest
 * possible point in execution, and then return control to the debugger,
 * awaiting a step or continue operation to set it running again.
 */
int
mdb_tgt_run(mdb_tgt_t *t, int argc, const mdb_arg_t *argv)
{
	int i;

	for (i = 0; i < argc; i++) {
		if (argv->a_type != MDB_TYPE_STRING)
			return (set_errno(EINVAL));
	}

	if (t->t_ops->t_run(t, argc, argv) == -1)
		return (-1); /* errno is set for us */

	t->t_flags &= ~T_CONT_BITS;
	(void) mdb_tgt_sespec_activate_all(t);

	if (mdb.m_term != NULL)
		IOP_CTL(mdb.m_term, MDB_IOC_CTTY, NULL);

	return (0);
}

int
mdb_tgt_step(mdb_tgt_t *t, mdb_tgt_status_t *tsp)
{
	return (tgt_request_continue(t, tsp, MDB_TGT_F_STEP, t->t_ops->t_step));
}

int
mdb_tgt_step_out(mdb_tgt_t *t, mdb_tgt_status_t *tsp)
{
	t->t_flags |= MDB_TGT_F_STEP_OUT; /* set flag even if tgt not busy */
	return (tgt_request_continue(t, tsp, 0, t->t_ops->t_cont));
}

int
mdb_tgt_step_branch(mdb_tgt_t *t, mdb_tgt_status_t *tsp)
{
	t->t_flags |= MDB_TGT_F_STEP_BRANCH; /* set flag even if tgt not busy */
	return (tgt_request_continue(t, tsp, 0, t->t_ops->t_cont));
}

int
mdb_tgt_next(mdb_tgt_t *t, mdb_tgt_status_t *tsp)
{
	t->t_flags |= MDB_TGT_F_NEXT; /* set flag even if tgt not busy */
	return (tgt_request_continue(t, tsp, 0, t->t_ops->t_step));
}

int
mdb_tgt_continue(mdb_tgt_t *t, mdb_tgt_status_t *tsp)
{
	return (tgt_request_continue(t, tsp, MDB_TGT_F_CONT, t->t_ops->t_cont));
}

int
mdb_tgt_signal(mdb_tgt_t *t, int sig)
{
	return (t->t_ops->t_signal(t, sig));
}

void *
mdb_tgt_vespec_data(mdb_tgt_t *t, int vid)
{
	mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, vid);

	if (vep == NULL) {
		(void) set_errno(EMDB_NOSESPEC);
		return (NULL);
	}

	return (vep->ve_data);
}

/*
 * Return a structured description and comment string for the given vespec.
 * We fill in the common information from the vespec, and then call down to
 * the underlying sespec to provide the comment string and modify any
 * event type-specific information.
 */
char *
mdb_tgt_vespec_info(mdb_tgt_t *t, int vid, mdb_tgt_spec_desc_t *sp,
    char *buf, size_t nbytes)
{
	mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, vid);

	mdb_tgt_spec_desc_t desc;
	mdb_sespec_t *sep;

	if (vep == NULL) {
		if (sp != NULL)
			bzero(sp, sizeof (mdb_tgt_spec_desc_t));
		(void) set_errno(EMDB_NOSESPEC);
		return (NULL);
	}

	if (sp == NULL)
		sp = &desc;

	sep = vep->ve_se;

	sp->spec_id = vep->ve_id;
	sp->spec_flags = vep->ve_flags;
	sp->spec_hits = vep->ve_hits;
	sp->spec_limit = vep->ve_limit;
	sp->spec_state = sep->se_state;
	sp->spec_errno = sep->se_errno;
	sp->spec_base = NULL;
	sp->spec_size = 0;
	sp->spec_data = vep->ve_data;

	return (sep->se_ops->se_info(t, sep, vep, sp, buf, nbytes));
}

/*
 * Qsort callback for sorting vespecs by VID, used below.
 */
static int
tgt_vespec_compare(const mdb_vespec_t **lp, const mdb_vespec_t **rp)
{
	return ((*lp)->ve_id - (*rp)->ve_id);
}

/*
 * Iterate over all vespecs and call the specified callback function with the
 * corresponding VID and caller data pointer.  We want the callback function
 * to see a consistent, sorted snapshot of the vespecs, and allow the callback
 * to take actions such as deleting the vespec itself, so we cannot simply
 * iterate over the lists.  Instead, we pre-allocate an array of vespec
 * pointers, fill it in and place an additional hold on each vespec, and then
 * sort it.  After the callback has been executed on each vespec in the
 * sorted array, we remove our hold and free the temporary array.
 */
int
mdb_tgt_vespec_iter(mdb_tgt_t *t, mdb_tgt_vespec_f *func, void *p)
{
	mdb_vespec_t **veps, **vepp, **vend;
	mdb_vespec_t *vep, *nvep;
	mdb_sespec_t *sep;

	uint_t vecnt = t->t_vecnt;

	veps = mdb_alloc(sizeof (mdb_vespec_t *) * vecnt, UM_SLEEP);
	vend = veps + vecnt;
	vepp = veps;

	for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) {
		for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) {
			mdb_tgt_vespec_hold(t, vep);
			nvep = mdb_list_next(vep);
			*vepp++ = vep;
		}
	}

	for (sep = mdb_list_next(&t->t_idle); sep; sep = mdb_list_next(sep)) {
		for (vep = mdb_list_next(&sep->se_velist); vep; vep = nvep) {
			mdb_tgt_vespec_hold(t, vep);
			nvep = mdb_list_next(vep);
			*vepp++ = vep;
		}
	}

	if (vepp != vend) {
		fail("target has %u vespecs on list but vecnt shows %u\n",
		    (uint_t)(vepp - veps), vecnt);
	}

	qsort(veps, vecnt, sizeof (mdb_vespec_t *),
	    (int (*)(const void *, const void *))tgt_vespec_compare);

	for (vepp = veps; vepp < vend; vepp++) {
		if (func(t, p, (*vepp)->ve_id, (*vepp)->ve_data) != 0)
			break;
	}

	for (vepp = veps; vepp < vend; vepp++)
		mdb_tgt_vespec_rele(t, *vepp);

	mdb_free(veps, sizeof (mdb_vespec_t *) * vecnt);
	return (0);
}

/*
 * Reset the vespec flags, match limit, and callback data to the specified
 * values.  We silently correct invalid parameters, except for the VID.
 * The caller is required to query the existing properties and pass back
 * the existing values for any properties that should not be modified.
 * If the callback data is modified, the caller is responsible for cleaning
 * up any state associated with the previous value.
 */
int
mdb_tgt_vespec_modify(mdb_tgt_t *t, int id, uint_t flags,
    uint_t limit, void *data)
{
	mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, id);

	if (vep == NULL)
		return (set_errno(EMDB_NOSESPEC));

	/*
	 * If the value of the MDB_TGT_SPEC_DISABLED bit is changing, call the
	 * appropriate vespec function to do the enable/disable work.
	 */
	if ((flags & MDB_TGT_SPEC_DISABLED) !=
	    (vep->ve_flags & MDB_TGT_SPEC_DISABLED)) {
		if (flags & MDB_TGT_SPEC_DISABLED)
			(void) mdb_tgt_vespec_disable(t, id);
		else
			(void) mdb_tgt_vespec_enable(t, id);
	}

	/*
	 * Make that only one MDB_TGT_SPEC_AUTO* bit is set in the new flags
	 * value: extra bits are cleared according to order of precedence.
	 */
	if (flags & MDB_TGT_SPEC_AUTOSTOP)
		flags &= ~(MDB_TGT_SPEC_AUTODEL | MDB_TGT_SPEC_AUTODIS);
	else if (flags & MDB_TGT_SPEC_AUTODEL)
		flags &= ~MDB_TGT_SPEC_AUTODIS;

	/*
	 * The TEMPORARY property always takes precedence over STICKY.
	 */
	if (flags & MDB_TGT_SPEC_TEMPORARY)
		flags &= ~MDB_TGT_SPEC_STICKY;

	/*
	 * If any MDB_TGT_SPEC_AUTO* bits are changing, reset the hit count
	 * back to zero and clear all of the old auto bits.
	 */
	if ((flags & T_AUTO_BITS) != (vep->ve_flags & T_AUTO_BITS)) {
		vep->ve_flags &= ~T_AUTO_BITS;
		vep->ve_hits = 0;
	}

	vep->ve_flags = (vep->ve_flags & T_IMPL_BITS) | (flags & ~T_IMPL_BITS);
	vep->ve_data = data;

	/*
	 * If any MDB_TGT_SPEC_AUTO* flags are set, make sure the limit is at
	 * least one.  If none are set, reset it back to zero.
	 */
	if (vep->ve_flags & T_AUTO_BITS)
		vep->ve_limit = MAX(limit, 1);
	else
		vep->ve_limit = 0;

	/*
	 * As a convenience, we allow the caller to specify SPEC_DELETED in
	 * the flags field as indication that the event should be deleted.
	 */
	if (flags & MDB_TGT_SPEC_DELETED)
		(void) mdb_tgt_vespec_delete(t, id);

	return (0);
}

/*
 * Remove the user disabled bit from the specified vespec, and attempt to
 * activate the underlying sespec and move it to the active list if possible.
 */
int
mdb_tgt_vespec_enable(mdb_tgt_t *t, int id)
{
	mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, id);

	if (vep == NULL)
		return (set_errno(EMDB_NOSESPEC));

	if (vep->ve_flags & MDB_TGT_SPEC_DISABLED) {
		ASSERT(mdb_list_next(vep) == NULL);
		vep->ve_flags &= ~MDB_TGT_SPEC_DISABLED;
		if (mdb_tgt_sespec_activate_one(t, vep->ve_se) < 0)
			return (-1); /* errno is set for us */
	}

	return (0);
}

/*
 * Set the user disabled bit on the specified vespec, and move it to the idle
 * list.  If the vespec is not alone with its sespec or if it is a currently
 * matched event, we must always create a new idle sespec and move the vespec
 * there.  If the vespec was alone and active, we can simply idle the sespec.
 */
int
mdb_tgt_vespec_disable(mdb_tgt_t *t, int id)
{
	mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, id);
	mdb_sespec_t *sep;

	if (vep == NULL)
		return (set_errno(EMDB_NOSESPEC));

	if (vep->ve_flags & MDB_TGT_SPEC_DISABLED)
		return (0); /* already disabled */

	if (mdb_list_prev(vep) != NULL || mdb_list_next(vep) != NULL ||
	    vep->ve_se->se_matched != NULL) {

		sep = mdb_tgt_sespec_insert(t, vep->ve_se->se_ops, &t->t_idle);

		mdb_list_delete(&vep->ve_se->se_velist, vep);
		mdb_tgt_sespec_rele(t, vep->ve_se);

		mdb_list_append(&sep->se_velist, vep);
		mdb_tgt_sespec_hold(t, sep);

		vep->ve_flags &= ~MDB_TGT_SPEC_MATCHED;
		vep->ve_se = sep;

	} else if (vep->ve_se->se_state != MDB_TGT_SPEC_IDLE)
		mdb_tgt_sespec_idle_one(t, vep->ve_se, EMDB_SPECDIS);

	vep->ve_flags |= MDB_TGT_SPEC_DISABLED;
	return (0);
}

/*
 * Delete the given vespec.  We use the MDB_TGT_SPEC_DELETED flag to ensure that
 * multiple calls to mdb_tgt_vespec_delete to not attempt to decrement the
 * reference count on the vespec more than once.  This is because the vespec
 * may remain referenced if it is currently held by another routine (e.g.
 * vespec_iter), and so the user could attempt to delete it more than once
 * since it reference count will be >= 2 prior to the first delete call.
 */
int
mdb_tgt_vespec_delete(mdb_tgt_t *t, int id)
{
	mdb_vespec_t *vep = mdb_tgt_vespec_lookup(t, id);

	if (vep == NULL)
		return (set_errno(EMDB_NOSESPEC));

	if (vep->ve_flags & MDB_TGT_SPEC_DELETED)
		return (set_errno(EBUSY));

	vep->ve_flags |= MDB_TGT_SPEC_DELETED;
	mdb_tgt_vespec_rele(t, vep);
	return (0);
}

int
mdb_tgt_add_vbrkpt(mdb_tgt_t *t, uintptr_t addr,
    int spec_flags, mdb_tgt_se_f *func, void *p)
{
	return (t->t_ops->t_add_vbrkpt(t, addr, spec_flags, func, p));
}

int
mdb_tgt_add_sbrkpt(mdb_tgt_t *t, const char *symbol,
    int spec_flags, mdb_tgt_se_f *func, void *p)
{
	return (t->t_ops->t_add_sbrkpt(t, symbol, spec_flags, func, p));
}

int
mdb_tgt_add_pwapt(mdb_tgt_t *t, physaddr_t pa, size_t n, uint_t flags,
    int spec_flags, mdb_tgt_se_f *func, void *p)
{
	if ((flags & ~MDB_TGT_WA_RWX) || flags == 0) {
		(void) set_errno(EINVAL);
		return (0);
	}

	if (pa + n < pa) {
		(void) set_errno(EMDB_WPRANGE);
		return (0);
	}

	return (t->t_ops->t_add_pwapt(t, pa, n, flags, spec_flags, func, p));
}

int
mdb_tgt_add_vwapt(mdb_tgt_t *t, uintptr_t va, size_t n, uint_t flags,
    int spec_flags, mdb_tgt_se_f *func, void *p)
{
	if ((flags & ~MDB_TGT_WA_RWX) || flags == 0) {
		(void) set_errno(EINVAL);
		return (0);
	}

	if (va + n < va) {
		(void) set_errno(EMDB_WPRANGE);
		return (0);
	}

	return (t->t_ops->t_add_vwapt(t, va, n, flags, spec_flags, func, p));
}

int
mdb_tgt_add_iowapt(mdb_tgt_t *t, uintptr_t addr, size_t n, uint_t flags,
    int spec_flags, mdb_tgt_se_f *func, void *p)
{
	if ((flags & ~MDB_TGT_WA_RWX) || flags == 0) {
		(void) set_errno(EINVAL);
		return (0);
	}

	if (addr + n < addr) {
		(void) set_errno(EMDB_WPRANGE);
		return (0);
	}

	return (t->t_ops->t_add_iowapt(t, addr, n, flags, spec_flags, func, p));
}

int
mdb_tgt_add_sysenter(mdb_tgt_t *t, int sysnum,
    int spec_flags, mdb_tgt_se_f *func, void *p)
{
	return (t->t_ops->t_add_sysenter(t, sysnum, spec_flags, func, p));
}

int
mdb_tgt_add_sysexit(mdb_tgt_t *t, int sysnum,
    int spec_flags, mdb_tgt_se_f *func, void *p)
{
	return (t->t_ops->t_add_sysexit(t, sysnum, spec_flags, func, p));
}

int
mdb_tgt_add_signal(mdb_tgt_t *t, int sig,
    int spec_flags, mdb_tgt_se_f *func, void *p)
{
	return (t->t_ops->t_add_signal(t, sig, spec_flags, func, p));
}

int
mdb_tgt_add_fault(mdb_tgt_t *t, int flt,
    int spec_flags, mdb_tgt_se_f *func, void *p)
{
	return (t->t_ops->t_add_fault(t, flt, spec_flags, func, p));
}

int
mdb_tgt_getareg(mdb_tgt_t *t, mdb_tgt_tid_t tid,
    const char *rname, mdb_tgt_reg_t *rp)
{
	return (t->t_ops->t_getareg(t, tid, rname, rp));
}

int
mdb_tgt_putareg(mdb_tgt_t *t, mdb_tgt_tid_t tid,
    const char *rname, mdb_tgt_reg_t r)
{
	return (t->t_ops->t_putareg(t, tid, rname, r));
}

int
mdb_tgt_stack_iter(mdb_tgt_t *t, const mdb_tgt_gregset_t *gregs,
    mdb_tgt_stack_f *cb, void *p)
{
	return (t->t_ops->t_stack_iter(t, gregs, cb, p));
}

int
mdb_tgt_xdata_iter(mdb_tgt_t *t, mdb_tgt_xdata_f *func, void *private)
{
	mdb_xdata_t *xdp;

	for (xdp = mdb_list_next(&t->t_xdlist); xdp; xdp = mdb_list_next(xdp)) {
		if (func(private, xdp->xd_name, xdp->xd_desc,
		    xdp->xd_copy(t, NULL, 0)) != 0)
			break;
	}

	return (0);
}

ssize_t
mdb_tgt_getxdata(mdb_tgt_t *t, const char *name, void *buf, size_t nbytes)
{
	mdb_xdata_t *xdp;

	for (xdp = mdb_list_next(&t->t_xdlist); xdp; xdp = mdb_list_next(xdp)) {
		if (strcmp(xdp->xd_name, name) == 0)
			return (xdp->xd_copy(t, buf, nbytes));
	}

	return (set_errno(ENODATA));
}

long
mdb_tgt_notsup()
{
	return (set_errno(EMDB_TGTNOTSUP));
}

void *
mdb_tgt_null()
{
	(void) set_errno(EMDB_TGTNOTSUP);
	return (NULL);
}

long
mdb_tgt_nop()
{
	return (0L);
}

int
mdb_tgt_xdata_insert(mdb_tgt_t *t, const char *name, const char *desc,
	ssize_t (*copy)(mdb_tgt_t *, void *, size_t))
{
	mdb_xdata_t *xdp;

	for (xdp = mdb_list_next(&t->t_xdlist); xdp; xdp = mdb_list_next(xdp)) {
		if (strcmp(xdp->xd_name, name) == 0)
			return (set_errno(EMDB_XDEXISTS));
	}

	xdp = mdb_alloc(sizeof (mdb_xdata_t), UM_SLEEP);
	mdb_list_append(&t->t_xdlist, xdp);

	xdp->xd_name = name;
	xdp->xd_desc = desc;
	xdp->xd_copy = copy;

	return (0);
}

int
mdb_tgt_xdata_delete(mdb_tgt_t *t, const char *name)
{
	mdb_xdata_t *xdp;

	for (xdp = mdb_list_next(&t->t_xdlist); xdp; xdp = mdb_list_next(xdp)) {
		if (strcmp(xdp->xd_name, name) == 0) {
			mdb_list_delete(&t->t_xdlist, xdp);
			mdb_free(xdp, sizeof (mdb_xdata_t));
			return (0);
		}
	}

	return (set_errno(EMDB_NOXD));
}

int
mdb_tgt_sym_match(const GElf_Sym *sym, uint_t mask)
{
#if STT_NUM != (STT_TLS + 1)
#error "STT_NUM has grown. update mdb_tgt_sym_match()"
#endif

	uchar_t s_bind = GELF_ST_BIND(sym->st_info);
	uchar_t s_type = GELF_ST_TYPE(sym->st_info);

	/*
	 * In case you haven't already guessed, this relies on the bitmask
	 * used by <mdb/mdb_target.h> and <libproc.h> for encoding symbol
	 * type and binding matching the order of STB and STT constants
	 * in <sys/elf.h>.  Changes to ELF must maintain binary
	 * compatibility, so I think this is reasonably fair game.
	 */
	if (s_bind < STB_NUM && s_type < STT_NUM) {
		uint_t type = (1 << (s_type + 8)) | (1 << s_bind);
		return ((type & ~mask) == 0);
	}

	return (0); /* Unknown binding or type; fail to match */
}

void
mdb_tgt_elf_export(mdb_gelf_file_t *gf)
{
	GElf_Xword d = 0, t = 0;
	GElf_Addr b = 0, e = 0;
	uint32_t m = 0;
	mdb_var_t *v;

	/*
	 * Reset legacy adb variables based on the specified ELF object file
	 * provided by the target.  We define these variables:
	 *
	 * b - the address of the data segment (first writeable Phdr)
	 * d - the size of the data segment
	 * e - the address of the entry point
	 * m - the magic number identifying the file
	 * t - the address of the text segment (first executable Phdr)
	 */
	if (gf != NULL) {
		const GElf_Phdr *text = NULL, *data = NULL;
		size_t i;

		e = gf->gf_ehdr.e_entry;
		bcopy(&gf->gf_ehdr.e_ident[EI_MAG0], &m, sizeof (m));

		for (i = 0; i < gf->gf_npload; i++) {
			if (text == NULL && (gf->gf_phdrs[i].p_flags & PF_X))
				text = &gf->gf_phdrs[i];
			if (data == NULL && (gf->gf_phdrs[i].p_flags & PF_W))
				data = &gf->gf_phdrs[i];
		}

		if (text != NULL)
			t = text->p_memsz;
		if (data != NULL) {
			b = data->p_vaddr;
			d = data->p_memsz;
		}
	}

	if ((v = mdb_nv_lookup(&mdb.m_nv, "b")) != NULL)
		mdb_nv_set_value(v, b);
	if ((v = mdb_nv_lookup(&mdb.m_nv, "d")) != NULL)
		mdb_nv_set_value(v, d);
	if ((v = mdb_nv_lookup(&mdb.m_nv, "e")) != NULL)
		mdb_nv_set_value(v, e);
	if ((v = mdb_nv_lookup(&mdb.m_nv, "m")) != NULL)
		mdb_nv_set_value(v, m);
	if ((v = mdb_nv_lookup(&mdb.m_nv, "t")) != NULL)
		mdb_nv_set_value(v, t);
}

/*ARGSUSED*/
void
mdb_tgt_sespec_hold(mdb_tgt_t *t, mdb_sespec_t *sep)
{
	sep->se_refs++;
	ASSERT(sep->se_refs != 0);
}

void
mdb_tgt_sespec_rele(mdb_tgt_t *t, mdb_sespec_t *sep)
{
	ASSERT(sep->se_refs != 0);

	if (--sep->se_refs == 0) {
		mdb_dprintf(MDB_DBG_TGT, "destroying sespec %p\n", (void *)sep);
		ASSERT(mdb_list_next(&sep->se_velist) == NULL);

		if (sep->se_state != MDB_TGT_SPEC_IDLE) {
			sep->se_ops->se_dtor(t, sep);
			mdb_list_delete(&t->t_active, sep);
		} else
			mdb_list_delete(&t->t_idle, sep);

		mdb_free(sep, sizeof (mdb_sespec_t));
	}
}

mdb_sespec_t *
mdb_tgt_sespec_insert(mdb_tgt_t *t, const mdb_se_ops_t *ops, mdb_list_t *list)
{
	mdb_sespec_t *sep = mdb_zalloc(sizeof (mdb_sespec_t), UM_SLEEP);

	if (list == &t->t_active)
		sep->se_state = MDB_TGT_SPEC_ACTIVE;
	else
		sep->se_state = MDB_TGT_SPEC_IDLE;

	mdb_list_append(list, sep);
	sep->se_ops = ops;
	return (sep);
}

mdb_sespec_t *
mdb_tgt_sespec_lookup_active(mdb_tgt_t *t, const mdb_se_ops_t *ops, void *args)
{
	mdb_sespec_t *sep;

	for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) {
		if (sep->se_ops == ops && sep->se_ops->se_secmp(t, sep, args))
			break;
	}

	return (sep);
}

mdb_sespec_t *
mdb_tgt_sespec_lookup_idle(mdb_tgt_t *t, const mdb_se_ops_t *ops, void *args)
{
	mdb_sespec_t *sep;

	for (sep = mdb_list_next(&t->t_idle); sep; sep = mdb_list_next(sep)) {
		if (sep->se_ops == ops && sep->se_ops->se_vecmp(t,
		    mdb_list_next(&sep->se_velist), args))
			break;
	}

	return (sep);
}

/*ARGSUSED*/
void
mdb_tgt_vespec_hold(mdb_tgt_t *t, mdb_vespec_t *vep)
{
	vep->ve_refs++;
	ASSERT(vep->ve_refs != 0);
}

void
mdb_tgt_vespec_rele(mdb_tgt_t *t, mdb_vespec_t *vep)
{
	ASSERT(vep->ve_refs != 0);

	if (--vep->ve_refs == 0) {
		/*
		 * Remove this vespec from the sespec's velist and decrement
		 * the reference count on the sespec.
		 */
		mdb_list_delete(&vep->ve_se->se_velist, vep);
		mdb_tgt_sespec_rele(t, vep->ve_se);

		/*
		 * If we are deleting the most recently assigned VID, reset
		 * t_vepos or t_veneg as appropriate to re-use that number.
		 * This could be enhanced to re-use any free number by
		 * maintaining a bitmap or hash of the allocated IDs.
		 */
		if (vep->ve_id > 0 && t->t_vepos == vep->ve_id + 1)
			t->t_vepos = vep->ve_id;
		else if (vep->ve_id < 0 && t->t_veneg == -vep->ve_id + 1)
			t->t_veneg = -vep->ve_id;

		/*
		 * Call the destructor to clean up ve_args, and then free
		 * the actual vespec structure.
		 */
		vep->ve_dtor(vep);
		mdb_free(vep, sizeof (mdb_vespec_t));

		ASSERT(t->t_vecnt != 0);
		t->t_vecnt--;
	}
}

int
mdb_tgt_vespec_insert(mdb_tgt_t *t, const mdb_se_ops_t *ops, int flags,
    mdb_tgt_se_f *func, void *data, void *args, void (*dtor)(mdb_vespec_t *))
{
	mdb_vespec_t *vep = mdb_zalloc(sizeof (mdb_vespec_t), UM_SLEEP);

	int id, mult, *seqp;
	mdb_sespec_t *sep;

	/*
	 * Make that only one MDB_TGT_SPEC_AUTO* bit is set in the new flags
	 * value: extra bits are cleared according to order of precedence.
	 */
	if (flags & MDB_TGT_SPEC_AUTOSTOP)
		flags &= ~(MDB_TGT_SPEC_AUTODEL | MDB_TGT_SPEC_AUTODIS);
	else if (flags & MDB_TGT_SPEC_AUTODEL)
		flags &= ~MDB_TGT_SPEC_AUTODIS;

	/*
	 * The TEMPORARY property always takes precedence over STICKY.
	 */
	if (flags & MDB_TGT_SPEC_TEMPORARY)
		flags &= ~MDB_TGT_SPEC_STICKY;

	/*
	 * Find a matching sespec or create a new one on the appropriate list.
	 * We always create a new sespec if the vespec is created disabled.
	 */
	if (flags & MDB_TGT_SPEC_DISABLED)
		sep = mdb_tgt_sespec_insert(t, ops, &t->t_idle);
	else if ((sep = mdb_tgt_sespec_lookup_active(t, ops, args)) == NULL &&
	    (sep = mdb_tgt_sespec_lookup_idle(t, ops, args)) == NULL)
		sep = mdb_tgt_sespec_insert(t, ops, &t->t_active);

	/*
	 * Generate a new ID for the vespec.  Increasing positive integers are
	 * assigned to visible vespecs; decreasing negative integers are
	 * assigned to hidden vespecs.  The target saves our most recent choice.
	 */
	if (flags & MDB_TGT_SPEC_INTERNAL) {
		seqp = &t->t_veneg;
		mult = -1;
	} else {
		seqp = &t->t_vepos;
		mult = 1;
	}

	id = *seqp;

	while (mdb_tgt_vespec_lookup(t, id * mult) != NULL)
		id = MAX(id + 1, 1);

	*seqp = MAX(id + 1, 1);

	vep->ve_id = id * mult;
	vep->ve_flags = flags & ~(MDB_TGT_SPEC_MATCHED | MDB_TGT_SPEC_DELETED);
	vep->ve_se = sep;
	vep->ve_callback = func;
	vep->ve_data = data;
	vep->ve_args = args;
	vep->ve_dtor = dtor;

	mdb_list_append(&sep->se_velist, vep);
	mdb_tgt_sespec_hold(t, sep);

	mdb_tgt_vespec_hold(t, vep);
	t->t_vecnt++;

	/*
	 * If this vespec is the first reference to the sespec and it's active,
	 * then it is newly created and we should attempt to initialize it.
	 * If se_ctor fails, then move the sespec back to the idle list.
	 */
	if (sep->se_refs == 1 && sep->se_state == MDB_TGT_SPEC_ACTIVE &&
	    sep->se_ops->se_ctor(t, sep, vep->ve_args) == -1) {

		mdb_list_delete(&t->t_active, sep);
		mdb_list_append(&t->t_idle, sep);

		sep->se_state = MDB_TGT_SPEC_IDLE;
		sep->se_errno = errno;
		sep->se_data = NULL;
	}

	/*
	 * If the sespec is active and the target is currently running (because
	 * we grabbed it using PGRAB_NOSTOP), then go ahead and attempt to arm
	 * the sespec so it will take effect immediately.
	 */
	if (sep->se_state == MDB_TGT_SPEC_ACTIVE &&
	    t->t_status.st_state == MDB_TGT_RUNNING)
		mdb_tgt_sespec_arm_one(t, sep);

	mdb_dprintf(MDB_DBG_TGT, "inserted [ %d ] sep=%p refs=%u state=%d\n",
	    vep->ve_id, (void *)sep, sep->se_refs, sep->se_state);

	return (vep->ve_id);
}

/*
 * Search the target's active, idle, and disabled lists for the vespec matching
 * the specified VID, and return a pointer to it, or NULL if no match is found.
 */
mdb_vespec_t *
mdb_tgt_vespec_lookup(mdb_tgt_t *t, int vid)
{
	mdb_sespec_t *sep;
	mdb_vespec_t *vep;

	if (vid == 0)
		return (NULL); /* 0 is never a valid VID */

	for (sep = mdb_list_next(&t->t_active); sep; sep = mdb_list_next(sep)) {
		for (vep = mdb_list_next(&sep->se_velist); vep;
		    vep = mdb_list_next(vep)) {
			if (vep->ve_id == vid)
				return (vep);
		}
	}

	for (sep = mdb_list_next(&t->t_idle); sep; sep = mdb_list_next(sep)) {
		for (vep = mdb_list_next(&sep->se_velist); vep;
		    vep = mdb_list_next(vep)) {
			if (vep->ve_id == vid)
				return (vep);
		}
	}

	return (NULL);
}

/*ARGSUSED*/
void
no_ve_dtor(mdb_vespec_t *vep)
{
	/* default destructor does nothing */
}

/*ARGSUSED*/
void
no_se_f(mdb_tgt_t *t, int vid, void *data)
{
	/* default callback does nothing */
}

/*ARGSUSED*/
void
no_se_dtor(mdb_tgt_t *t, mdb_sespec_t *sep)
{
	/* default destructor does nothing */
}

/*ARGSUSED*/
int
no_se_secmp(mdb_tgt_t *t, mdb_sespec_t *sep, void *args)
{
	return (sep->se_data == args);
}

/*ARGSUSED*/
int
no_se_vecmp(mdb_tgt_t *t, mdb_vespec_t *vep, void *args)
{
	return (vep->ve_args == args);
}

/*ARGSUSED*/
int
no_se_arm(mdb_tgt_t *t, mdb_sespec_t *sep)
{
	return (0); /* return success */
}

/*ARGSUSED*/
int
no_se_disarm(mdb_tgt_t *t, mdb_sespec_t *sep)
{
	return (0); /* return success */
}

/*ARGSUSED*/
int
no_se_cont(mdb_tgt_t *t, mdb_sespec_t *sep, mdb_tgt_status_t *tsp)
{
	if (tsp != &t->t_status)
		bcopy(&t->t_status, tsp, sizeof (mdb_tgt_status_t));

	return (0); /* return success */
}

int
mdb_tgt_register_dcmds(mdb_tgt_t *t, const mdb_dcmd_t *dcp, int flags)
{
	int fail = 0;

	for (; dcp->dc_name != NULL; dcp++) {
		if (mdb_module_add_dcmd(t->t_module, dcp, flags) == -1) {
			warn("failed to add dcmd %s", dcp->dc_name);
			fail++;
		}
	}

	return (fail > 0 ? -1 : 0);
}

int
mdb_tgt_register_walkers(mdb_tgt_t *t, const mdb_walker_t *wp, int flags)
{
	int fail = 0;

	for (; wp->walk_name != NULL; wp++) {
		if (mdb_module_add_walker(t->t_module, wp, flags) == -1) {
			warn("failed to add walk %s", wp->walk_name);
			fail++;
		}
	}

	return (fail > 0 ? -1 : 0);
}

void
mdb_tgt_register_regvars(mdb_tgt_t *t, const mdb_tgt_regdesc_t *rdp,
    const mdb_nv_disc_t *disc, int flags)
{
	for (; rdp->rd_name != NULL; rdp++) {
		if (!(rdp->rd_flags & MDB_TGT_R_EXPORT))
			continue; /* Don't export register as a variable */

		if (rdp->rd_flags & MDB_TGT_R_RDONLY)
			flags |= MDB_NV_RDONLY;

		(void) mdb_nv_insert(&mdb.m_nv, rdp->rd_name, disc,
		    (uintptr_t)t, MDB_NV_PERSIST | flags);
	}
}