xref: /illumos-gate/usr/src/cmd/mdb/common/mdb/mdb_dump.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
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 
56 static void
57 mdb_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 
93 static void
94 mdb_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 
146 static int
147 mdb_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 
191 int
192 mdb_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 
406 out:
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