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); }