/*
 * Copyright (c) 1997-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)mdb_fmt.c	1.2	99/11/19 SMI"

/*
 * Format String Decoder
 *
 * This file provides the core engine for converting strings of format
 * characters into formatted output.  The various format dcmds invoke the
 * mdb_fmt_print() function below with a target, address space identifier,
 * address, count, and format character, and it reads the required data from
 * the target and prints the formatted output to stdout.  Since nearly two
 * thirds of the format characters can be expressed as simple printf format
 * strings, we implement the engine using the lookup table below.  Each entry
 * provides either a pointer to a printf format string or a pointer to a
 * function to perform special processing.  For the printf case, the
 * corresponding data size in bytes is also supplied.  The printf processing
 * code handles 1, 2, 4, and 8-byte reads into an unsigned integer container
 * of the given size, and then simply calls mdb_iob_printf with the integer
 * and format string. This handles all printf cases, except when unsigned
 * promotion of an integer type in the varargs list does not perform the
 * conversion we require to get the proper result.  With the current set of
 * format characters, this case only occurs twice: we need a 4-byte float
 * to get promoted to 8-byte double for the 'f' format so it can be
 * correctly formatted by %f, and we need a 1-byte int8_t to get promoted
 * with sign extension to a 4-byte int32_t for the 'v' format so it can be
 * correctly formatted by %d.  We provide explicit functions to handle these
 * cases, as well as to handle special format characters such as 'i', etc.
 * We also provide a cmd_formats() dcmd function below which prints a table
 * of the output formats and their sizes.  Format characters that provide
 * custom functions provide their help description string explicitly.  All
 * the printf formats have their help strings generated automatically by
 * our printf "unparser" mdb_iob_format2str().
 */

#include <mdb/mdb_types.h>
#include <mdb/mdb_target.h>
#include <mdb/mdb_io.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb.h>

#define	FUNCP(p)	((void *)(p))	/* Cast to f_ptr type */
#define	SZ_NONE		((size_t)-1L)	/* Format does not change dot */

typedef mdb_tgt_addr_t mdb_fmt_func_f(mdb_tgt_t *,
    mdb_tgt_as_t, mdb_tgt_addr_t, size_t);

typedef struct mdb_fmt_desc {
	enum {
		FMT_NONE,	/* Format character is not supported */
		FMT_FUNC,	/* f_ptr is a mdb_fmt_func_f to call */
		FMT_PRINTF	/* f_ptr is a const char * format string */
	} f_type;
	void *f_ptr;		/* Data pointer (see above) */
	const char *f_help;	/* Additional help string */
	size_t f_size;		/* Size of type in bytes, or SZ_NONE */
} mdb_fmt_desc_t;

static const char help_plus[] = "increment dot by the count";
static const char help_minus[] = "decrement dot by the count";
static const char help_escchr[] = "character using C character notation";
static const char help_swapint[] = "swap bytes and shorts";
static const char help_dotinstr[] = "address and disassembled instruction";
static const char help_instr[] = "disassembled instruction";
static const char help_escstr[] = "string using C string notation";
static const char help_time32[] = "decoded time32_t";
static const char help_carat[] = "decrement dot by increment * count";
static const char help_dot[] = "dot as symbol+offset";
static const char help_f[] = "float";
static const char help_swapshort[] = "swap bytes";
static const char help_nl[] = "newline";
static const char help_ws[] = "whitespace";
static const char help_rawstr[] = "raw string";
static const char help_tab[] = "horizontal tab";
static const char help_sdbyte[] = "decimal signed int";
static const char help_time64[] = "decoded time64_t";

/*ARGSUSED*/
static mdb_tgt_addr_t
fmt_dot(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	uint_t oflags = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT;
	char buf[24];

	mdb_iob_clrflags(mdb.m_out, oflags);

	if (mdb.m_flags & MDB_FL_PSYM) {
		while (cnt-- != 0)
			mdb_iob_printf(mdb.m_out, "%-#16lla%16T", addr);
	} else {
		(void) mdb_iob_snprintf(buf, sizeof (buf),
		    "%#llx:", (u_longlong_t)addr);
		while (cnt-- != 0)
			mdb_iob_printf(mdb.m_out, "%-16s%16T", buf);
	}

	mdb_iob_setflags(mdb.m_out, oflags);
	mdb_nv_set_value(mdb.m_rvalue, addr);
	return (addr);
}

static mdb_tgt_addr_t
fmt_float(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	float f;
	/*
	 * We need to handle float as a special case because we need it to be
	 * promoted to a double by virtue of appearing as a parameter, and all
	 * our generic format handling below is based on integer types.
	 */
	while (cnt-- != 0) {
		if (mdb_tgt_aread(t, as, &f, sizeof (f), addr) != sizeof (f)) {
			warn("failed to read data from target");
			break;
		}
		mdb_iob_printf(mdb.m_out, "%e", f);
		addr += sizeof (f);
	}
	return (addr);
}

/*ARGSUSED*/
static mdb_tgt_addr_t
fmt_plus(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	return (addr + cnt);
}

/*ARGSUSED*/
static mdb_tgt_addr_t
fmt_minus(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	return (addr - cnt);
}

/*ARGSUSED*/
static mdb_tgt_addr_t
fmt_carat(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	return (addr - (mdb.m_incr * cnt));
}

/*ARGSUSED*/
static mdb_tgt_addr_t
fmt_nl(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	while (cnt-- != 0)
		mdb_iob_nl(mdb.m_out);

	return (addr);
}

/*ARGSUSED*/
static mdb_tgt_addr_t
fmt_ws(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	mdb_iob_ws(mdb.m_out, cnt);
	return (addr);
}

/*ARGSUSED*/
static mdb_tgt_addr_t
fmt_tab(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	size_t ts = mdb_iob_gettabstop(mdb.m_out);

	mdb_iob_tabstop(mdb.m_out, cnt);
	mdb_iob_tab(mdb.m_out);
	mdb_iob_tabstop(mdb.m_out, ts);

	return (addr);
}

static mdb_tgt_addr_t
fmt_rawstr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	uint_t oflags = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT;
	char buf[BUFSIZ];
	ssize_t nbytes;

	mdb_iob_clrflags(mdb.m_out, oflags);

	while (cnt-- != 0) {
		while ((nbytes = mdb_tgt_readstr(t, as, buf,
		    sizeof (buf), addr)) > 0) {
			mdb_iob_puts(mdb.m_out, buf);
			addr += nbytes;
		}
		addr++; /* Skip over null byte at end of string */
	}

	mdb_iob_setflags(mdb.m_out, oflags);
	return (addr);
}

static mdb_tgt_addr_t
fmt_escstr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	uint_t oflags = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT;
	char buf[BUFSIZ];
	ssize_t nbytes;
	char *s;

	mdb_iob_clrflags(mdb.m_out, oflags);

	while (cnt-- != 0) {
		while ((nbytes = mdb_tgt_readstr(t, as, buf,
		    sizeof (buf), addr)) > 0) {

			s = strchr2esc(buf);
			mdb_iob_puts(mdb.m_out, s);
			strfree(s);
			addr += nbytes;
		}
		addr++; /* Skip over null byte at end of string */
	}

	mdb_iob_setflags(mdb.m_out, oflags);
	return (addr);
}

static mdb_tgt_addr_t
fmt_escchr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	ssize_t nbytes;
	char *buf, *s;

	buf = mdb_alloc(cnt + 1, UM_SLEEP);
	buf[cnt] = 0;

	if ((nbytes = mdb_tgt_aread(t, as, buf, cnt, addr)) > 0) {
		s = strchr2esc(buf);
		mdb_iob_puts(mdb.m_out, s);
		strfree(s);
		addr += nbytes;
	}

	mdb_free(buf, cnt + 1);
	return (addr);
}

static mdb_tgt_addr_t
fmt_swapshort(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	ushort_t x;

	while (cnt-- != 0) {
		if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) {
			x = (x << 8) | (x >> 8);
			mdb_iob_printf(mdb.m_out, "%-8x", x);
			mdb_nv_set_value(mdb.m_rvalue, x);
			addr += sizeof (x);
		}
	}
	return (addr);
}

static mdb_tgt_addr_t
fmt_swapint(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	uint_t x;

	while (cnt-- != 0) {
		if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) {
			x = ((x << 24) | ((x << 8) & 0xff0000) |
			    ((x >> 8) & 0xff00) | ((x >> 24) & 0xff));
			mdb_iob_printf(mdb.m_out, "%-16x", x);
			mdb_nv_set_value(mdb.m_rvalue, x);
			addr += sizeof (x);
		}
	}
	return (addr);
}

static mdb_tgt_addr_t
fmt_time32(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	uint32_t x;

	while (cnt-- != 0) {
		if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) {
			mdb_iob_printf(mdb.m_out, "%-24Y", (time_t)x);
			mdb_nv_set_value(mdb.m_rvalue, x);
			addr += sizeof (x);
		}
	}
	return (addr);
}

static mdb_tgt_addr_t
fmt_time64(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	uint64_t x;

	while (cnt-- != 0) {
		if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) {
			mdb_iob_printf(mdb.m_out, "%-24Y", (time_t)x);
			mdb_nv_set_value(mdb.m_rvalue, x);
			addr += sizeof (x);
		}
	}
	return (addr);
}

static mdb_tgt_addr_t
fmt_sdbyte(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	int8_t x;

	while (cnt-- != 0) {
		if (mdb_tgt_aread(t, as, &x, sizeof (x), addr) == sizeof (x)) {
			mdb_iob_printf(mdb.m_out, "%-8d", (int32_t)x);
			mdb_nv_set_value(mdb.m_rvalue, (uint8_t)x);
			addr += sizeof (x);
		}
	}
	return (addr);
}

static mdb_tgt_addr_t
fmt_instr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	char buf[BUFSIZ];
	uintptr_t naddr;

	while (cnt-- != 0) {
		naddr = mdb_dis_ins2str(mdb.m_disasm, t, as, buf, addr);
		if (naddr == addr)
			return (addr); /* If we didn't move, we failed */
		mdb_iob_printf(mdb.m_out, "%s\n", buf);
		addr = naddr;
	}
	return (addr);
}

static mdb_tgt_addr_t
fmt_dotinstr(mdb_tgt_t *t, mdb_tgt_as_t as, mdb_tgt_addr_t addr, size_t cnt)
{
	uint_t oflags = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT;

	char buf[BUFSIZ];
	uintptr_t naddr;
	uint32_t i;

	for (mdb_iob_clrflags(mdb.m_out, oflags); cnt-- != 0; addr = naddr) {
		if (mdb_tgt_aread(t, as, &i, sizeof (i), addr) != sizeof (i))
			break; /* Fail if we can't read instruction */
		naddr = mdb_dis_ins2str(mdb.m_disasm, t, as, buf, addr);
		if (naddr == addr)
			break; /* Fail if we didn't advance */
		mdb_iob_printf(mdb.m_out, "%lx %x: %s\n", (long)addr, i, buf);
	}

	mdb_iob_setflags(mdb.m_out, oflags);
	return (addr);
}

static const mdb_fmt_desc_t fmttab[] = {
	{ FMT_NONE, NULL, NULL, 0 },				/* 0 = NUL */
	{ FMT_NONE, NULL, NULL, 0 },				/* 1 = SOH */
	{ FMT_NONE, NULL, NULL, 0 },				/* 2 = STX */
	{ FMT_NONE, NULL, NULL, 0 },				/* 3 = ETX */
	{ FMT_NONE, NULL, NULL, 0 },				/* 4 = EOT */
	{ FMT_NONE, NULL, NULL, 0 },				/* 5 = ENQ */
	{ FMT_NONE, NULL, NULL, 0 },				/* 6 = ACK */
	{ FMT_NONE, NULL, NULL, 0 },				/* 7 = BEL */
	{ FMT_NONE, NULL, NULL, 0 },				/* 8 = BS */
	{ FMT_NONE, NULL, NULL, 0 },				/* 9 = \t */
	{ FMT_NONE, NULL, NULL, 0 },				/* 10 = \n */
	{ FMT_NONE, NULL, NULL, 0 },				/* 11 = VT */
	{ FMT_NONE, NULL, NULL, 0 },				/* 12 = FF */
	{ FMT_NONE, NULL, NULL, 0 },				/* 13 = \r */
	{ FMT_NONE, NULL, NULL, 0 },				/* 14 = SO */
	{ FMT_NONE, NULL, NULL, 0 },				/* 15 = SI */
	{ FMT_NONE, NULL, NULL, 0 },				/* 16 = DLE */
	{ FMT_NONE, NULL, NULL, 0 },				/* 17 = DC1 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 18 = DC2 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 19 = DC3 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 20 = DC4 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 21 = NAK */
	{ FMT_NONE, NULL, NULL, 0 },				/* 22 = EYC */
	{ FMT_NONE, NULL, NULL, 0 },				/* 23 = ETB */
	{ FMT_NONE, NULL, NULL, 0 },				/* 24 = CAN */
	{ FMT_NONE, NULL, NULL, 0 },				/* 25 = EM */
	{ FMT_NONE, NULL, NULL, 0 },				/* 26 = SUB */
	{ FMT_NONE, NULL, NULL, 0 },				/* 27 = ESC */
	{ FMT_NONE, NULL, NULL, 0 },				/* 28 = FS */
	{ FMT_NONE, NULL, NULL, 0 },				/* 29 = GS */
	{ FMT_NONE, NULL, NULL, 0 },				/* 30 = RS */
	{ FMT_NONE, NULL, NULL, 0 },				/* 31 = US */
	{ FMT_NONE, NULL, NULL, 0 },				/* 32 = SPACE */
	{ FMT_NONE, NULL, NULL, 0 },				/* 33 = ! */
	{ FMT_NONE, NULL, NULL, 0 },				/* 34 = " */
	{ FMT_NONE, NULL, NULL, 0 },				/* 35 = # */
	{ FMT_NONE, NULL, NULL, 0 },				/* 36 = $ */
	{ FMT_NONE, NULL, NULL, 0 },				/* 37 = % */
	{ FMT_NONE, NULL, NULL, 0 },				/* 38 = & */
	{ FMT_NONE, NULL, NULL, 0 },				/* 39 = ' */
	{ FMT_NONE, NULL, NULL, 0 },				/* 40 = ( */
	{ FMT_NONE, NULL, NULL, 0 },				/* 41 = ) */
	{ FMT_NONE, NULL, NULL, 0 },				/* 42 = * */
	{ FMT_FUNC, FUNCP(fmt_plus), help_plus, 0 },		/* 43 = + */
	{ FMT_NONE, NULL, NULL, 0 },				/* 44 = , */
	{ FMT_FUNC, FUNCP(fmt_minus), help_minus, 0 },		/* 45 = - */
	{ FMT_NONE, NULL, NULL, 0 },				/* 46 = . */
	{ FMT_NONE, NULL, NULL, 0 },				/* 47 = / */
	{ FMT_NONE, NULL, NULL, 0 },				/* 48 = 0 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 49 = 1 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 50 = 2 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 51 = 3 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 52 = 4 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 53 = 5 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 54 = 6 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 55 = 7 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 56 = 8 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 57 = 9 */
	{ FMT_NONE, NULL, NULL, 0 },				/* 58 = : */
	{ FMT_NONE, NULL, NULL, 0 },				/* 59 = ; */
	{ FMT_NONE, NULL, NULL, 0 },				/* 60 = < */
	{ FMT_NONE, NULL, NULL, 0 },				/* 61 = = */
	{ FMT_NONE, NULL, NULL, 0 },				/* 62 = > */
	{ FMT_NONE, NULL, NULL, 0 },				/* 63 = ? */
	{ FMT_NONE, NULL, NULL, 0 },				/* 64 = @ */
	{ FMT_NONE, NULL, NULL, 0 },				/* 65 = A */
	{ FMT_PRINTF, "%-8x", NULL, 1 },			/* 66 = B */
	{ FMT_FUNC, FUNCP(fmt_escchr), help_escchr, 1 },	/* 67 = C */
	{ FMT_PRINTF, "%-16d", NULL, 4 },			/* 68 = D */
	{ FMT_PRINTF, "%-16llu", NULL, 8 },			/* 69 = E */
	{ FMT_PRINTF, "%g", NULL, sizeof (double) },		/* 70 = F */
	{ FMT_PRINTF, "%-16llo", NULL, 8 },			/* 71 = G */
	{ FMT_FUNC, FUNCP(fmt_swapint), help_swapint, 4 },	/* 72 = H */
	{ FMT_FUNC, FUNCP(fmt_dotinstr), help_dotinstr, 0 },	/* 73 = I */
	{ FMT_PRINTF, "%-16llx", NULL, 8 },			/* 74 = J */
#ifdef _LP64
	{ FMT_PRINTF, "%-16llx", NULL, 8 },			/* 75 = K (J) */
#else
	{ FMT_PRINTF, "%-16x", NULL, 4 },			/* 75 = K (X) */
#endif
	{ FMT_NONE, NULL, NULL, 0 }, /* 4-byte pattern match */	/* 76 = L */
	{ FMT_NONE, NULL, NULL, 0 }, /* 8-byte pattern match */	/* 77 = M */
	{ FMT_NONE, NULL, NULL, 0 },				/* 78 = N */
	{ FMT_PRINTF, "%-#16o", NULL, 4 },			/* 79 = O */
	{ FMT_PRINTF, "%-16a", NULL, sizeof (uintptr_t) },	/* 80 = P */
	{ FMT_PRINTF, "%-#16q", NULL, 4 },			/* 81 = Q */
	{ FMT_NONE, NULL, NULL, 0 },				/* 82 = R */
	{ FMT_FUNC, FUNCP(fmt_escstr), help_escstr, 0 },	/* 83 = S */
	{ FMT_NONE, NULL, NULL, 0 },				/* 84 = T */
	{ FMT_PRINTF, "%-16u", NULL, 4 },			/* 85 = U */
	{ FMT_PRINTF, "%-8u", NULL, 1 },			/* 86 = V */
	{ FMT_PRINTF, "%-16r", NULL, 4 },			/* 87 = W */
	{ FMT_PRINTF, "%-16x", NULL, 4 },			/* 88 = X */
	{ FMT_FUNC, FUNCP(fmt_time32), help_time32, 4 },	/* 89 = Y */
	{ FMT_PRINTF, "%-16llx", NULL, 8 },			/* 90 = Z */
	{ FMT_NONE, NULL, NULL, 0 },				/* 91 = [ */
	{ FMT_NONE, NULL, NULL, 0 },				/* 92 = \ */
	{ FMT_NONE, NULL, NULL, 0 },				/* 93 = ] */
	{ FMT_FUNC, FUNCP(fmt_carat), help_carat, 0 },		/* 94 = ^ */
	{ FMT_NONE, NULL, NULL, 0 },				/* 95 = _ */
	{ FMT_NONE, NULL, NULL, 0 },				/* 96 = ` */
	{ FMT_FUNC, FUNCP(fmt_dot), help_dot, SZ_NONE },	/* 97 = a */
	{ FMT_PRINTF, "%-#8o", NULL, 1 },			/* 98 = b */
	{ FMT_PRINTF, "%c", NULL, 1 },				/* 99 = c */
	{ FMT_PRINTF, "%-8hd", NULL, 2 },			/* 100 = d */
	{ FMT_PRINTF, "%-16lld", NULL, 8 },			/* 101 = e */
	{ FMT_FUNC, FUNCP(fmt_float), help_f, sizeof (float) },	/* 102 = f */
	{ FMT_PRINTF, "%-16llq", NULL, 8 },			/* 103 = g */
	{ FMT_FUNC, FUNCP(fmt_swapshort), help_swapshort, 2 },	/* 104 = h */
	{ FMT_FUNC, FUNCP(fmt_instr), help_instr, 0 },		/* 105 = i */
	{ FMT_NONE, NULL, NULL, 0 },				/* 106 = j */
	{ FMT_NONE, NULL, NULL, 0 },				/* 107 = k */
	{ FMT_NONE, NULL, NULL, 0 }, /* 2-byte pattern match */	/* 108 = l */
	{ FMT_NONE, NULL, NULL, 0 },				/* 109 = m */
	{ FMT_FUNC, FUNCP(fmt_nl), help_nl, SZ_NONE },		/* 110 = n */
	{ FMT_PRINTF, "%-#8ho", NULL, 2 },			/* 111 = o */
	{ FMT_PRINTF, "%-16a", NULL, sizeof (uintptr_t) },	/* 112 = p */
	{ FMT_PRINTF, "%-#8hq", NULL, 2 },			/* 113 = q */
	{ FMT_FUNC, FUNCP(fmt_ws), help_ws, SZ_NONE },		/* 114 = r */
	{ FMT_FUNC, FUNCP(fmt_rawstr), help_rawstr, 0 },	/* 115 = s */
	{ FMT_FUNC, FUNCP(fmt_tab), help_tab, SZ_NONE },	/* 116 = t */
	{ FMT_PRINTF, "%-8hu", NULL, 2 },			/* 117 = u */
	{ FMT_FUNC, FUNCP(fmt_sdbyte), help_sdbyte, 1 },	/* 118 = v */
	{ FMT_PRINTF, "%-8hr", NULL, 2 },			/* 119 = w */
	{ FMT_PRINTF, "%-8hx", NULL, 2 },			/* 120 = x */
	{ FMT_FUNC, FUNCP(fmt_time64), help_time64, 8 },	/* 121 = y */
	{ FMT_NONE, NULL, NULL, 0 },				/* 122 = z */
};

mdb_tgt_addr_t
mdb_fmt_print(mdb_tgt_t *t, mdb_tgt_as_t as,
    mdb_tgt_addr_t addr, size_t cnt, char fmt)
{
	const mdb_fmt_desc_t *fp = &fmttab[fmt];
	mdb_fmt_func_f *funcp;
	uintmax_t rvalue;
	void *buf;

	union {
		uint64_t i8;
		uint32_t i4;
		uint16_t i2;
		uint8_t i1;
	} u;

	if (fmt < 0 || fmt > (sizeof (fmttab) / sizeof (fmttab[0]))) {
		warn("invalid format character -- '%c'\n", fmt);
		return (addr);
	}

	switch (fp->f_type) {
	case FMT_FUNC:
		funcp = (mdb_fmt_func_f *)fp->f_ptr;
		addr = funcp(t, as, addr, cnt);
		break;

	case FMT_PRINTF:
		switch (fp->f_size) {
		case 1:
			buf = &u.i1;
			break;
		case 2:
			buf = &u.i2;
			break;
		case 4:
			buf = &u.i4;
			break;
		case 8:
			buf = &u.i8;
			break;
		default:
			fail("format %c is defined using illegal size\n", fmt);
		}

		while (cnt-- != 0) {
			if (mdb_tgt_aread(t, as, buf, fp->f_size, addr) !=
			    fp->f_size) {
				warn("failed to read data from target");
				return (addr);
			}

			switch (fp->f_size) {
			case 1:
				mdb_iob_printf(mdb.m_out, fp->f_ptr, u.i1);
				rvalue = u.i1;
				break;
			case 2:
				mdb_iob_printf(mdb.m_out, fp->f_ptr, u.i2);
				rvalue = u.i2;
				break;
			case 4:
				mdb_iob_printf(mdb.m_out, fp->f_ptr, u.i4);
				rvalue = u.i4;
				break;
			case 8:
				mdb_iob_printf(mdb.m_out, fp->f_ptr, u.i8);
				rvalue = u.i8;
				break;
			}

			mdb_nv_set_value(mdb.m_rvalue, rvalue);
			addr += fp->f_size;
		}
		break;

	default:
		warn("invalid format character -- '%c'\n", fmt);
	}

	return (addr);
}

/*ARGSUSED*/
int
cmd_formats(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	const mdb_fmt_desc_t *fp = &fmttab[0];
	int i;

	if (argc != 0)
		return (DCMD_USAGE);

	for (i = 0; i < (sizeof (fmttab) / sizeof (fmttab[0])); i++, fp++) {
		if (fp->f_type == FMT_NONE)
			continue;

		if (fp->f_type == FMT_PRINTF)
			mdb_printf("%c - %s", i, mdb_iob_format2str(fp->f_ptr));
		else
			mdb_printf("%c - %s", i, fp->f_help);

		switch (fp->f_size) {
		case SZ_NONE:
			mdb_printf("\n");
			break;
		case 0:
			mdb_printf(" (variable size)\n");
			break;
		case 1:
			mdb_printf(" (1 byte)\n");
			break;
		default:
			mdb_printf(" (%lu bytes)\n", fp->f_size);
		}
	}

	return (DCMD_OK);
}
