changeset 13688:32dde9989090

2701 Add tab completion support for mdb Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com> Reviewed by: Adam Leventhal <ahl@delphix.com> Reviewed by: Sebastien Roy <sebastien.roy@delphix.com> Reviewed by: Darren Reed <darrenr@fastmail.net> Reviewed by: Gordon Ross <gordon.w.ross@gmail.com> Approved by: Richard Lowe <richlowe@richlowe.net>
author Matt Amdur <matt.amdur@delphix.com>
date Fri, 11 May 2012 22:38:13 -0400
parents 72ce76fa37fb
children 125d1b3a6fa8
files usr/src/cmd/mdb/Makefile.kmdb.files usr/src/cmd/mdb/Makefile.mdb usr/src/cmd/mdb/common/mdb/mdb.c usr/src/cmd/mdb/common/mdb/mdb.h usr/src/cmd/mdb/common/mdb/mdb_cmds.c usr/src/cmd/mdb/common/mdb/mdb_modapi.h usr/src/cmd/mdb/common/mdb/mdb_module.c usr/src/cmd/mdb/common/mdb/mdb_module.h usr/src/cmd/mdb/common/mdb/mdb_module_load.c usr/src/cmd/mdb/common/mdb/mdb_print.c usr/src/cmd/mdb/common/mdb/mdb_print.h usr/src/cmd/mdb/common/mdb/mdb_tab.c usr/src/cmd/mdb/common/mdb/mdb_tab.h usr/src/cmd/mdb/common/mdb/mdb_termio.c usr/src/cmd/mdb/common/mdb/mdb_whatis.c
diffstat 15 files changed, 1201 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/mdb/Makefile.kmdb.files	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/Makefile.kmdb.files	Fri May 11 22:38:13 2012 -0400
@@ -23,6 +23,11 @@
 # Use is subject to license terms.
 #
 
+#
+# Copyright (c) 2012 by Delphix. All rights reserved.
+# Copyright (c) 2012 Joyent, Inc. All rights reserved.
+#
+
 KMDBSRCS += \
 	ffs.c \
 	kaif_start.c \
@@ -75,6 +80,7 @@
 	mdb_string.c \
 	mdb_strio.c \
 	kmdb_stubs.c \
+	mdb_tab.c \
 	mdb_target.c \
 	kmdb_terminfo.c \
 	mdb_termio.c \
--- a/usr/src/cmd/mdb/Makefile.mdb	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/Makefile.mdb	Fri May 11 22:38:13 2012 -0400
@@ -19,11 +19,16 @@
 # CDDL HEADER END
 #
 #
-# Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 
+#
+# Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
+# Copyright (c) 2012 by Delphix. All rights reserved.
+# Copyright (c) 2012 Joyent, Inc. All rights reserved.
+#
+
 .KEEP_STATE:
 .SUFFIXES:
 
@@ -76,6 +81,7 @@
 	mdb_stdlib.c \
 	mdb_string.c \
 	mdb_strio.c \
+	mdb_tab.c \
 	mdb_target.c \
 	mdb_tdb.c \
 	mdb_termio.c \
--- a/usr/src/cmd/mdb/common/mdb/mdb.c	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb.c	Fri May 11 22:38:13 2012 -0400
@@ -24,6 +24,11 @@
  */
 
 /*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+
+/*
  * Modular Debugger (MDB)
  *
  * Refer to the white paper "A Modular Debugger for Solaris" for information
@@ -1132,6 +1137,16 @@
 	return (status);
 }
 
+void
+mdb_call_tab(mdb_idcmd_t *idcp, mdb_tab_cookie_t *mcp, uint_t flags,
+    uintmax_t argc, mdb_arg_t *argv)
+{
+	if (idcp->idc_tabp == NULL)
+		return;
+
+	idcp->idc_tabp(mcp, flags, argc, argv);
+}
+
 /*
  * Call an internal dcmd directly: this code is used by module API functions
  * that need to execute dcmds, and by mdb_call() above.
--- a/usr/src/cmd/mdb/common/mdb/mdb.h	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb.h	Fri May 11 22:38:13 2012 -0400
@@ -23,6 +23,11 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+
 #ifndef	_MDB_H
 #define	_MDB_H
 
@@ -38,6 +43,7 @@
 #include <mdb/mdb_modapi.h>
 #include <mdb/mdb_list.h>
 #include <mdb/mdb_vcb.h>
+#include <mdb/mdb_tab.h>
 #ifdef _KMDB
 #include <kmdb/kmdb_wr.h>
 #endif
@@ -206,6 +212,8 @@
 
 extern int mdb_call_idcmd(mdb_idcmd_t *, uintmax_t, uintmax_t, uint_t,
     mdb_argvec_t *, mdb_addrvec_t *, mdb_vcb_t *);
+extern void mdb_call_tab(mdb_idcmd_t *, mdb_tab_cookie_t *, uint_t, uintmax_t,
+    mdb_arg_t *);
 
 extern int mdb_call(uintmax_t, uintmax_t, uint_t);
 extern int mdb_run(void);
--- a/usr/src/cmd/mdb/common/mdb/mdb_cmds.c	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb_cmds.c	Fri May 11 22:38:13 2012 -0400
@@ -24,6 +24,11 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+
 #include <sys/elf.h>
 #include <sys/elf_SPARC.h>
 
@@ -61,6 +66,7 @@
 #include <mdb/mdb_whatis.h>
 #include <mdb/mdb_whatis_impl.h>
 #include <mdb/mdb_macalias.h>
+#include <mdb/mdb_tab.h>
 #ifdef _KMDB
 #include <kmdb/kmdb_kdi.h>
 #endif
@@ -2129,6 +2135,24 @@
 	return (DCMD_OK);
 }
 
+static int
+cmd_walk_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc,
+    const mdb_arg_t *argv)
+{
+	if (argc > 1)
+		return (1);
+
+	if (argc == 1) {
+		ASSERT(argv[0].a_type == MDB_TYPE_STRING);
+		return (mdb_tab_complete_walker(mcp, argv[0].a_un.a_str));
+	}
+
+	if (argc == 0 && flags & DCMD_TAB_SPACE)
+		return (mdb_tab_complete_walker(mcp, NULL));
+
+	return (1);
+}
+
 static ssize_t
 mdb_partial_xread(void *buf, size_t nbytes, uintptr_t addr, void *arg)
 {
@@ -2924,7 +2948,8 @@
 	    head_help },
 	{ "help", "[cmd]", "list commands/command help", cmd_help },
 	{ "list", "?type member [variable]",
-	    "walk list using member as link pointer", cmd_list },
+	    "walk list using member as link pointer", cmd_list, NULL,
+	    mdb_tab_complete_mt },
 	{ "map", "?expr", "print dot after evaluating expression", cmd_map },
 	{ "mappings", "?[name]", "print address space mappings", cmd_mappings },
 	{ "nm", "?[-DPdghnopuvx] [-f format] [-t types] [object]",
@@ -2935,14 +2960,16 @@
 	{ "obey", NULL, NULL, cmd_obey },
 	{ "objects", "[-v]", "print load objects information", cmd_objects },
 	{ "offsetof", "type member", "print the offset of a given struct "
-	    "or union member", cmd_offsetof },
+	    "or union member", cmd_offsetof, NULL, mdb_tab_complete_mt },
 	{ "print", "?[-aCdhiLptx] [-c lim] [-l lim] [type] [member|offset ...]",
-	    "print the contents of a data structure", cmd_print, print_help },
+	    "print the contents of a data structure", cmd_print, print_help,
+	    cmd_print_tab },
 	{ "regs", NULL, "print general purpose registers", cmd_notsup },
 	{ "set", "[-wF] [+/-o opt] [-s dist] [-I path] [-L path] [-P prompt]",
 	    "get/set debugger properties", cmd_set },
 	{ "showrev", "[-pv]", "print version information", cmd_showrev },
-	{ "sizeof", "type", "print the size of a type", cmd_sizeof },
+	{ "sizeof", "type", "print the size of a type", cmd_sizeof, NULL,
+	    cmd_sizeof_tab },
 	{ "stack", "?[cnt]", "print stack backtrace", cmd_notsup },
 	{ "stackregs", "?", "print stack backtrace and registers",
 	    cmd_notsup },
@@ -2954,7 +2981,8 @@
 	{ "version", NULL, "print debugger version string", cmd_version },
 	{ "vtop", ":[-a as]", "print physical mapping of virtual address",
 	    cmd_vtop },
-	{ "walk", "?name [variable]", "walk data structure", cmd_walk },
+	{ "walk", "?name [variable]", "walk data structure", cmd_walk, NULL,
+	    cmd_walk_tab },
 	{ "walkers", NULL, "list available walkers", cmd_walkers },
 	{ "whatis", ":[-aikqv]", "given an address, return information",
 	    cmd_whatis, whatis_help },
--- a/usr/src/cmd/mdb/common/mdb/mdb_modapi.h	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb_modapi.h	Fri May 11 22:38:13 2012 -0400
@@ -21,6 +21,8 @@
 
 /*
  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
  */
 
 #ifndef	_MDB_MODAPI_H
@@ -69,7 +71,7 @@
 #define	MAX(x, y) ((x) > (y) ? (x) : (y))
 #endif
 
-#define	MDB_API_VERSION	3	/* Current API version number */
+#define	MDB_API_VERSION	4	/* Current API version number */
 
 /*
  * Debugger command function flags:
@@ -83,6 +85,11 @@
 #define	DCMD_HDRSPEC(fl)	(((fl) & DCMD_LOOPFIRST) || !((fl) & DCMD_LOOP))
 
 /*
+ * Debugger tab command function flags
+ */
+#define	DCMD_TAB_SPACE	0x01	/* Tab cb invoked with trailing space */
+
+/*
  * Debugger command function return values:
  */
 #define	DCMD_OK		0	/* Dcmd completed successfully */
@@ -111,7 +118,10 @@
 	} a_un;
 } mdb_arg_t;
 
+typedef struct mdb_tab_cookie mdb_tab_cookie_t;
 typedef int mdb_dcmd_f(uintptr_t, uint_t, int, const mdb_arg_t *);
+typedef int mdb_dcmd_tab_f(mdb_tab_cookie_t *, uint_t, int,
+    const mdb_arg_t *);
 
 typedef struct mdb_dcmd {
 	const char *dc_name;		/* Command name */
@@ -119,6 +129,7 @@
 	const char *dc_descr;		/* Description */
 	mdb_dcmd_f *dc_funcp;		/* Command function */
 	void (*dc_help)(void);		/* Command help function (or NULL) */
+	mdb_dcmd_tab_f *dc_tabp;	/* Tab completion function */
 } mdb_dcmd_t;
 
 #define	WALK_ERR	-1		/* Walk fatal error (terminate walk) */
@@ -302,6 +313,31 @@
 extern void *mdb_callback_add(int, mdb_callback_f, void *);
 extern void mdb_callback_remove(void *);
 
+#define	MDB_TABC_ALL_TYPES	0x1	/* Include array types in type output */
+#define	MDB_TABC_MEMBERS	0x2	/* Tab comp. types with members */
+#define	MDB_TABC_NOPOINT	0x4	/* Tab comp. everything but pointers */
+#define	MDB_TABC_NOARRAY	0x8	/* Don't include array data in output */
+
+/*
+ * Module's interaction path
+ */
+extern void mdb_tab_insert(mdb_tab_cookie_t *, const char *);
+extern void mdb_tab_setmbase(mdb_tab_cookie_t *, const char *);
+
+/*
+ * Tab completion utility functions for modules.
+ */
+extern int mdb_tab_complete_type(mdb_tab_cookie_t *, const char *, uint_t);
+extern int mdb_tab_complete_member(mdb_tab_cookie_t *, const char *,
+    const char *);
+extern int mdb_tab_typename(int *, const mdb_arg_t **, char *buf, size_t len);
+
+/*
+ * Tab completion functions for common signatures.
+ */
+extern int mdb_tab_complete_mt(mdb_tab_cookie_t *, uint_t, int,
+    const mdb_arg_t *);
+
 extern size_t strlcat(char *, const char *, size_t);
 extern char *strcat(char *, const char *);
 extern char *strcpy(char *, const char *);
--- a/usr/src/cmd/mdb/common/mdb/mdb_module.c	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb_module.c	Fri May 11 22:38:13 2012 -0400
@@ -21,6 +21,8 @@
 /*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
  */
 
 #include <sys/param.h>
@@ -43,6 +45,20 @@
 #include <mdb/mdb.h>
 
 /*
+ * The format of an mdb dcmd changed between MDB_API_VERSION 3 and 4, with an
+ * addition of a new field to the public interface. To maintain backwards
+ * compatibility with older versions, we know to keep around the old version of
+ * the structure so we can correctly read the set of dcmds passed in.
+ */
+typedef struct mdb_dcmd_v3 {
+	const char *dco_name;		/* Command name */
+	const char *dco_usage;		/* Usage message (optional) */
+	const char *dco_descr;		/* Description */
+	mdb_dcmd_f *dco_funcp;		/* Command function */
+	void (*dco_help)(void);		/* Command help function (or NULL) */
+} mdb_dcmd_v3_t;
+
+/*
  * For builtin modules, we set mod_init to this function, which just
  * returns a constant modinfo struct with no dcmds and walkers.
  */
@@ -92,6 +108,9 @@
 	const mdb_dcmd_t *dcp;
 	const mdb_walker_t *wp;
 
+	const mdb_dcmd_v3_t *dcop;
+	mdb_dcmd_t *dctp = NULL;
+
 	mdb_module_t *mod;
 
 	mod = mdb_zalloc(sizeof (mdb_module_t), UM_SLEEP);
@@ -164,6 +183,7 @@
 	 */
 	switch (info->mi_dvers) {
 	case MDB_API_VERSION:
+	case 3:
 	case 2:
 	case 1:
 		/*
@@ -186,6 +206,35 @@
 	}
 
 	/*
+	 * In MDB_API_VERSION 4, the size of the mdb_dcmd_t struct changed. If
+	 * our module is from an earlier version, we need to walk it in the old
+	 * structure and convert it to the new one.
+	 *
+	 * Note that we purposefully don't predicate on whether or not we have
+	 * the empty list case and duplicate it anyways. That case is rare and
+	 * it makes our logic simpler when we need to unload the module.
+	 */
+	if (info->mi_dvers < 4) {
+		int ii = 0;
+		for (dcop = (mdb_dcmd_v3_t *)&mod->mod_info->mi_dcmds[0];
+		    dcop->dco_name != NULL; dcop++)
+			ii++;
+		/* Don't forget null terminated one at the end */
+		dctp = mdb_zalloc(sizeof (mdb_dcmd_t) * (ii + 1), UM_SLEEP);
+		ii = 0;
+		for (dcop = (mdb_dcmd_v3_t *)&mod->mod_info->mi_dcmds[0];
+		    dcop->dco_name != NULL; dcop++, ii++) {
+			dctp[ii].dc_name = dcop->dco_name;
+			dctp[ii].dc_usage = dcop->dco_usage;
+			dctp[ii].dc_descr = dcop->dco_descr;
+			dctp[ii].dc_funcp = dcop->dco_funcp;
+			dctp[ii].dc_help = dcop->dco_help;
+			dctp[ii].dc_tabp = NULL;
+		}
+		mod->mod_info->mi_dcmds = dctp;
+	}
+
+	/*
 	 * Before we actually go ahead with the load, we need to check
 	 * each dcmd and walk structure for any invalid values:
 	 */
@@ -300,6 +349,7 @@
 {
 	mdb_var_t *v = mdb_nv_lookup(&mdb.m_modules, name);
 	mdb_module_t *mod;
+	const mdb_dcmd_t *dcp;
 
 	if (v == NULL)
 		return (set_errno(EMDB_NOMOD));
@@ -358,6 +408,18 @@
 	mdb_nv_destroy(&mod->mod_dcmds);
 
 	strfree((char *)mod->mod_name);
+
+	if (mod->mod_info->mi_dvers < 4) {
+		int ii = 0;
+
+		for (dcp = &mod->mod_info->mi_dcmds[0]; dcp->dc_name != NULL;
+		    dcp++)
+			ii++;
+
+		mdb_free((void *)mod->mod_info->mi_dcmds,
+		    sizeof (mdb_dcmd_t) * (ii + 1));
+	}
+
 	mdb_free(mod->mod_info, sizeof (mdb_modinfo_t));
 	mdb_free(mod, sizeof (mdb_module_t));
 
@@ -384,6 +446,7 @@
 	idcp->idc_descr = dcp->dc_descr;
 	idcp->idc_help = dcp->dc_help;
 	idcp->idc_funcp = dcp->dc_funcp;
+	idcp->idc_tabp = dcp->dc_tabp;
 	idcp->idc_modp = mod;
 
 	v = mdb_nv_insert(&mod->mod_dcmds, dcp->dc_name, NULL,
--- a/usr/src/cmd/mdb/common/mdb/mdb_module.h	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb_module.h	Fri May 11 22:38:13 2012 -0400
@@ -24,11 +24,14 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+
 #ifndef	_MDB_MODULE_H
 #define	_MDB_MODULE_H
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include <mdb/mdb_argvec.h>
 #include <mdb/mdb_nv.h>
 #include <mdb/mdb_modapi.h>
@@ -67,6 +70,7 @@
 	const char *idc_descr;		/* Description */
 	mdb_dcmd_f *idc_funcp;		/* Command function */
 	void (*idc_help)(void);		/* Help function */
+	mdb_dcmd_tab_f *idc_tabp;	/* Tab completion pointer */
 	mdb_module_t *idc_modp;		/* Backpointer to module */
 	mdb_var_t *idc_var;		/* Backpointer to global variable */
 } mdb_idcmd_t;
--- a/usr/src/cmd/mdb/common/mdb/mdb_module_load.c	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb_module_load.c	Fri May 11 22:38:13 2012 -0400
@@ -21,6 +21,8 @@
 
 /*
  * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
  */
 
 #include <sys/param.h>
@@ -201,6 +203,7 @@
 	mdb_iob_setflags(mdb.m_out, oflag);
 }
 
+/*ARGSUSED*/
 int
 mdb_module_unload(const char *name, int mode)
 {
--- a/usr/src/cmd/mdb/common/mdb/mdb_print.c	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb_print.c	Fri May 11 22:38:13 2012 -0400
@@ -23,6 +23,11 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+
 #include <mdb/mdb_modapi.h>
 #include <mdb/mdb_target.h>
 #include <mdb/mdb_argvec.h>
@@ -34,6 +39,7 @@
 #include <mdb/mdb_ctf.h>
 #include <mdb/mdb_ctf_impl.h>
 #include <mdb/mdb.h>
+#include <mdb/mdb_tab.h>
 
 #include <sys/isa_defs.h>
 #include <sys/param.h>
@@ -209,6 +215,28 @@
 	return (DCMD_OK);
 }
 
+int
+cmd_sizeof_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc,
+    const mdb_arg_t *argv)
+{
+	char tn[MDB_SYM_NAMLEN];
+	int ret;
+
+	if (argc == 0 && !(flags & DCMD_TAB_SPACE))
+		return (0);
+
+	if (argc == 0 && (flags & DCMD_TAB_SPACE))
+		return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_NOPOINT));
+
+	if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0)
+		return (ret);
+
+	if (argc == 1)
+		return (mdb_tab_complete_type(mcp, tn, MDB_TABC_NOPOINT));
+
+	return (0);
+}
+
 /*ARGSUSED*/
 int
 cmd_offsetof(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
@@ -1882,7 +1910,8 @@
 				return (-1);
 			}
 
-			(void) mdb_snprintf(member, end - start + 1, start);
+			(void) mdb_snprintf(member, end - start + 1, "%s",
+			    start);
 
 			index = mdb_strtoull(member);
 
@@ -1959,7 +1988,7 @@
 		for (end = start + 1; isalnum(*end) || *end == '_'; end++)
 			continue;
 
-		(void) mdb_snprintf(member, end - start + 1, start);
+		(void) mdb_snprintf(member, end - start + 1, "%s", start);
 
 		if (mdb_ctf_member_info(rid, member, &off, &id) != 0) {
 			mdb_warn("failed to find member %s of %s", member,
@@ -1981,6 +2010,170 @@
 	return (0);
 }
 
+int
+cmd_print_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc,
+    const mdb_arg_t *argv)
+{
+	char tn[MDB_SYM_NAMLEN];
+	char member[64];
+	int i, dummy, delim, kind;
+	int ret = 0;
+	mdb_ctf_id_t id, rid;
+	mdb_ctf_arinfo_t ar;
+	char *start, *end;
+	ulong_t dul;
+
+	/*
+	 * This getopts is only here to make the tab completion work better when
+	 * including options in the ::print arguments. None of the values should
+	 * be used. This should only be updated with additional arguments, if
+	 * they are added to cmd_print.
+	 */
+	i = mdb_getopts(argc, argv,
+	    'a', MDB_OPT_SETBITS, PA_SHOWADDR, &dummy,
+	    'C', MDB_OPT_SETBITS, TRUE, &dummy,
+	    'c', MDB_OPT_UINTPTR, &dummy,
+	    'd', MDB_OPT_SETBITS, PA_INTDEC, &dummy,
+	    'h', MDB_OPT_SETBITS, PA_SHOWHOLES, &dummy,
+	    'i', MDB_OPT_SETBITS, TRUE, &dummy,
+	    'L', MDB_OPT_SETBITS, TRUE, &dummy,
+	    'l', MDB_OPT_UINTPTR, &dummy,
+	    'n', MDB_OPT_SETBITS, PA_NOSYMBOLIC, &dummy,
+	    'p', MDB_OPT_SETBITS, TRUE, &dummy,
+	    's', MDB_OPT_UINTPTR, &dummy,
+	    'T', MDB_OPT_SETBITS, PA_SHOWTYPE | PA_SHOWBASETYPE, &dummy,
+	    't', MDB_OPT_SETBITS, PA_SHOWTYPE, &dummy,
+	    'x', MDB_OPT_SETBITS, PA_INTHEX, &dummy,
+	    NULL);
+
+	argc -= i;
+	argv += i;
+
+	if (argc == 0 && !(flags & DCMD_TAB_SPACE))
+		return (0);
+
+	if (argc == 0 && (flags & DCMD_TAB_SPACE))
+		return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_NOPOINT |
+		    MDB_TABC_NOARRAY));
+
+	if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0)
+		return (ret);
+
+	if (argc == 1 && (!(flags & DCMD_TAB_SPACE) || ret == 1))
+		return (mdb_tab_complete_type(mcp, tn, MDB_TABC_NOPOINT |
+		    MDB_TABC_NOARRAY));
+
+	if (argc == 1 && (flags & DCMD_TAB_SPACE))
+		return (mdb_tab_complete_member(mcp, tn, NULL));
+
+	/*
+	 * This is the reason that tab completion was created. We're going to go
+	 * along and walk the delimiters until we find something a member that
+	 * we don't recognize, at which point we'll try and tab complete it.
+	 * Note that ::print takes multiple args, so this is going to operate on
+	 * whatever the last arg that we have is.
+	 */
+	if (mdb_ctf_lookup_by_name(tn, &id) != 0)
+		return (1);
+
+	(void) mdb_ctf_type_resolve(id, &rid);
+	start = (char *)argv[argc-1].a_un.a_str;
+	delim = parse_delimiter(&start);
+
+	/*
+	 * If we hit the case where we actually have no delimiters, than we need
+	 * to make sure that we properly set up the fields the loops would.
+	 */
+	if (delim == MEMBER_DELIM_DONE)
+		(void) mdb_snprintf(member, sizeof (member), "%s", start);
+
+	while (delim != MEMBER_DELIM_DONE) {
+		switch (delim) {
+		case MEMBER_DELIM_PTR:
+			kind = mdb_ctf_type_kind(rid);
+			if (kind != CTF_K_POINTER)
+				return (1);
+
+			(void) mdb_ctf_type_reference(rid, &id);
+			(void) mdb_ctf_type_resolve(id, &rid);
+			break;
+		case MEMBER_DELIM_DOT:
+			kind = mdb_ctf_type_kind(rid);
+			if (kind != CTF_K_STRUCT && kind != CTF_K_UNION)
+				return (1);
+			break;
+		case MEMBER_DELIM_LBR:
+			end = strchr(start, ']');
+			/*
+			 * We're not going to try and tab complete the indexes
+			 * here. So for now, punt on it. Also, we're not going
+			 * to try and validate you're within the bounds, just
+			 * that you get the type you asked for.
+			 */
+			if (end == NULL)
+				return (1);
+
+			switch (mdb_ctf_type_kind(rid)) {
+			case CTF_K_POINTER:
+				(void) mdb_ctf_type_reference(rid, &id);
+				(void) mdb_ctf_type_resolve(id, &rid);
+				break;
+			case CTF_K_ARRAY:
+				(void) mdb_ctf_array_info(rid, &ar);
+				id = ar.mta_contents;
+				(void) mdb_ctf_type_resolve(id, &rid);
+				break;
+			default:
+				return (1);
+			}
+
+			start = end + 1;
+			delim = parse_delimiter(&start);
+			break;
+		case MEMBER_DELIM_ERR:
+		default:
+			break;
+		}
+
+		for (end = start + 1; isalnum(*end) || *end == '_'; end++)
+			continue;
+
+		(void) mdb_snprintf(member, end - start + 1, start);
+
+		/*
+		 * We are going to try to resolve this name as a member. There
+		 * are a few two different questions that we need to answer. The
+		 * first is do we recognize this member. The second is are we at
+		 * the end of the string. If we encounter a member that we don't
+		 * recognize before the end, then we have to error out and can't
+		 * complete it. But if there are no more delimiters then we can
+		 * try and complete it.
+		 */
+		ret = mdb_ctf_member_info(rid, member, &dul, &id);
+		start = end;
+		delim = parse_delimiter(&start);
+		if (ret != 0 && errno == EMDB_CTFNOMEMB) {
+			if (delim != MEMBER_DELIM_DONE)
+				return (1);
+			continue;
+		} else if (ret != 0)
+			return (1);
+
+		if (delim == MEMBER_DELIM_DONE)
+			return (mdb_tab_complete_member_by_id(mcp, rid,
+			    member));
+
+		(void) mdb_ctf_type_resolve(id, &rid);
+	}
+
+	/*
+	 * If we've reached here, then we need to try and tab complete the last
+	 * field, which is currently member, based on the ctf type id that we
+	 * already have in rid.
+	 */
+	return (mdb_tab_complete_member_by_id(mcp, rid, member));
+}
+
 /*
  * Recursively descend a print a given data structure.  We create a struct of
  * the relevant print arguments and then call mdb_ctf_type_visit() to do the
@@ -2005,6 +2198,10 @@
 	mdb_syminfo_t s_info;
 	GElf_Sym sym;
 
+	/*
+	 * If a new option is added, make sure the getopts above in
+	 * cmd_print_tab is also updated.
+	 */
 	i = mdb_getopts(argc, argv,
 	    'a', MDB_OPT_SETBITS, PA_SHOWADDR, &uflags,
 	    'C', MDB_OPT_SETBITS, TRUE, &opt_C,
--- a/usr/src/cmd/mdb/common/mdb/mdb_print.h	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb_print.h	Fri May 11 22:38:13 2012 -0400
@@ -23,9 +23,16 @@
  * Use is subject to license terms.
  */
 
+/*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+
 #ifndef	_MDB_PRINT_H
 #define	_MDB_PRINT_H
 
+#include <mdb/mdb_tab.h>
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -35,10 +42,12 @@
 extern int cmd_enum(uintptr_t, uint_t, int, const mdb_arg_t *);
 extern void enum_help(void);
 extern int cmd_sizeof(uintptr_t, uint_t, int, const mdb_arg_t *);
+extern int cmd_sizeof_tab(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *);
 extern int cmd_offsetof(uintptr_t, uint_t, int, const mdb_arg_t *);
 extern int cmd_list(uintptr_t, uint_t, int, const mdb_arg_t *);
 extern int cmd_array(uintptr_t, uint_t, int, const mdb_arg_t *);
 extern int cmd_print(uintptr_t, uint_t, int, const mdb_arg_t *);
+extern int cmd_print_tab(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *);
 extern void print_help(void);
 
 #endif	/* _MDB */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/mdb/common/mdb/mdb_tab.c	Fri May 11 22:38:13 2012 -0400
@@ -0,0 +1,652 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+/*
+ * This file contains all of the interfaces for mdb's tab completion engine.
+ * Currently some interfaces are private to mdb and its internal implementation,
+ * those are in mdb_tab.h. Other pieces are public interfaces. Those are in
+ * mdb_modapi.h.
+ *
+ * Memory allocations in tab completion context have to be done very carefully.
+ * We need to think of ourselves as the same as any other command that is being
+ * executed by the user, which means we must use UM_GC to handle being
+ * interrupted.
+ */
+
+#include <mdb/mdb_modapi.h>
+#include <mdb/mdb_ctf.h>
+#include <mdb/mdb_ctf_impl.h>
+#include <mdb/mdb_string.h>
+#include <mdb/mdb_module.h>
+#include <mdb/mdb_debug.h>
+#include <mdb/mdb_print.h>
+#include <mdb/mdb_nv.h>
+#include <mdb/mdb_tab.h>
+#include <mdb/mdb.h>
+
+#include <ctype.h>
+
+/*
+ * There may be another way to do this, but this works well enough.
+ */
+#define	COMMAND_SEPARATOR "::"
+
+/*
+ * find_command_start --
+ *
+ * 	Given a buffer find the start of the last command.
+ */
+static char *
+tab_find_command_start(char *buf)
+{
+	char *offset = strstr(buf, COMMAND_SEPARATOR);
+
+	if (offset == NULL)
+		return (NULL);
+
+	for (;;) {
+		char *next = strstr(offset + strlen(COMMAND_SEPARATOR),
+		    COMMAND_SEPARATOR);
+
+		if (next == NULL) {
+			return (offset);
+		}
+
+		offset = next;
+	}
+}
+
+/*
+ * get_dcmd --
+ *
+ * 	Given a buffer containing a command and its argument return
+ * 	the name of the command and the offset in the buffer where
+ * 	the command arguments start.
+ *
+ * 	Note: This will modify the buffer.
+ */
+char *
+tab_get_dcmd(char *buf, char **args, uint_t *flags)
+{
+	char *start = buf + strlen(COMMAND_SEPARATOR);
+	char *separator = start;
+	const char *end = buf + strlen(buf);
+	uint_t space = 0;
+
+	while (separator < end && !isspace(*separator))
+		separator++;
+
+	if (separator == end) {
+		*args = NULL;
+	} else {
+		if (isspace(*separator))
+			space = 1;
+
+		*separator++ = '\0';
+		*args = separator;
+	}
+
+	if (space)
+		*flags |= DCMD_TAB_SPACE;
+
+	return (start);
+}
+
+/*
+ * count_args --
+ *
+ * 	Given a buffer containing dmcd arguments return the total number
+ * 	of arguments.
+ *
+ * 	While parsing arguments we need to keep track of whether or not the last
+ * 	arguments ends with a trailing space.
+ */
+static int
+tab_count_args(const char *input, uint_t *flags)
+{
+	const char *index;
+	int argc = 0;
+	uint_t space = *flags & DCMD_TAB_SPACE;
+	index = input;
+
+	while (*index != '\0') {
+		while (*index != '\0' && isspace(*index)) {
+			index++;
+			space = 1;
+		}
+
+		if (*index != '\0' && !isspace(*index)) {
+			argc++;
+			space = 0;
+			while (*index != '\0' && !isspace (*index)) {
+				index++;
+			}
+		}
+	}
+
+	if (space)
+		*flags |= DCMD_TAB_SPACE;
+	else
+		*flags &= ~DCMD_TAB_SPACE;
+
+	return (argc);
+}
+
+/*
+ * copy_args --
+ *
+ * 	Given a buffer containing dcmd arguments and an array of mdb_arg_t's
+ * 	initialize the string value of each mdb_arg_t.
+ *
+ * 	Note: This will modify the buffer.
+ */
+static int
+tab_copy_args(char *input, int argc, mdb_arg_t *argv)
+{
+	int i = 0;
+	char *index;
+
+	index = input;
+
+	while (*index) {
+		while (*index && isspace(*index)) {
+			index++;
+		}
+
+		if (*index && !isspace(*index)) {
+			char *end = index;
+
+			while (*end && !isspace(*end)) {
+				end++;
+			}
+
+			if (*end) {
+				*end++ = '\0';
+			}
+
+			argv[i].a_type = MDB_TYPE_STRING;
+			argv[i].a_un.a_str = index;
+
+			index = end;
+			i++;
+		}
+	}
+
+	if (i != argc)
+		return (-1);
+
+	return (0);
+}
+
+/*
+ * parse-buf --
+ *
+ * 	Parse the given buffer and return the specified dcmd, the number
+ * 	of arguments, and array of mdb_arg_t containing the argument
+ * 	values.
+ *
+ * 	Note: this will modify the specified buffer. Caller is responisble
+ * 	for freeing argvp.
+ */
+static int
+tab_parse_buf(char *buf, char **dcmdp, int *argcp, mdb_arg_t **argvp,
+    uint_t *flags)
+{
+	char *data = tab_find_command_start(buf);
+	char *args_data = NULL;
+	char *dcmd = NULL;
+	int argc = 0;
+	mdb_arg_t *argv = NULL;
+
+	if (data == NULL) {
+		return (-1);
+	}
+
+	dcmd = tab_get_dcmd(data, &args_data, flags);
+
+	if (dcmd == NULL) {
+		return (-1);
+	}
+
+	if (args_data != NULL) {
+		argc = tab_count_args(args_data, flags);
+
+		if (argc != 0) {
+			argv = mdb_alloc(sizeof (mdb_arg_t) * argc,
+			    UM_SLEEP | UM_GC);
+
+			if (tab_copy_args(args_data, argc, argv) == -1)
+				return (-1);
+		}
+	}
+
+	*dcmdp = dcmd;
+	*argcp = argc;
+	*argvp = argv;
+
+	return (0);
+}
+
+/*
+ * tab_command --
+ *
+ * 	This function is executed anytime a tab is entered. It checks
+ * 	the current buffer to determine if there is a valid dmcd,
+ * 	if that dcmd has a tab completion handler it will invoke it.
+ *
+ *	This function returns the string (if any) that should be added to the
+ *	existing buffer to complete it.
+ */
+int
+mdb_tab_command(mdb_tab_cookie_t *mcp, const char *buf)
+{
+	char *data;
+	char *dcmd = NULL;
+	int argc = 0;
+	mdb_arg_t *argv = NULL;
+	int ret = 0;
+	mdb_idcmd_t *cp;
+	uint_t flags = 0;
+
+	/*
+	 * Parsing the command and arguments will modify the buffer
+	 * (replacing spaces with \0), so make a copy of the specified
+	 * buffer first.
+	 */
+	data = mdb_alloc(strlen(buf) + 1, UM_SLEEP | UM_GC);
+	(void) strcpy(data, buf);
+
+	/*
+	 * Get the specified dcmd and arguments from the buffer.
+	 */
+	ret = tab_parse_buf(data, &dcmd, &argc, &argv, &flags);
+
+	if (ret != 0) {
+		goto out;
+	}
+
+	/*
+	 * Check to see if the buffer contains a valid dcmd
+	 */
+	cp = mdb_dcmd_lookup(dcmd);
+
+	/*
+	 * When argc is zero it indicates that we are trying to tab complete
+	 * a dcmd. Note, that if there isn't the start of a dcmd, i.e. ::, then
+	 * we will have already bailed in the call to tab_parse_buf.
+	 */
+	if (cp == NULL && argc != 0) {
+		goto out;
+	}
+
+	/*
+	 * Invoke the command specific tab completion handler or the built in
+	 * dcmd one if there is no dcmd.
+	 */
+	if (cp == NULL)
+		(void) mdb_tab_complete_dcmd(mcp, dcmd);
+	else
+		mdb_call_tab(cp, mcp, flags, argc, argv);
+
+out:
+	return (mdb_tab_size(mcp));
+}
+
+static int
+tab_complete_dcmd(mdb_var_t *v, void *arg)
+{
+	mdb_idcmd_t *idcp = mdb_nv_get_cookie(mdb_nv_get_cookie(v));
+	mdb_tab_cookie_t *mcp = (mdb_tab_cookie_t *)arg;
+
+	/*
+	 * The way that mdb is implemented, even commands like $C will show up
+	 * here. As such, we don't want to match anything that doesn't start
+	 * with an alpha or number. While nothing currently appears (via a
+	 * cursory search with mdb -k) to start with a capital letter or a
+	 * number, we'll support them anyways.
+	 */
+	if (!isalnum(idcp->idc_name[0]))
+		return (0);
+
+	mdb_tab_insert(mcp, idcp->idc_name);
+	return (0);
+}
+
+int
+mdb_tab_complete_dcmd(mdb_tab_cookie_t *mcp, const char *dcmd)
+{
+	mdb_tab_setmbase(mcp, dcmd);
+	mdb_nv_sort_iter(&mdb.m_dcmds, tab_complete_dcmd, mcp,
+	    UM_GC | UM_SLEEP);
+	return (0);
+}
+
+static int
+tab_complete_walker(mdb_var_t *v, void *arg)
+{
+	mdb_iwalker_t *iwp = mdb_nv_get_cookie(mdb_nv_get_cookie(v));
+	mdb_tab_cookie_t *mcp = arg;
+
+	mdb_tab_insert(mcp, iwp->iwlk_name);
+	return (0);
+}
+
+int
+mdb_tab_complete_walker(mdb_tab_cookie_t *mcp, const char *walker)
+{
+	if (walker != NULL)
+		mdb_tab_setmbase(mcp, walker);
+	mdb_nv_sort_iter(&mdb.m_walkers, tab_complete_walker, mcp,
+	    UM_GC | UM_SLEEP);
+
+	return (0);
+}
+
+mdb_tab_cookie_t *
+mdb_tab_init(void)
+{
+	mdb_tab_cookie_t *mcp;
+
+	mcp = mdb_zalloc(sizeof (mdb_tab_cookie_t), UM_SLEEP | UM_GC);
+	(void) mdb_nv_create(&mcp->mtc_nv, UM_SLEEP | UM_GC);
+
+	return (mcp);
+}
+
+size_t
+mdb_tab_size(mdb_tab_cookie_t *mcp)
+{
+	return (mdb_nv_size(&mcp->mtc_nv));
+}
+
+/*
+ * Determine whether the specified name is a valid tab completion for
+ * the given command. If the name is a valid tab completion then
+ * it will be saved in the mdb_tab_cookie_t.
+ */
+void
+mdb_tab_insert(mdb_tab_cookie_t *mcp, const char *name)
+{
+	size_t len, matches, index;
+	uint_t flags;
+	mdb_var_t *v;
+	char *n;
+	const char *nvn;
+
+	/*
+	 * If we have a match set, then we want to verify that we actually match
+	 * it.
+	 */
+	if (mcp->mtc_base != NULL &&
+	    strncmp(name, mcp->mtc_base, strlen(mcp->mtc_base)) != 0)
+		return;
+
+	v = mdb_nv_lookup(&mcp->mtc_nv, name);
+	if (v != NULL)
+		return;
+
+	/*
+	 * Names that we get passed in may be longer than MDB_NV_NAMELEN which
+	 * is currently 31 including the null terminator. If that is the case,
+	 * then we're going to take care of allocating a string and holding it
+	 * for our caller. Note that we don't need to free it, because we're
+	 * allocating this with UM_GC.
+	 */
+	flags = 0;
+	len = strlen(name);
+	if (len > MDB_NV_NAMELEN - 1) {
+		n = mdb_alloc(len + 1, UM_SLEEP | UM_GC);
+		(void) strcpy(n, name);
+		nvn = n;
+		flags |= MDB_NV_EXTNAME;
+	} else {
+		nvn = name;
+	}
+	flags |= MDB_NV_RDONLY;
+
+	(void) mdb_nv_insert(&mcp->mtc_nv, nvn, NULL, 0, flags);
+
+	matches = mdb_tab_size(mcp);
+	if (matches == 1) {
+		(void) strlcpy(mcp->mtc_match, nvn, MDB_SYM_NAMLEN);
+	} else {
+		index = 0;
+		while (mcp->mtc_match[index] &&
+		    mcp->mtc_match[index] == nvn[index])
+			index++;
+
+		mcp->mtc_match[index] = '\0';
+	}
+}
+
+/*ARGSUSED*/
+static int
+tab_print_cb(mdb_var_t *v, void *ignored)
+{
+	mdb_printf("%s\n", mdb_nv_get_name(v));
+	return (0);
+}
+
+void
+mdb_tab_print(mdb_tab_cookie_t *mcp)
+{
+	mdb_nv_sort_iter(&mcp->mtc_nv, tab_print_cb, NULL, UM_SLEEP | UM_GC);
+}
+
+const char *
+mdb_tab_match(mdb_tab_cookie_t *mcp)
+{
+	size_t blen;
+
+	if (mcp->mtc_base == NULL)
+		blen = 0;
+	else
+		blen = strlen(mcp->mtc_base);
+	return (mcp->mtc_match + blen);
+}
+
+void
+mdb_tab_setmbase(mdb_tab_cookie_t *mcp, const char *base)
+{
+	(void) strlcpy(mcp->mtc_base, base, MDB_SYM_NAMLEN);
+}
+
+/*
+ * This function is currently a no-op due to the fact that we have to GC because
+ * we're in command context.
+ */
+/*ARGSUSED*/
+void
+mdb_tab_fini(mdb_tab_cookie_t *mcp)
+{
+}
+
+/*
+ * This function takes a ctf id and determines whether or not the associated
+ * type should be considered as a potential match for the given tab
+ * completion command. We verify that the type itself is valid
+ * for completion given the current context of the command, resolve
+ * its actual name, and then pass it off to mdb_tab_insert to determine
+ * if it's an actual match.
+ */
+static int
+tab_complete_type(mdb_ctf_id_t id, void *arg)
+{
+	int rkind;
+	char buf[MDB_SYM_NAMLEN];
+	mdb_ctf_id_t rid;
+	mdb_tab_cookie_t *mcp = arg;
+	uint_t flags = (uint_t)(uintptr_t)mcp->mtc_cba;
+
+	/*
+	 * CTF data includes types that mdb commands don't understand. Before
+	 * we resolve the actual type prune any entry that is a type we
+	 * don't care about.
+	 */
+	switch (mdb_ctf_type_kind(id)) {
+	case CTF_K_CONST:
+	case CTF_K_RESTRICT:
+	case CTF_K_VOLATILE:
+		return (0);
+	}
+
+	if (mdb_ctf_type_resolve(id, &rid) != 0)
+		return (1);
+
+	rkind = mdb_ctf_type_kind(rid);
+
+	if ((flags & MDB_TABC_MEMBERS) && rkind != CTF_K_STRUCT &&
+	    rkind != CTF_K_UNION)
+		return (0);
+
+	if ((flags & MDB_TABC_NOPOINT) && rkind == CTF_K_POINTER)
+		return (0);
+
+	if ((flags & MDB_TABC_NOARRAY) && rkind == CTF_K_ARRAY)
+		return (0);
+
+	(void) mdb_ctf_type_name(id, buf, sizeof (buf));
+
+	mdb_tab_insert(mcp, buf);
+	return (0);
+}
+
+/*ARGSUSED*/
+static int
+mdb_tab_complete_module(void *data, const mdb_map_t *mp, const char *name)
+{
+	(void) mdb_ctf_type_iter(name, tab_complete_type, data);
+	return (0);
+}
+
+int
+mdb_tab_complete_type(mdb_tab_cookie_t *mcp, const char *name, uint_t flags)
+{
+	mdb_tgt_t *t = mdb.m_target;
+
+	mcp->mtc_cba = (void *)(uintptr_t)flags;
+	if (name != NULL)
+		mdb_tab_setmbase(mcp, name);
+
+	(void) mdb_tgt_object_iter(t, mdb_tab_complete_module, mcp);
+	return (0);
+}
+
+/*ARGSUSED*/
+static int
+tab_complete_member(const char *name, mdb_ctf_id_t id, ulong_t off, void *arg)
+{
+	mdb_tab_cookie_t *mcp = arg;
+	mdb_tab_insert(mcp, name);
+	return (0);
+}
+
+int
+mdb_tab_complete_member_by_id(mdb_tab_cookie_t *mcp, mdb_ctf_id_t id,
+    const char *member)
+{
+	if (member != NULL)
+		mdb_tab_setmbase(mcp, member);
+	(void) mdb_ctf_member_iter(id, tab_complete_member, mcp);
+	return (0);
+}
+
+int
+mdb_tab_complete_member(mdb_tab_cookie_t *mcp, const char *type,
+    const char *member)
+{
+	mdb_ctf_id_t id;
+
+	if (mdb_ctf_lookup_by_name(type, &id) != 0)
+		return (-1);
+
+	return (mdb_tab_complete_member_by_id(mcp, id, member));
+}
+
+int
+mdb_tab_complete_mt(mdb_tab_cookie_t *mcp, uint_t flags, int argc,
+    const mdb_arg_t *argv)
+{
+	char tn[MDB_SYM_NAMLEN];
+	int ret;
+
+	if (argc == 0 && !(flags & DCMD_TAB_SPACE))
+		return (0);
+
+	if (argc == 0)
+		return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_MEMBERS));
+
+	if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0)
+		return (ret);
+
+	if (argc == 1 && (!(flags & DCMD_TAB_SPACE) || ret == 1))
+		return (mdb_tab_complete_type(mcp, tn, MDB_TABC_MEMBERS));
+
+	if (argc == 1 && (flags & DCMD_TAB_SPACE))
+		return (mdb_tab_complete_member(mcp, tn, NULL));
+
+	if (argc == 2)
+		return (mdb_tab_complete_member(mcp, tn, argv[1].a_un.a_str));
+
+	return (0);
+}
+
+/*
+ * This is similar to mdb_print.c's args_to_typename, but it has subtle
+ * differences surrounding how the strings of one element are handled that have
+ * 'struct', 'enum', or 'union' in them and instead works with them for tab
+ * completion purposes.
+ */
+int
+mdb_tab_typename(int *argcp, const mdb_arg_t **argvp, char *buf, size_t len)
+{
+	int argc = *argcp;
+	const mdb_arg_t *argv = *argvp;
+
+	if (argc < 1 || argv->a_type != MDB_TYPE_STRING)
+		return (DCMD_USAGE);
+
+	if (strcmp(argv->a_un.a_str, "struct") == 0 ||
+	    strcmp(argv->a_un.a_str, "enum") == 0 ||
+	    strcmp(argv->a_un.a_str, "union") == 0) {
+		if (argc == 1) {
+			(void) mdb_snprintf(buf, len, "%s ",
+			    argv[0].a_un.a_str);
+			return (1);
+		}
+
+		if (argv[1].a_type != MDB_TYPE_STRING)
+			return (DCMD_USAGE);
+
+		(void) mdb_snprintf(buf, len, "%s %s",
+		    argv[0].a_un.a_str, argv[1].a_un.a_str);
+
+		*argcp = argc - 1;
+		*argvp = argv + 1;
+	} else {
+		(void) mdb_snprintf(buf, len, "%s", argv[0].a_un.a_str);
+	}
+
+	return (0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/mdb/common/mdb/mdb_tab.h	Fri May 11 22:38:13 2012 -0400
@@ -0,0 +1,71 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+/*
+ * This file contains mdb private tab completion related functions. Public
+ * functions for modules are put into the module API, see mdb_modapi.h. Note
+ * that the mdb_ctf_id_t value is private to mdb and not a part of the module
+ * api, hence it has to stay in here.
+ */
+
+#ifndef	_MDB_TAB_H
+#define	_MDB_TAB_H
+
+#include <sys/types.h>
+#include <mdb/mdb_ctf.h>
+#include <mdb/mdb_nv.h>
+#include <mdb/mdb_modapi.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#ifdef	_MDB
+
+struct mdb_tab_cookie {
+	mdb_nv_t	mtc_nv;
+	char		mtc_match[MDB_SYM_NAMLEN];
+	char		mtc_base[MDB_SYM_NAMLEN];
+	void		*mtc_cba;
+};
+
+extern mdb_tab_cookie_t *mdb_tab_init(void);
+extern size_t mdb_tab_size(mdb_tab_cookie_t *);
+extern const char *mdb_tab_match(mdb_tab_cookie_t *);
+extern void mdb_tab_print(mdb_tab_cookie_t *);
+extern void mdb_tab_fini(mdb_tab_cookie_t *);
+
+extern int mdb_tab_complete_dcmd(mdb_tab_cookie_t *, const char *);
+extern int mdb_tab_complete_walker(mdb_tab_cookie_t *, const char *);
+extern int mdb_tab_complete_member_by_id(mdb_tab_cookie_t *, mdb_ctf_id_t,
+    const char *);
+extern int mdb_tab_command(mdb_tab_cookie_t *, const char *);
+
+#endif	/* _MDB */
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif /* _MDB_TAB_H */
--- a/usr/src/cmd/mdb/common/mdb/mdb_termio.c	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb_termio.c	Fri May 11 22:38:13 2012 -0400
@@ -25,6 +25,11 @@
  */
 
 /*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+
+/*
  * Terminal I/O Backend
  *
  * Terminal editing backend for standard input.  The terminal i/o backend is
@@ -76,6 +81,7 @@
 #include <mdb/mdb_string.h>
 #include <mdb/mdb_modapi.h>
 #include <mdb/mdb_frame.h>
+#include <mdb/mdb_tab.h>
 #include <mdb/mdb.h>
 
 #ifdef ERR
@@ -174,6 +180,7 @@
 #define	TIO_TTYWARN	0x20		/* Warnings about tty issued */
 #define	TIO_CAPWARN	0x40		/* Warnings about terminfo issued */
 #define	TIO_XTERM	0x80		/* Terminal is xterm compatible */
+#define	TIO_TAB		0x100		/* Tab completion mode */
 
 static const mdb_bitmask_t tio_flag_masks[] = {
 	{ "FINDHIST", TIO_FINDHIST, TIO_FINDHIST },
@@ -184,6 +191,7 @@
 	{ "TTYWARN", TIO_TTYWARN, TIO_TTYWARN },
 	{ "CAPWARN", TIO_CAPWARN, TIO_CAPWARN },
 	{ "XTERM", TIO_XTERM, TIO_XTERM },
+	{ "TAB", TIO_TAB, TIO_TAB},
 	{ NULL, 0, 0 }
 };
 
@@ -256,6 +264,7 @@
 static void termio_redraw(termio_data_t *);
 static void termio_prompt(termio_data_t *);
 
+static const char *termio_tab(termio_data_t *, int);
 static const char *termio_insert(termio_data_t *, int);
 static const char *termio_accept(termio_data_t *, int);
 static const char *termio_backspace(termio_data_t *, int);
@@ -398,7 +407,10 @@
 		goto out;
 	}
 
-	termio_prompt(td);
+	if (td->tio_flags & TIO_TAB)
+		termio_redraw(td);
+	else
+		termio_prompt(td);
 
 	/*
 	 * We need to redraw the entire command-line and restart our read loop
@@ -428,6 +440,13 @@
 
 	td->tio_active = TRUE;
 
+	/*
+	 * We may have had some error while in tab completion mode which sent us
+	 * longjmping all over the place. If that's the case, come back here and
+	 * make sure the flag is off.
+	 */
+	td->tio_flags &= ~TIO_TAB;
+
 	do {
 char_loop:
 		if ((c = mdb_iob_getc(td->tio_in)) == EOF) {
@@ -1488,6 +1507,11 @@
 	td->tio_keymap['['] = termio_accel;
 	td->tio_keymap[']'] = termio_accel;
 
+	/*
+	 * Grab tabs
+	 */
+	td->tio_keymap['\t'] = termio_tab;
+
 	td->tio_x = 0;
 	td->tio_y = 0;
 	td->tio_max_x = 0;
@@ -1590,6 +1614,67 @@
 	return (NULL);
 }
 
+/*
+ * This function may end up calling termio_read recursively as part of invoking
+ * the mdb pager. To work around this fact, we need to go through and make sure
+ * that we change the underlying terminal settings before and after this
+ * function call. If we don't do this, we invoke the pager, and don't abort
+ * (which will longjmp us elsewhere) we're going to return to the read loop with
+ * the wrong termio settings.
+ *
+ * Furthermore, because of the fact that we're being invoked in a user context
+ * that allows us to be interrupted, we need to actually allocate the memory
+ * that we're using with GC so that it gets cleaned up in case of the pager
+ * resetting us and never reaching the end.
+ */
+/*ARGSUSED*/
+static const char *
+termio_tab(termio_data_t *td, int c)
+{
+	char *buf;
+	const char *result;
+	int nres;
+	mdb_tab_cookie_t *mtp;
+
+	if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1)
+		warn("failed to restore terminal attributes");
+
+	buf = mdb_alloc(td->tio_cmdbuf.cmd_bufidx + 1, UM_SLEEP | UM_GC);
+	(void) strncpy(buf, td->tio_cmdbuf.cmd_buf, td->tio_cmdbuf.cmd_bufidx);
+	buf[td->tio_cmdbuf.cmd_bufidx] = '\0';
+	td->tio_flags |= TIO_TAB;
+	mtp = mdb_tab_init();
+	nres = mdb_tab_command(mtp, buf);
+
+	if (nres == 0) {
+		result = NULL;
+	} else {
+		result = mdb_tab_match(mtp);
+		if (nres != 1) {
+			mdb_printf("\n");
+			mdb_tab_print(mtp);
+		}
+	}
+
+	if (result != NULL) {
+		int index = 0;
+
+		while (result[index] != '\0') {
+			(void) termio_insert(td, result[index]);
+			index++;
+		}
+	}
+
+	termio_redraw(td);
+	mdb_tab_fini(mtp);
+	td->tio_flags &= ~TIO_TAB;
+	if (termio_ctl(td->tio_io, TCSETSW, &td->tio_rtios) == -1)
+		warn("failed to set terminal attributes");
+
+
+	return (NULL);
+}
+
 static const char *
 termio_delchar(termio_data_t *td, int c)
 {
--- a/usr/src/cmd/mdb/common/mdb/mdb_whatis.c	Thu May 03 05:27:43 2012 -0500
+++ b/usr/src/cmd/mdb/common/mdb/mdb_whatis.c	Fri May 11 22:38:13 2012 -0400
@@ -23,7 +23,12 @@
  * Use is subject to license terms.
  */
 
-#include <sys/mdb_modapi.h>
+/*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
+ */
+
+#include <mdb/mdb_modapi.h>
 #include <mdb/mdb.h>
 #include <mdb/mdb_io.h>
 #include <mdb/mdb_module.h>