1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29#include <mdb/mdb_dump.h>
30#include <mdb/mdb_modapi.h>
31#include <mdb/mdb_nv.h>
32#include <mdb/mdb_err.h>
33#include <mdb/mdb.h>
34#include <limits.h>
35
36#define	DUMP_PARAGRAPH	16
37#define	DUMP_WIDTH(x)	(DUMP_PARAGRAPH * ((((x) >> 16) & 0xf) + 1))
38#define	DUMP_GROUP(x)	((((x) >> 20) & 0xff) + 1)
39#define	DUMP_MAXWIDTH	DUMP_WIDTH(MDB_DUMP_WIDTH(0x10))
40
41/*
42 * This is the implementation of mdb's generic hexdump facility (though
43 * not named such in case we decide to add support for other radices).
44 * While it is possible to call mdb_dump_internal directly, it is
45 * recommended that you use mdb_dumpptr or mdb_dump64 instead.
46 */
47
48
49/*
50 * Output the header for the dump.  pad is the width of the address
51 * field, and offset is the index of the byte that we want highlighted.
52 * If the output isn't MDB_DUMP_ALIGNed, we use offset to adjust the
53 * labels to reflect the true least significant address nibble.
54 */
55
56static void
57mdb_dump_header(int flags, int pad, int offset)
58{
59	int	nalign = !(flags & MDB_DUMP_ALIGN);
60	int	group = DUMP_GROUP(flags);
61	int	width = DUMP_WIDTH(flags);
62	int	i;
63
64	mdb_printf("%*s  ", pad, "");
65	for (i = 0; i < width; i++) {
66		if (!(i % group))
67			mdb_printf((group == 1 && i && !(i % 8)) ? "  " : " ");
68		if (i == offset && !nalign)
69			mdb_printf("\\/");
70		else
71			mdb_printf("%2x", (i + (nalign * offset)) & 0xf);
72	}
73
74	if (flags & MDB_DUMP_ASCII) {
75		mdb_printf("  ");
76		for (i = 0; i < width; i++) {
77			if (i == offset && !nalign)
78				mdb_printf("v");
79			else
80				mdb_printf("%x", (i + (nalign * offset)) & 0xf);
81		}
82	}
83
84	mdb_printf("\n");
85}
86
87
88/*
89 * Output a line of data.  pad is as defined above.  A non-zero lmargin
90 * and/or rmargin indicate a set of bytes that shouldn't be printed.
91 */
92
93static void
94mdb_dump_data(uint64_t addr, uchar_t *buf, int flags, int pad,
95	int lmargin, int rmargin)
96{
97	uchar_t	abuf[DUMP_MAXWIDTH + 1];
98	int	group = DUMP_GROUP(flags);
99	int	width = DUMP_WIDTH(flags);
100	int	i;
101#ifdef	_LITTLE_ENDIAN
102	int	flip = FALSE;
103
104	if (flags & MDB_DUMP_ENDIAN)
105		flip = TRUE;
106#endif
107
108	mdb_printf("%0*llx: ", pad, addr);
109
110	for (i = 0; i < width; i++) {
111		if (!(i % group))
112			mdb_printf((group == 1 && i && !(i % 8)) ? "  " : " ");
113		if (i < lmargin || (width - i) <= rmargin) {
114			mdb_printf("  ");
115#ifdef	_LITTLE_ENDIAN
116		} else if (flip) {
117			int j = group * ((i / group) + 1) - (i % group) - 1;
118			mdb_printf("%02x", buf[j]);
119#endif
120		} else {
121			mdb_printf("%02x", buf[i]);
122		}
123	}
124
125	if (flags & MDB_DUMP_ASCII) {
126		for (i = 0; i < width; i++)
127			if (i < lmargin || (width - i) <= rmargin)
128				abuf[i] = ' ';
129			else if (buf[i] < ' ' || buf[i] > '~')
130				abuf[i] = '.';
131			else
132				abuf[i] = buf[i];
133		abuf[width] = '\0';
134		mdb_printf("  %s", abuf);
135	}
136
137	mdb_printf("\n");
138}
139
140
141/*
142 * Given an address and a length, compute the number of characters
143 * needed to display addresses within that range.
144 */
145
146static int
147mdb_dump_pad(uint64_t addr, uint64_t len, int flags, int bytes)
148{
149	uint64_t x;
150	int bits;
151
152	if (flags & MDB_DUMP_PEDANT) {
153		/*
154		 * Assume full width pointers
155		 */
156		bits = NBBY * bytes;
157	} else {
158		/*
159		 * Vary width based on address and length, but first
160		 * check to see if the address is relevant.
161		 */
162		if (len > 1 || (addr && len == 1))
163			len--;
164		if (flags & MDB_DUMP_RELATIVE)
165			x = len;
166		else
167			x = len + addr;
168
169		bits = 0;
170		while (x) {
171			bits++;
172			x >>= 1;
173		}
174	}
175
176	return ((bits + 3) / 4);
177}
178
179
180/*
181 * The main dump routine, called by mdb_dump64 and (indirectly) by
182 * mdb_dumpptr.  Arguments:
183 *   addr  - the address to start dumping at
184 *   len   - the amount of data to dump
185 *   flags - to tune operation (see mdb_modapi.h)
186 *   func  - callback function used to obtain data
187 *   arg   - argument to pass to callback function
188 *   bytes - size of pointer type
189 */
190
191int
192mdb_dump_internal(uint64_t addr, uint64_t len, int flags, mdb_dump64_cb_t func,
193	void *arg, int bytes)
194{
195	uchar_t	buffers[2][DUMP_MAXWIDTH];
196	uchar_t	*buf, *pbuf;
197	uint64_t i;
198	ssize_t	j;
199	uint64_t addrmax;
200	uint64_t offset;	/* bytes between first position and addr */
201	uint64_t reqlen = len;	/* requested length */
202	int	l, r;		/* left and right margins */
203	int	pskip;		/* previous line was skipped */
204	int	pvalid;		/* previous line was valid (we may skip) */
205	int	bread, bwanted;	/* used to handle partial reads */
206	int	pad, n;
207	int	group, width;
208	int	err = 0;
209
210	addrmax = (1LL << (bytes * NBBY - 1)) - 1 + (1LL << (bytes * NBBY - 1));
211
212	/*
213	 * Ensure that len doesn't wrap around the end of addressable
214	 * memory.  Note that because we take an address and a length,
215	 * it isn't possible to dump from 0 to UINT64_MAX if
216	 * MDB_DUMP_TRIM is set.
217	 */
218	if (len && (len - 1 > addrmax - addr)) {
219		len = addrmax - addr;
220		if (addr || (addrmax < UINT64_MAX))
221			len++;
222	}
223
224	/*
225	 * If a) the grouping isn't a power of two, or
226	 *    b) the display width is not evenly divisible by the grouping
227	 * we ignore the specified grouping (and default to 4).
228	 */
229	group = DUMP_GROUP(flags);
230	width = DUMP_WIDTH(flags);
231	if (((group - 1) & group) || (width % group)) {
232		group = 4;
233		flags = (flags & 0xfffff) | MDB_DUMP_GROUP(group);
234	}
235
236	/*
237	 * If we are reordering bytes to adjust for endianness, turn
238	 * off text output, headers, and alignment to cut down on the
239	 * number of special cases (and confusing output).  For
240	 * correctness, we will continue to observe MDB_DUMP_TRIM, but
241	 * will truncate output if the specified length isn't a
242	 * multiple of the grouping.
243	 */
244	if (flags & MDB_DUMP_ENDIAN) {
245		flags &= ~(MDB_DUMP_ALIGN | MDB_DUMP_HEADER | MDB_DUMP_ASCII);
246		if (flags & MDB_DUMP_TRIM)
247			len -= len % group;
248	}
249
250	/*
251	 * If we are interested in seeing the data indexed relative to
252	 * the starting location, paragraph alignment is irrelevant.
253	 * The left margin will always be 0.
254	 */
255	if (flags & MDB_DUMP_RELATIVE) {
256		flags &= ~MDB_DUMP_ALIGN;
257		l = 0;
258	} else {
259		l = addr % DUMP_PARAGRAPH;
260	}
261
262	/*
263	 * Compute the width of our addresses, and adjust our starting
264	 * point based on the address and the state of the alignment
265	 * flag.
266	 */
267	pad = mdb_dump_pad(addr, len, flags, bytes);
268	if (flags & MDB_DUMP_ALIGN) {
269		len += l;
270		addr -= l;
271		offset = l;
272	} else {
273		offset = 0;
274	}
275
276	/*
277	 * Display the header (if appropriate), using the left margin
278	 * to determine what our column header offset should be.
279	 */
280	if (flags & MDB_DUMP_HEADER)
281		mdb_dump_header(flags, pad, l);
282
283	/*
284	 * If we aren't trimming and aligning the output, the left
285	 * margin is now irrelevant and should be zeroed.
286	 */
287	if (!(flags & MDB_DUMP_TRIM) || !(flags & MDB_DUMP_ALIGN))
288		l = 0;
289
290	/*
291	 * We haven't skipped the previous line, it isn't valid to skip
292	 * the current line, and we use buffer 0 first.  lint doesn't
293	 * realize that this implies pbuf won't be accessed until after
294	 * it is set, so we explicitly initialize that here, too.
295	 */
296	pskip = pvalid = FALSE;
297	pbuf = NULL;
298	n = 0;
299	r = 0;
300
301	for (i = 0; i < len && r == 0; i += width) {
302		/*
303		 * Select the current buffer.
304		 */
305		buf = buffers[n];
306
307		/*
308		 * We have a right margin only if we are on the last
309		 * line and either (1) MDB_DUMP_TRIM is set or (2) our
310		 * untrimmed output would require reading past the end
311		 * of addressable memory.  In either case, we clear
312		 * pvalid since we don't want to skip the last line.
313		 */
314		if ((uint64_t)width >= len - i) {
315			pvalid = FALSE;
316			if (flags & MDB_DUMP_TRIM)
317				r = width - (len - i);
318			if ((uint64_t)width - 1 > addrmax - (addr + i)) {
319				int nr = width - (addrmax - (addr + i)) - 1;
320				r = MAX(r, nr);
321			}
322		}
323
324		/*
325		 * Read data into the current buffer, obeying the left
326		 * and right margins.
327		 *
328		 * We handle read(2)-style partial results by
329		 * repeatedly calling the callback until we fill the
330		 * buffer, we get a 0 (end of file), or we get a -1
331		 * (error).  We take care to never read the same data
332		 * twice, though.
333		 *
334		 * mdb(1)-style partial results (i.e. EMDB_PARTIAL) are
335		 * treated like any other error.  If more exotic
336		 * handling is desired, the caller is free to wrap
337		 * their callback with an auxiliary function.  See
338		 * mdb_dumpptr and mdb_dump64 for examples of this.
339		 */
340		bread = l;
341		bwanted = width - r;
342		while (bread < bwanted) {
343			j = func(buf + bread, bwanted - bread,
344			    addr + i + bread, arg);
345			if (j <= 0) {
346				if (i + bread < offset) {
347					l++;
348					j = 1;
349				} else {
350					r += bwanted - bread;
351					pvalid = FALSE;
352					if (j == -1)
353						err = errno;
354					if (bread == l) {
355						i += width;
356						goto out;
357					}
358					break;
359				}
360			}
361			bread += j;
362		}
363
364		/*
365		 * If we are eliminating repeated lines, AND it is
366		 * valid to eliminate this line, AND the current line
367		 * is the same as the previous line, don't print the
368		 * current line.  If we didn't skip the previous line,
369		 * print an asterisk and set the previous-line-skipped
370		 * flag.
371		 *
372		 * Otherwise, print the line and clear the
373		 * previous-line-skipped flag.
374		 */
375		if ((flags & MDB_DUMP_SQUISH) && pvalid &&
376		    (memcmp(buf, pbuf, width) == 0)) {
377			if (!pskip) {
378				mdb_printf("*\n");
379				pskip = TRUE;
380			}
381		} else {
382			if (flags & MDB_DUMP_RELATIVE)
383				mdb_dump_data(i, buf, flags, pad, l, r);
384			else
385				mdb_dump_data(addr + i, buf, flags, pad, l, r);
386			pskip = FALSE;
387		}
388
389		/*
390		 * If we have a non-zero left margin then we don't have
391		 * a full buffer of data and we shouldn't try to skip
392		 * the next line.  It doesn't matter if the right
393		 * margin is non-zero since we'll fall out of the loop.
394		 */
395		if (!l)
396			pvalid = TRUE;
397
398		/*
399		 * Swap buffers, and zero the left margin.
400		 */
401		n = (n + 1) % 2;
402		pbuf = buf;
403		l = 0;
404	}
405
406out:
407	/*
408	 * If we successfully dumped everything, update . to be the
409	 * address following that of the last byte requested.
410	 */
411	if (i - r - offset >= reqlen) {
412		if (flags & MDB_DUMP_NEWDOT)
413			mdb_set_dot(addr + offset + reqlen);
414	} else if (err) {
415		errno = err;
416		mdb_warn("failed to read data at %#llx", addr + i - r);
417		return (-1);
418	}
419
420	return (0);
421}
422