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,10 +42,11 @@
 #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,11 +1686,11 @@
                 uint16_t i2;
                 uint8_t i1;
         } u;
 
         if (mdb_ctf_type_resolve(id, &base) == -1) {
-                mdb_warn("could not resolve type\n");
+                mdb_warn("could not resolve type");
                 return (-1);
         }
 
         /*
          * If the user gives -a, then always print out the address of the

@@ -2001,11 +2002,10 @@
 
                 start = end;
                 delim = parse_delimiter(&start);
         }
 
-
         *idp = id;
         *offp = off;
 
         return (0);
 }

@@ -2460,6 +2460,577 @@
             "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);
 }