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