Print this page
2574 mdb needs ::printf
Reviewed by: Joshua M. Clulow <josh@sysmgr.org>
Reviewed by: Eric Schrock <eric.schrock@delphix.com>
Reviewed by: Adam Leventhal <ahl@delphix.com>
Approved by: ?
*** 42,51 ****
--- 42,52 ----
#include <mdb/mdb_tab.h>
#include <sys/isa_defs.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
+ #include <netinet/in.h>
#include <strings.h>
#include <libctf.h>
#include <ctype.h>
typedef struct holeinfo {
*** 1685,1695 ****
uint16_t i2;
uint8_t i1;
} u;
if (mdb_ctf_type_resolve(id, &base) == -1) {
! mdb_warn("could not resolve type\n");
return (-1);
}
/*
* If the user gives -a, then always print out the address of the
--- 1686,1696 ----
uint16_t i2;
uint8_t i1;
} u;
if (mdb_ctf_type_resolve(id, &base) == -1) {
! mdb_warn("could not resolve type");
return (-1);
}
/*
* If the user gives -a, then always print out the address of the
*** 2001,2011 ****
start = end;
delim = parse_delimiter(&start);
}
-
*idp = id;
*offp = off;
return (0);
}
--- 2002,2011 ----
*** 2460,2465 ****
--- 2460,3036 ----
"Members may be specified with standard C syntax using the\n"
"array indexing operator \"[index]\", structure member\n"
"operator \".\", or structure pointer operator \"->\".\n"
"\n"
"Offsets must use the $[ expression ] syntax\n");
+ }
+
+ static int
+ printf_signed(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt,
+ boolean_t sign)
+ {
+ ssize_t size;
+ mdb_ctf_id_t base;
+ ctf_encoding_t e;
+
+ union {
+ uint64_t ui8;
+ uint32_t ui4;
+ uint16_t ui2;
+ uint8_t ui1;
+ int64_t i8;
+ int32_t i4;
+ int16_t i2;
+ int8_t i1;
+ } u;
+
+ if (mdb_ctf_type_resolve(id, &base) == -1) {
+ mdb_warn("could not resolve type");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_ctf_type_kind(base) != CTF_K_INTEGER) {
+ mdb_warn("expected integer type\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_ctf_type_encoding(base, &e) != 0) {
+ mdb_warn("could not get type encoding");
+ return (DCMD_ABORT);
+ }
+
+ if (sign)
+ sign = e.cte_format & CTF_INT_SIGNED;
+
+ size = e.cte_bits / NBBY;
+
+ /*
+ * Check to see if our life has been complicated by the presence of
+ * a bitfield. If it has, we will print it using logic that is only
+ * slightly different than that found in print_bitfield(), above. (In
+ * particular, see the comments there for an explanation of the
+ * endianness differences in this code.)
+ */
+ if (size > 8 || (e.cte_bits % NBBY) != 0 ||
+ (size & (size - 1)) != 0) {
+ uint64_t mask = (1ULL << e.cte_bits) - 1;
+ uint64_t value = 0;
+ uint8_t *buf = (uint8_t *)&value;
+ uint8_t shift;
+
+ /*
+ * Round our size up one byte.
+ */
+ size = (e.cte_bits + (NBBY - 1)) / NBBY;
+
+ if (e.cte_bits > sizeof (value) * NBBY - 1) {
+ mdb_printf("invalid bitfield size %u", e.cte_bits);
+ return (DCMD_ABORT);
+ }
+
+ #ifdef _BIG_ENDIAN
+ buf += sizeof (value) - size;
+ off += e.cte_bits;
+ #endif
+
+ if (mdb_vread(buf, size, addr) == -1) {
+ mdb_warn("failed to read %lu bytes at %p", size, addr);
+ return (DCMD_ERR);
+ }
+
+ shift = off % NBBY;
+ #ifdef _BIG_ENDIAN
+ shift = NBBY - shift;
+ #endif
+
+ /*
+ * If we have a bit offset within the byte, shift it down.
+ */
+ if (off % NBBY != 0)
+ value >>= shift;
+ value &= mask;
+
+ if (sign) {
+ int sshift = sizeof (value) * NBBY - e.cte_bits;
+ value = ((int64_t)value << sshift) >> sshift;
+ }
+
+ mdb_printf(fmt, value);
+ return (0);
+ }
+
+ if (mdb_vread(&u.i8, size, addr) == -1) {
+ mdb_warn("failed to read %lu bytes at %p", (ulong_t)size, addr);
+ return (DCMD_ERR);
+ }
+
+ switch (size) {
+ case sizeof (uint8_t):
+ mdb_printf(fmt, (uint64_t)(sign ? u.i1 : u.ui1));
+ break;
+ case sizeof (uint16_t):
+ mdb_printf(fmt, (uint64_t)(sign ? u.i2 : u.ui2));
+ break;
+ case sizeof (uint32_t):
+ mdb_printf(fmt, (uint64_t)(sign ? u.i4 : u.ui4));
+ break;
+ case sizeof (uint64_t):
+ mdb_printf(fmt, (uint64_t)(sign ? u.i8 : u.ui8));
+ break;
+ }
+
+ return (0);
+ }
+
+ static int
+ printf_int(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt)
+ {
+ return (printf_signed(id, addr, off, fmt, B_TRUE));
+ }
+
+ static int
+ printf_uint(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt)
+ {
+ return (printf_signed(id, addr, off, fmt, B_FALSE));
+ }
+
+ /*ARGSUSED*/
+ static int
+ printf_uint32(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt)
+ {
+ mdb_ctf_id_t base;
+ ctf_encoding_t e;
+ uint32_t value;
+
+ if (mdb_ctf_type_resolve(id, &base) == -1) {
+ mdb_warn("could not resolve type\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_ctf_type_kind(base) != CTF_K_INTEGER ||
+ mdb_ctf_type_encoding(base, &e) != 0 ||
+ e.cte_bits / NBBY != sizeof (value)) {
+ mdb_warn("expected 32-bit integer type\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_vread(&value, sizeof (value), addr) == -1) {
+ mdb_warn("failed to read 32-bit value at %p", addr);
+ return (DCMD_ERR);
+ }
+
+ mdb_printf(fmt, value);
+
+ return (0);
+ }
+
+ /*ARGSUSED*/
+ static int
+ printf_ptr(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt)
+ {
+ uintptr_t value;
+ mdb_ctf_id_t base;
+
+ if (mdb_ctf_type_resolve(id, &base) == -1) {
+ mdb_warn("could not resolve type\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_ctf_type_kind(base) != CTF_K_POINTER) {
+ mdb_warn("expected pointer type\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_vread(&value, sizeof (value), addr) == -1) {
+ mdb_warn("failed to read pointer at %llx", addr);
+ return (DCMD_ERR);
+ }
+
+ mdb_printf(fmt, value);
+
+ return (0);
+ }
+
+ /*ARGSUSED*/
+ static int
+ printf_string(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt)
+ {
+ mdb_ctf_id_t base;
+ mdb_ctf_arinfo_t r;
+ char buf[1024];
+ ssize_t size;
+
+ if (mdb_ctf_type_resolve(id, &base) == -1) {
+ mdb_warn("could not resolve type");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_ctf_type_kind(base) == CTF_K_POINTER) {
+ uintptr_t value;
+
+ if (mdb_vread(&value, sizeof (value), addr) == -1) {
+ mdb_warn("failed to read pointer at %llx", addr);
+ return (DCMD_ERR);
+ }
+
+ if (mdb_readstr(buf, sizeof (buf) - 1, value) < 0) {
+ mdb_warn("failed to read string at %llx", value);
+ return (DCMD_ERR);
+ }
+
+ mdb_printf(fmt, buf);
+ return (0);
+ }
+
+ if (mdb_ctf_type_kind(base) != CTF_K_ARRAY) {
+ mdb_warn("exepected pointer or array type\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_ctf_array_info(base, &r) == -1 ||
+ mdb_ctf_type_resolve(r.mta_contents, &base) == -1 ||
+ (size = mdb_ctf_type_size(base)) == -1) {
+ mdb_warn("can't determine array type");
+ return (DCMD_ABORT);
+ }
+
+ if (size != 1) {
+ mdb_warn("string format specifier requires "
+ "an array of characters\n");
+ return (DCMD_ABORT);
+ }
+
+ bzero(buf, sizeof (buf));
+
+ if (mdb_vread(buf, MIN(r.mta_nelems, sizeof (buf) - 1), addr) == -1) {
+ mdb_warn("failed to read array at %p", addr);
+ return (DCMD_ERR);
+ }
+
+ mdb_printf(fmt, buf);
+
+ return (0);
+ }
+
+ /*ARGSUSED*/
+ static int
+ printf_ipv6(mdb_ctf_id_t id, uintptr_t addr, ulong_t off, char *fmt)
+ {
+ mdb_ctf_id_t base;
+ mdb_ctf_id_t ipv6_type, ipv6_base;
+ in6_addr_t ipv6;
+
+ if (mdb_ctf_lookup_by_name("in6_addr_t", &ipv6_type) == -1) {
+ mdb_warn("could not resolve in6_addr_t type\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_ctf_type_resolve(id, &base) == -1) {
+ mdb_warn("could not resolve type\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_ctf_type_resolve(ipv6_type, &ipv6_base) == -1) {
+ mdb_warn("could not resolve in6_addr_t type\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_ctf_type_cmp(base, ipv6_base) != 0) {
+ mdb_warn("requires argument of type in6_addr_t\n");
+ return (DCMD_ABORT);
+ }
+
+ if (mdb_vread(&ipv6, sizeof (ipv6), addr) == -1) {
+ mdb_warn("couldn't read in6_addr_t at %p", addr);
+ return (DCMD_ERR);
+ }
+
+ mdb_printf(fmt, &ipv6);
+
+ return (0);
+ }
+
+ /*
+ * To validate the format string specified to ::printf, we run the format
+ * string through a very simple state machine that restricts us to a subset
+ * of mdb_printf() functionality.
+ */
+ enum {
+ PRINTF_NOFMT = 1, /* no current format specifier */
+ PRINTF_PERC, /* processed '%' */
+ PRINTF_FMT, /* processing format specifier */
+ PRINTF_LEFT, /* processed '-', expecting width */
+ PRINTF_WIDTH, /* processing width */
+ PRINTF_QUES /* processed '?', expecting format */
+ };
+
+ int
+ cmd_printf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+ {
+ char type[MDB_SYM_NAMLEN];
+ int i, nfmts = 0, ret;
+ mdb_ctf_id_t id;
+ const char *fmt, *member;
+ char **fmts, *last, *dest, f;
+ int (**funcs)(mdb_ctf_id_t, uintptr_t, ulong_t, char *);
+ int state = PRINTF_NOFMT;
+ printarg_t pa;
+
+ if (!(flags & DCMD_ADDRSPEC))
+ return (DCMD_USAGE);
+
+ bzero(&pa, sizeof (pa));
+ pa.pa_as = MDB_TGT_AS_VIRT;
+ pa.pa_realtgt = pa.pa_tgt = mdb.m_target;
+
+ if (argc == 0 || argv[0].a_type != MDB_TYPE_STRING) {
+ mdb_warn("expected a format string\n");
+ return (DCMD_USAGE);
+ }
+
+ /*
+ * Our first argument is a format string; rip it apart and run it
+ * through our state machine to validate that our input is within the
+ * subset of mdb_printf() format strings that we allow.
+ */
+ fmt = argv[0].a_un.a_str;
+ /*
+ * 'dest' must be large enough to hold a copy of the format string,
+ * plus a NUL and up to 2 additional characters for each conversion
+ * in the format string. This gives us a bloat factor of 5/2 ~= 3.
+ * e.g. "%d" (strlen of 2) --> "%lld\0" (need 5 bytes)
+ */
+ dest = mdb_zalloc(strlen(fmt) * 3, UM_SLEEP | UM_GC);
+ fmts = mdb_zalloc(strlen(fmt) * sizeof (char *), UM_SLEEP | UM_GC);
+ funcs = mdb_zalloc(strlen(fmt) * sizeof (void *), UM_SLEEP | UM_GC);
+ last = dest;
+
+ for (i = 0; fmt[i] != '\0'; i++) {
+ *dest++ = f = fmt[i];
+
+ switch (state) {
+ case PRINTF_NOFMT:
+ state = f == '%' ? PRINTF_PERC : PRINTF_NOFMT;
+ break;
+
+ case PRINTF_PERC:
+ state = f == '-' ? PRINTF_LEFT :
+ f >= '0' && f <= '9' ? PRINTF_WIDTH :
+ f == '?' ? PRINTF_QUES :
+ f == '%' ? PRINTF_NOFMT : PRINTF_FMT;
+ break;
+
+ case PRINTF_LEFT:
+ state = f >= '0' && f <= '9' ? PRINTF_WIDTH :
+ f == '?' ? PRINTF_QUES : PRINTF_FMT;
+ break;
+
+ case PRINTF_WIDTH:
+ state = f >= '0' && f <= '9' ? PRINTF_WIDTH :
+ PRINTF_FMT;
+ break;
+
+ case PRINTF_QUES:
+ state = PRINTF_FMT;
+ break;
+ }
+
+ if (state != PRINTF_FMT)
+ continue;
+
+ dest--;
+
+ /*
+ * Now check that we have one of our valid format characters.
+ */
+ switch (f) {
+ case 'a':
+ case 'A':
+ case 'p':
+ funcs[nfmts] = printf_ptr;
+ break;
+
+ case 'd':
+ case 'q':
+ case 'R':
+ funcs[nfmts] = printf_int;
+ *dest++ = 'l';
+ *dest++ = 'l';
+ break;
+
+ case 'I':
+ funcs[nfmts] = printf_uint32;
+ break;
+
+ case 'N':
+ funcs[nfmts] = printf_ipv6;
+ break;
+
+ case 'o':
+ case 'r':
+ case 'u':
+ case 'x':
+ case 'X':
+ funcs[nfmts] = printf_uint;
+ *dest++ = 'l';
+ *dest++ = 'l';
+ break;
+
+ case 's':
+ funcs[nfmts] = printf_string;
+ break;
+
+ case 'Y':
+ funcs[nfmts] = sizeof (time_t) == sizeof (int) ?
+ printf_uint32 : printf_uint;
+ break;
+
+ default:
+ mdb_warn("illegal format string at or near "
+ "'%c' (position %d)\n", f, i + 1);
+ return (DCMD_ABORT);
+ }
+
+ *dest++ = f;
+ *dest++ = '\0';
+ fmts[nfmts++] = last;
+ last = dest;
+ state = PRINTF_NOFMT;
+ }
+
+ argc--;
+ argv++;
+
+ /*
+ * Now we expect a type name.
+ */
+ if ((ret = args_to_typename(&argc, &argv, type, sizeof (type))) != 0)
+ return (ret);
+
+ argv++;
+ argc--;
+
+ if (mdb_ctf_lookup_by_name(type, &id) != 0) {
+ mdb_warn("failed to look up type %s", type);
+ return (DCMD_ABORT);
+ }
+
+ if (argc == 0) {
+ mdb_warn("at least one member must be specified\n");
+ return (DCMD_USAGE);
+ }
+
+ if (argc != nfmts) {
+ mdb_warn("%s format specifiers (found %d, expected %d)\n",
+ argc > nfmts ? "missing" : "extra", nfmts, argc);
+ return (DCMD_ABORT);
+ }
+
+ for (i = 0; i < argc; i++) {
+ mdb_ctf_id_t mid;
+ ulong_t off;
+ int ignored;
+
+ if (argv[i].a_type != MDB_TYPE_STRING) {
+ mdb_warn("expected only type member arguments\n");
+ return (DCMD_ABORT);
+ }
+
+ if (strcmp((member = argv[i].a_un.a_str), ".") == 0) {
+ /*
+ * We allow "." to be specified to denote the current
+ * value of dot.
+ */
+ if (funcs[i] != printf_ptr && funcs[i] != printf_uint &&
+ funcs[i] != printf_int) {
+ mdb_warn("expected integer or pointer format "
+ "specifier for '.'\n");
+ return (DCMD_ABORT);
+ }
+
+ mdb_printf(fmts[i], mdb_get_dot());
+ continue;
+ }
+
+ pa.pa_addr = addr;
+
+ if (parse_member(&pa, member, id, &mid, &off, &ignored) != 0)
+ return (DCMD_ABORT);
+
+ if ((ret = funcs[i](mid, pa.pa_addr, off, fmts[i])) != 0) {
+ mdb_warn("failed to print member '%s'\n", member);
+ return (ret);
+ }
+ }
+
+ mdb_printf("%s", last);
+
+ return (DCMD_OK);
+ }
+
+ static char _mdb_printf_help[] =
+ "The format string argument is a printf(3C)-like format string that is a\n"
+ "subset of the format strings supported by mdb_printf(). The type argument\n"
+ "is the name of a type to be used to interpret the memory referenced by dot.\n"
+ "The member should either be a field in the specified structure, or the\n"
+ "special member '.', denoting the value of dot (and treated as a pointer).\n"
+ "The number of members must match the number of format specifiers in the\n"
+ "format string.\n"
+ "\n"
+ "The following format specifiers are recognized by ::printf:\n"
+ "\n"
+ " %% Prints the '%' symbol.\n"
+ " %a Prints the member in symbolic form.\n"
+ " %d Prints the member as a decimal integer. If the member is a signed\n"
+ " integer type, the output will be signed.\n"
+ " %I Prints the member a IPv4 address (must be a 32-bit integer type).\n"
+ " %N Prints the member an IPv6 address (must be of type in6_addr_t).\n"
+ " %o Prints the member as an unsigned octal integer.\n"
+ " %p Prints the member as a pointer, in hexadecimal.\n"
+ " %q Prints the member in signed octal. Honk if you ever use this!\n"
+ " %r Prints the member as an unsigned value in the current output radix.\n"
+ " %R Prints the member as a signed value in the current output radix.\n"
+ " %s Prints the member as a string (requires a pointer or an array of\n"
+ " characters).\n"
+ " %u Prints the member as an unsigned decimal integer.\n"
+ " %x Prints the member in hexadecimal.\n"
+ " %X Prints the member in hexadecimal, using the characters A-F as the\n"
+ " digits for the values 10-15.\n"
+ " %Y Prints the member as a time_t as the string "
+ "'year month day HH:MM:SS'.\n"
+ "\n"
+ "The following field width specifiers are recognized by ::printf:\n"
+ "\n"
+ " %n Field width is set to the specified decimal value.\n"
+ " %? Field width is set to the maximum width of a hexadecimal pointer\n"
+ " value. This is 8 in an ILP32 environment, and 16 in an LP64\n"
+ " environment.\n"
+ "\n"
+ "The following flag specifers are recognized by ::printf:\n"
+ "\n"
+ " %- Left-justify the output within the specified field width. If the\n"
+ " width of the output is less than the specified field width, the\n"
+ " output will be padded with blanks on the right-hand side. Without\n"
+ " %-, values are right-justified by default.\n"
+ "\n"
+ " %0 Zero-fill the output field if the output is right-justified and the\n"
+ " width of the output is less than the specified field width. Without\n"
+ " %0, right-justified values are prepended with blanks in order to\n"
+ " fill the field.\n"
+ "\n"
+ "Examples: \n"
+ "\n"
+ " ::walk proc | "
+ "::printf \"%-6d %s\\n\" proc_t p_pidp->pid_id p_user.u_psargs\n"
+ " ::walk thread | "
+ "::printf \"%?p %3d %a\\n\" kthread_t . t_pri t_startpc\n"
+ " ::walk zone | "
+ "::printf \"%-40s %20s\\n\" zone_t zone_name zone_nodename\n"
+ " ::walk ire | "
+ "::printf \"%Y %I\\n\" ire_t ire_create_time ire_u.ire4_u.ire4_addr\n"
+ "\n";
+
+ void
+ printf_help(void)
+ {
+ mdb_printf("%s", _mdb_printf_help);
}