root/usr/src/cmd/mdb/common/mdb/mdb_dump.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <mdb/mdb_dump.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_nv.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb.h>
#include <limits.h>

#define DUMP_PARAGRAPH  16
#define DUMP_WIDTH(x)   (DUMP_PARAGRAPH * ((((x) >> 16) & 0xf) + 1))
#define DUMP_GROUP(x)   ((((x) >> 20) & 0xff) + 1)
#define DUMP_MAXWIDTH   DUMP_WIDTH(MDB_DUMP_WIDTH(0x10))

/*
 * This is the implementation of mdb's generic hexdump facility (though
 * not named such in case we decide to add support for other radices).
 * While it is possible to call mdb_dump_internal directly, it is
 * recommended that you use mdb_dumpptr or mdb_dump64 instead.
 */


/*
 * Output the header for the dump.  pad is the width of the address
 * field, and offset is the index of the byte that we want highlighted.
 * If the output isn't MDB_DUMP_ALIGNed, we use offset to adjust the
 * labels to reflect the true least significant address nibble.
 */

static void
mdb_dump_header(int flags, int pad, int offset)
{
        int     nalign = !(flags & MDB_DUMP_ALIGN);
        int     group = DUMP_GROUP(flags);
        int     width = DUMP_WIDTH(flags);
        int     i;

        mdb_printf("%*s  ", pad, "");
        for (i = 0; i < width; i++) {
                if (!(i % group))
                        mdb_printf((group == 1 && i && !(i % 8)) ? "  " : " ");
                if (i == offset && !nalign)
                        mdb_printf("\\/");
                else
                        mdb_printf("%2x", (i + (nalign * offset)) & 0xf);
        }

        if (flags & MDB_DUMP_ASCII) {
                mdb_printf("  ");
                for (i = 0; i < width; i++) {
                        if (i == offset && !nalign)
                                mdb_printf("v");
                        else
                                mdb_printf("%x", (i + (nalign * offset)) & 0xf);
                }
        }

        mdb_printf("\n");
}


/*
 * Output a line of data.  pad is as defined above.  A non-zero lmargin
 * and/or rmargin indicate a set of bytes that shouldn't be printed.
 */

static void
mdb_dump_data(uint64_t addr, uchar_t *buf, int flags, int pad,
        int lmargin, int rmargin)
{
        uchar_t abuf[DUMP_MAXWIDTH + 1];
        int     group = DUMP_GROUP(flags);
        int     width = DUMP_WIDTH(flags);
        int     i;
#ifdef  _LITTLE_ENDIAN
        int     flip = FALSE;

        if (flags & MDB_DUMP_ENDIAN)
                flip = TRUE;
#endif

        mdb_printf("%0*llx: ", pad, addr);

        for (i = 0; i < width; i++) {
                if (!(i % group))
                        mdb_printf((group == 1 && i && !(i % 8)) ? "  " : " ");
                if (i < lmargin || (width - i) <= rmargin) {
                        mdb_printf("  ");
#ifdef  _LITTLE_ENDIAN
                } else if (flip) {
                        int j = group * ((i / group) + 1) - (i % group) - 1;
                        mdb_printf("%02x", buf[j]);
#endif
                } else {
                        mdb_printf("%02x", buf[i]);
                }
        }

        if (flags & MDB_DUMP_ASCII) {
                for (i = 0; i < width; i++)
                        if (i < lmargin || (width - i) <= rmargin)
                                abuf[i] = ' ';
                        else if (buf[i] < ' ' || buf[i] > '~')
                                abuf[i] = '.';
                        else
                                abuf[i] = buf[i];
                abuf[width] = '\0';
                mdb_printf("  %s", abuf);
        }

        mdb_printf("\n");
}


/*
 * Given an address and a length, compute the number of characters
 * needed to display addresses within that range.
 */

static int
mdb_dump_pad(uint64_t addr, uint64_t len, int flags, int bytes)
{
        uint64_t x;
        int bits;

        if (flags & MDB_DUMP_PEDANT) {
                /*
                 * Assume full width pointers
                 */
                bits = NBBY * bytes;
        } else {
                /*
                 * Vary width based on address and length, but first
                 * check to see if the address is relevant.
                 */
                if (len > 1 || (addr && len == 1))
                        len--;
                if (flags & MDB_DUMP_RELATIVE)
                        x = len;
                else
                        x = len + addr;

                bits = 0;
                while (x) {
                        bits++;
                        x >>= 1;
                }
        }

        return ((bits + 3) / 4);
}


/*
 * The main dump routine, called by mdb_dump64 and (indirectly) by
 * mdb_dumpptr.  Arguments:
 *   addr  - the address to start dumping at
 *   len   - the amount of data to dump
 *   flags - to tune operation (see mdb_modapi.h)
 *   func  - callback function used to obtain data
 *   arg   - argument to pass to callback function
 *   bytes - size of pointer type
 */

int
mdb_dump_internal(uint64_t addr, uint64_t len, int flags, mdb_dump64_cb_t func,
        void *arg, int bytes)
{
        uchar_t buffers[2][DUMP_MAXWIDTH];
        uchar_t *buf, *pbuf;
        uint64_t i;
        ssize_t j;
        uint64_t addrmax;
        uint64_t offset;        /* bytes between first position and addr */
        uint64_t reqlen = len;  /* requested length */
        int     l, r;           /* left and right margins */
        int     pskip;          /* previous line was skipped */
        int     pvalid;         /* previous line was valid (we may skip) */
        int     bread, bwanted; /* used to handle partial reads */
        int     pad, n;
        int     group, width;
        int     err = 0;

        addrmax = (1LL << (bytes * NBBY - 1)) - 1 + (1LL << (bytes * NBBY - 1));

        /*
         * Ensure that len doesn't wrap around the end of addressable
         * memory.  Note that because we take an address and a length,
         * it isn't possible to dump from 0 to UINT64_MAX if
         * MDB_DUMP_TRIM is set.
         */
        if (len && (len - 1 > addrmax - addr)) {
                len = addrmax - addr;
                if (addr || (addrmax < UINT64_MAX))
                        len++;
        }

        /*
         * If a) the grouping isn't a power of two, or
         *    b) the display width is not evenly divisible by the grouping
         * we ignore the specified grouping (and default to 4).
         */
        group = DUMP_GROUP(flags);
        width = DUMP_WIDTH(flags);
        if (((group - 1) & group) || (width % group)) {
                group = 4;
                flags = (flags & 0xfffff) | MDB_DUMP_GROUP(group);
        }

        /*
         * If we are reordering bytes to adjust for endianness, turn
         * off text output, headers, and alignment to cut down on the
         * number of special cases (and confusing output).  For
         * correctness, we will continue to observe MDB_DUMP_TRIM, but
         * will truncate output if the specified length isn't a
         * multiple of the grouping.
         */
        if (flags & MDB_DUMP_ENDIAN) {
                flags &= ~(MDB_DUMP_ALIGN | MDB_DUMP_HEADER | MDB_DUMP_ASCII);
                if (flags & MDB_DUMP_TRIM)
                        len -= len % group;
        }

        /*
         * If we are interested in seeing the data indexed relative to
         * the starting location, paragraph alignment is irrelevant.
         * The left margin will always be 0.
         */
        if (flags & MDB_DUMP_RELATIVE) {
                flags &= ~MDB_DUMP_ALIGN;
                l = 0;
        } else {
                l = addr % DUMP_PARAGRAPH;
        }

        /*
         * Compute the width of our addresses, and adjust our starting
         * point based on the address and the state of the alignment
         * flag.
         */
        pad = mdb_dump_pad(addr, len, flags, bytes);
        if (flags & MDB_DUMP_ALIGN) {
                len += l;
                addr -= l;
                offset = l;
        } else {
                offset = 0;
        }

        /*
         * Display the header (if appropriate), using the left margin
         * to determine what our column header offset should be.
         */
        if (flags & MDB_DUMP_HEADER)
                mdb_dump_header(flags, pad, l);

        /*
         * If we aren't trimming and aligning the output, the left
         * margin is now irrelevant and should be zeroed.
         */
        if (!(flags & MDB_DUMP_TRIM) || !(flags & MDB_DUMP_ALIGN))
                l = 0;

        /*
         * We haven't skipped the previous line, it isn't valid to skip
         * the current line, and we use buffer 0 first.  lint doesn't
         * realize that this implies pbuf won't be accessed until after
         * it is set, so we explicitly initialize that here, too.
         */
        pskip = pvalid = FALSE;
        pbuf = NULL;
        n = 0;
        r = 0;

        for (i = 0; i < len && r == 0; i += width) {
                /*
                 * Select the current buffer.
                 */
                buf = buffers[n];

                /*
                 * We have a right margin only if we are on the last
                 * line and either (1) MDB_DUMP_TRIM is set or (2) our
                 * untrimmed output would require reading past the end
                 * of addressable memory.  In either case, we clear
                 * pvalid since we don't want to skip the last line.
                 */
                if ((uint64_t)width >= len - i) {
                        pvalid = FALSE;
                        if (flags & MDB_DUMP_TRIM)
                                r = width - (len - i);
                        if ((uint64_t)width - 1 > addrmax - (addr + i)) {
                                int nr = width - (addrmax - (addr + i)) - 1;
                                r = MAX(r, nr);
                        }
                }

                /*
                 * Read data into the current buffer, obeying the left
                 * and right margins.
                 *
                 * We handle read(2)-style partial results by
                 * repeatedly calling the callback until we fill the
                 * buffer, we get a 0 (end of file), or we get a -1
                 * (error).  We take care to never read the same data
                 * twice, though.
                 *
                 * mdb(1)-style partial results (i.e. EMDB_PARTIAL) are
                 * treated like any other error.  If more exotic
                 * handling is desired, the caller is free to wrap
                 * their callback with an auxiliary function.  See
                 * mdb_dumpptr and mdb_dump64 for examples of this.
                 */
                bread = l;
                bwanted = width - r;
                while (bread < bwanted) {
                        j = func(buf + bread, bwanted - bread,
                            addr + i + bread, arg);
                        if (j <= 0) {
                                if (i + bread < offset) {
                                        l++;
                                        j = 1;
                                } else {
                                        r += bwanted - bread;
                                        pvalid = FALSE;
                                        if (j == -1)
                                                err = errno;
                                        if (bread == l) {
                                                i += width;
                                                goto out;
                                        }
                                        break;
                                }
                        }
                        bread += j;
                }

                /*
                 * If we are eliminating repeated lines, AND it is
                 * valid to eliminate this line, AND the current line
                 * is the same as the previous line, don't print the
                 * current line.  If we didn't skip the previous line,
                 * print an asterisk and set the previous-line-skipped
                 * flag.
                 *
                 * Otherwise, print the line and clear the
                 * previous-line-skipped flag.
                 */
                if ((flags & MDB_DUMP_SQUISH) && pvalid &&
                    (memcmp(buf, pbuf, width) == 0)) {
                        if (!pskip) {
                                mdb_printf("*\n");
                                pskip = TRUE;
                        }
                } else {
                        if (flags & MDB_DUMP_RELATIVE)
                                mdb_dump_data(i, buf, flags, pad, l, r);
                        else
                                mdb_dump_data(addr + i, buf, flags, pad, l, r);
                        pskip = FALSE;
                }

                /*
                 * If we have a non-zero left margin then we don't have
                 * a full buffer of data and we shouldn't try to skip
                 * the next line.  It doesn't matter if the right
                 * margin is non-zero since we'll fall out of the loop.
                 */
                if (!l)
                        pvalid = TRUE;

                /*
                 * Swap buffers, and zero the left margin.
                 */
                n = (n + 1) % 2;
                pbuf = buf;
                l = 0;
        }

out:
        /*
         * If we successfully dumped everything, update . to be the
         * address following that of the last byte requested.
         */
        if (i - r - offset >= reqlen) {
                if (flags & MDB_DUMP_NEWDOT)
                        mdb_set_dot(addr + offset + reqlen);
        } else if (err) {
                errno = err;
                mdb_warn("failed to read data at %#llx", addr + i - r);
                return (-1);
        }

        return (0);
}