xref: /illumos-gate/usr/src/common/hexdump/hexdump.c (revision 43379a280422204006ceee5a5c5380444cb515d9)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2024 Oxide Computer Company
14  */
15 
16 /*
17  * A generic hexdump implementation suitable for use in the kernel or userland.
18  * The output style is influenced by mdb's ::dump.
19  */
20 
21 #include <sys/ilstr.h>
22 #include <sys/kmem.h>
23 #include <sys/stdbool.h>
24 #include <sys/sysmacros.h>
25 #include <sys/hexdump.h>
26 
27 #ifdef _KERNEL
28 #include <sys/sunddi.h>
29 #include <sys/errno.h>
30 #else
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <strings.h>
34 #include <errno.h>
35 #endif
36 
37 #define	HD_DEFGROUPING		1
38 #define	HD_DEFWIDTH		16
39 
40 /*
41  * We use a simple comparison to determine whether a character is printable in
42  * the ASCII table. This is unaffected by locale settings and provides
43  * consistent results regardless of the environment.
44  */
45 #define	HEXDUMP_PRINTABLE(c) ((c) >= ' ' && (c) <= '~')
46 
47 typedef struct {
48 	uint64_t	hdp_flags;
49 	uint8_t		hdp_grouping;	/* display bytes in groups of.. */
50 	uint8_t		hdp_width;	/* bytes per row */
51 	uint8_t		hdp_indent;	/* indent size */
52 
53 	const uint8_t	*hdp_data;	/* Data to dump */
54 	uint64_t	hdp_len;	/* Length of data */
55 
56 	uint8_t		hdp_addrwidth;	/* Width of address field */
57 	uint64_t	hdp_bufaddr;	/* The supplied buffer address */
58 	uint64_t	hdp_baseaddr;	/* The base address for aligned print */
59 	uint64_t	hdp_adj;	/* The adjustment (buf - base) */
60 
61 	ilstr_t		hdp_buf;	/* Working buffer */
62 	ilstr_t		hdp_pbuf;	/* Previous line */
63 	uint64_t	hdp_offset;	/* Current offset */
64 	uint8_t		hdp_marker;	/* Marker offset */
65 } hexdump_param_t;
66 
67 #ifdef _KERNEL
68 /*
69  * In the kernel we do not have flsll, and ddi_fls is not available until
70  * genunix is loaded. Since we wish to be usable before that point, provide a
71  * simple alternative.
72  */
73 static int
flsll(unsigned long x)74 flsll(unsigned long x)
75 {
76 	int pos = 0;
77 
78 	while (x != 0) {
79 		pos++;
80 		x >>= 1;
81 	}
82 
83 	return (pos);
84 }
85 #endif /* _KERNEL */
86 
87 void
hexdump_init(hexdump_t * h)88 hexdump_init(hexdump_t *h)
89 {
90 	bzero(h, sizeof (*h));
91 	h->h_grouping = HD_DEFGROUPING;
92 	h->h_width = HD_DEFWIDTH;
93 }
94 
95 void
hexdump_fini(hexdump_t * h __unused)96 hexdump_fini(hexdump_t *h __unused)
97 {
98 }
99 
100 void
hexdump_set_width(hexdump_t * h,uint8_t width)101 hexdump_set_width(hexdump_t *h, uint8_t width)
102 {
103 	if (width == 0)
104 		h->h_width = HD_DEFWIDTH;
105 	else
106 		h->h_width = width;
107 }
108 
109 void
hexdump_set_grouping(hexdump_t * h,uint8_t grouping)110 hexdump_set_grouping(hexdump_t *h, uint8_t grouping)
111 {
112 	if (grouping == 0)
113 		h->h_grouping = 1;
114 	else
115 		h->h_grouping = grouping;
116 }
117 
118 void
hexdump_set_indent(hexdump_t * h,uint8_t indent)119 hexdump_set_indent(hexdump_t *h, uint8_t indent)
120 {
121 	h->h_indent = indent;
122 }
123 
124 void
hexdump_set_addr(hexdump_t * h,uint64_t addr)125 hexdump_set_addr(hexdump_t *h, uint64_t addr)
126 {
127 	h->h_addr = addr;
128 }
129 
130 void
hexdump_set_marker(hexdump_t * h,uint8_t marker)131 hexdump_set_marker(hexdump_t *h, uint8_t marker)
132 {
133 	h->h_marker = marker;
134 }
135 
136 void
hexdump_set_buf(hexdump_t * h,uint8_t * buf,size_t buflen)137 hexdump_set_buf(hexdump_t *h, uint8_t *buf, size_t buflen)
138 {
139 	h->h_buf = buf;
140 	h->h_buflen = buflen;
141 }
142 
143 static void
hexdump_space(hexdump_param_t * hdp,uint_t idx)144 hexdump_space(hexdump_param_t *hdp, uint_t idx)
145 {
146 	if (idx != 0 && idx % hdp->hdp_grouping == 0) {
147 		ilstr_append_char(&hdp->hdp_buf, ' ');
148 		/*
149 		 * If we are putting each byte in its own group, add an extra
150 		 * space every 8 bytes to improve readability.
151 		 */
152 		if (hdp->hdp_grouping == 1 && idx % 8 == 0)
153 			ilstr_append_char(&hdp->hdp_buf, ' ');
154 	}
155 	if (idx != 0 && hdp->hdp_flags & HDF_DOUBLESPACE)
156 		ilstr_append_char(&hdp->hdp_buf, ' ');
157 }
158 
159 static void
hexdump_header(hexdump_param_t * hdp)160 hexdump_header(hexdump_param_t *hdp)
161 {
162 	int markerpos = -1;
163 
164 	if (hdp->hdp_indent != 0)
165 		ilstr_aprintf(&hdp->hdp_buf, "%*s", hdp->hdp_indent, "");
166 
167 	if (hdp->hdp_flags & HDF_ADDRESS)
168 		ilstr_aprintf(&hdp->hdp_buf, "%*s   ", hdp->hdp_addrwidth, "");
169 
170 	for (uint_t i = 0; i < hdp->hdp_width; i++) {
171 		hexdump_space(hdp, i);
172 
173 		if ((hdp->hdp_marker > 0 && i == hdp->hdp_marker) ||
174 		    ((hdp->hdp_flags & HDF_ALIGN) &&
175 		    hdp->hdp_baseaddr + i == hdp->hdp_bufaddr)) {
176 			ilstr_append_str(&hdp->hdp_buf, "\\/");
177 			markerpos = i;
178 		} else {
179 			ilstr_aprintf(&hdp->hdp_buf, "%2x",
180 			    (i + hdp->hdp_offset) & 0xf);
181 		}
182 	}
183 
184 	if (hdp->hdp_flags & HDF_ASCII) {
185 		ilstr_append_str(&hdp->hdp_buf, "   ");
186 		for (uint_t i = 0; i < hdp->hdp_width; i++) {
187 			if (markerpos != -1 && markerpos == i) {
188 				ilstr_append_char(&hdp->hdp_buf, 'v');
189 			} else {
190 				ilstr_aprintf(&hdp->hdp_buf, "%x",
191 				    (i + hdp->hdp_offset) & 0xf);
192 			}
193 		}
194 	}
195 }
196 
197 static void
hexdump_data(hexdump_param_t * hdp)198 hexdump_data(hexdump_param_t *hdp)
199 {
200 	uint64_t addr = hdp->hdp_baseaddr + hdp->hdp_offset;
201 
202 	if (hdp->hdp_indent != 0)
203 		ilstr_aprintf(&hdp->hdp_buf, "%*s", hdp->hdp_indent, "");
204 
205 	if (hdp->hdp_flags & HDF_ADDRESS) {
206 		ilstr_aprintf(&hdp->hdp_buf, "%0*llx:  ",
207 		    hdp->hdp_addrwidth, addr);
208 	}
209 
210 	for (uint_t i = 0; i < hdp->hdp_width; i++) {
211 		if (hdp->hdp_offset + i >= hdp->hdp_len) {
212 			/*
213 			 * If we are not going to an ascii table, we don't need
214 			 * to pad this out with spaces to the right.
215 			 */
216 			if (!(hdp->hdp_flags & HDF_ASCII))
217 				break;
218 		}
219 		hexdump_space(hdp, i);
220 		if (addr + i < hdp->hdp_bufaddr ||
221 		    hdp->hdp_offset + i >= hdp->hdp_len) {
222 			ilstr_append_str(&hdp->hdp_buf, "  ");
223 		} else {
224 			ilstr_aprintf(&hdp->hdp_buf, "%02x",
225 			    hdp->hdp_data[hdp->hdp_offset + i - hdp->hdp_adj]);
226 		}
227 	}
228 
229 	if (hdp->hdp_flags & HDF_ASCII) {
230 		ilstr_append_str(&hdp->hdp_buf, " | ");
231 		for (uint_t i = 0; i < hdp->hdp_width; i++) {
232 			if (hdp->hdp_offset + i >= hdp->hdp_len)
233 				break;
234 			if (addr + i < hdp->hdp_bufaddr) {
235 				ilstr_append_char(&hdp->hdp_buf, ' ');
236 			} else {
237 				char c = hdp->hdp_data[hdp->hdp_offset + i
238 				    - hdp->hdp_adj];
239 
240 				ilstr_append_char(&hdp->hdp_buf,
241 				    HEXDUMP_PRINTABLE(c) ? c : '.');
242 			}
243 		}
244 	}
245 }
246 
247 static int
hexdump_output(hexdump_param_t * hdp,bool hdr,hexdump_cb_f cb,void * cbarg)248 hexdump_output(hexdump_param_t *hdp, bool hdr, hexdump_cb_f cb, void *cbarg)
249 {
250 	int ret;
251 
252 	/*
253 	 * The callback is invoked with an address of UINT64_MAX for the header
254 	 * row.
255 	 */
256 	ret = cb(cbarg, hdr ? UINT64_MAX : hdp->hdp_bufaddr + hdp->hdp_offset,
257 	    ilstr_cstr(&hdp->hdp_buf), ilstr_len(&hdp->hdp_buf));
258 	ilstr_reset(&hdp->hdp_buf);
259 
260 	return (ret);
261 }
262 
263 static bool
hexdump_squishable(hexdump_param_t * hdp)264 hexdump_squishable(hexdump_param_t *hdp)
265 {
266 	const char *str = ilstr_cstr(&hdp->hdp_buf);
267 	const char *pstr = ilstr_cstr(&hdp->hdp_pbuf);
268 
269 	if (hdp->hdp_flags & HDF_ADDRESS) {
270 		size_t strl = ilstr_len(&hdp->hdp_buf);
271 		size_t pstrl = ilstr_len(&hdp->hdp_pbuf);
272 
273 		if (strl <= hdp->hdp_addrwidth || pstrl <= hdp->hdp_addrwidth)
274 			return (false);
275 
276 		str += hdp->hdp_addrwidth;
277 		pstr += hdp->hdp_addrwidth;
278 	}
279 
280 	return (strcmp(str, pstr) == 0);
281 }
282 
283 int
hexdumph(hexdump_t * h,const uint8_t * data,size_t len,hexdump_flag_t flags,hexdump_cb_f cb,void * cbarg)284 hexdumph(hexdump_t *h, const uint8_t *data, size_t len, hexdump_flag_t flags,
285     hexdump_cb_f cb, void *cbarg)
286 {
287 	hexdump_param_t hdp = { 0 };
288 	int ret = 0;
289 
290 	if (data == NULL)
291 		return (0);
292 
293 	/*
294 	 * We can use a pre-allocated buffer for the constructed lines if the
295 	 * caller has provided one. One use of this is to invoke this routine
296 	 * early in boot before kmem is ready.
297 	 */
298 #ifdef _KERNEL
299 	if (kmem_ready == 0 && (h == NULL || h->h_buf == NULL)) {
300 		panic("hexdump before kmem is ready requires pre-allocated "
301 		    "buffer");
302 	}
303 #endif
304 	if (h != NULL && h->h_buf != NULL) {
305 		ilstr_init_prealloc(&hdp.hdp_buf, (char *)h->h_buf,
306 		    h->h_buflen);
307 		/* We do not support HDF_DEDUP with a pre-allocated buffer */
308 		flags &= ~HDF_DEDUP;
309 	} else {
310 		/*
311 		 * The KM_SLEEP flag is ignored outside kernel context. If a
312 		 * memory allocation fails in userland that will result in the
313 		 * caller ultimately receiving -1 with errno set to ENOMEM.
314 		 */
315 		ilstr_init(&hdp.hdp_buf, KM_SLEEP);
316 		if (flags & HDF_DEDUP)
317 			ilstr_init(&hdp.hdp_pbuf, KM_SLEEP);
318 	}
319 
320 	hdp.hdp_flags = flags;
321 	hdp.hdp_grouping = HD_DEFGROUPING;
322 	hdp.hdp_width = HD_DEFWIDTH;
323 	hdp.hdp_data = data;
324 	hdp.hdp_len = len;
325 
326 	hdp.hdp_bufaddr = hdp.hdp_baseaddr = 0;
327 	hdp.hdp_offset = hdp.hdp_marker = hdp.hdp_adj = 0;
328 
329 	if (h != NULL) {
330 		hdp.hdp_bufaddr = hdp.hdp_baseaddr = h->h_addr;
331 		hdp.hdp_width = h->h_width;
332 		hdp.hdp_grouping = h->h_grouping;
333 		hdp.hdp_indent = h->h_indent;
334 		hdp.hdp_marker = h->h_marker;
335 	}
336 
337 	if (hdp.hdp_width == 0)
338 		hdp.hdp_width = HD_DEFWIDTH;
339 	if (hdp.hdp_grouping == 0)
340 		hdp.hdp_grouping = HD_DEFGROUPING;
341 	if (hdp.hdp_marker > HD_DEFWIDTH)
342 		hdp.hdp_marker = 0;
343 
344 	/*
345 	 * If the grouping isn't a power of two, or the display width is not
346 	 * evenly divisible by the grouping we ignore the specified grouping
347 	 * and default to 4.
348 	 */
349 	if (!ISP2(hdp.hdp_grouping) || hdp.hdp_width % hdp.hdp_grouping != 0)
350 		hdp.hdp_grouping = 4;
351 
352 	/*
353 	 * Determine how much space is required for the address field.
354 	 * We need one character for every four bits of the final address.
355 	 */
356 	hdp.hdp_addrwidth = (flsll(hdp.hdp_baseaddr + len) + 3) / 4;
357 
358 	/*
359 	 * If alignment was requested and the address is not already aligned,
360 	 * adjust the starting address down to the nearest boundary. Missing
361 	 * data will be displayed as spaces.
362 	 */
363 	if (flags & HDF_ALIGN) {
364 		hdp.hdp_baseaddr = P2ALIGN(hdp.hdp_baseaddr, hdp.hdp_width);
365 		hdp.hdp_adj = hdp.hdp_bufaddr - hdp.hdp_baseaddr;
366 		hdp.hdp_len += hdp.hdp_adj;
367 	}
368 
369 	if (flags & HDF_HEADER) {
370 		hexdump_header(&hdp);
371 		if ((ret = hexdump_output(&hdp, true, cb, cbarg)) != 0)
372 			goto out;
373 	}
374 
375 	bool squishing = false;
376 
377 	while (hdp.hdp_offset < hdp.hdp_len) {
378 		hexdump_data(&hdp);
379 		if (flags & HDF_DEDUP) {
380 			if (hexdump_squishable(&hdp)) {
381 				ilstr_reset(&hdp.hdp_buf);
382 				if (squishing) {
383 					hdp.hdp_offset += hdp.hdp_width;
384 					continue;
385 				}
386 				ilstr_append_str(&hdp.hdp_buf, "*");
387 				squishing = true;
388 			} else {
389 				ilstr_reset(&hdp.hdp_pbuf);
390 				ilstr_append_str(&hdp.hdp_pbuf,
391 				    ilstr_cstr(&hdp.hdp_buf));
392 				squishing = false;
393 			}
394 		}
395 		if ((ret = hexdump_output(&hdp, false, cb, cbarg)) != 0)
396 			break;
397 		hdp.hdp_offset += hdp.hdp_width;
398 	}
399 
400 out:
401 	/*
402 	 * If ret is not zero then it is a value returned by a callback and we
403 	 * return that to the caller as-is. Otherwise we translate any errors
404 	 * from ilstr.
405 	 */
406 	if (ret == 0) {
407 		ilstr_errno_t ilerr = ilstr_errno(&hdp.hdp_buf);
408 
409 		switch (ilerr) {
410 		case ILSTR_ERROR_OK:
411 			break;
412 		case ILSTR_ERROR_NOMEM:
413 			ret = ENOMEM;
414 			break;
415 		case ILSTR_ERROR_OVERFLOW:
416 			ret = EOVERFLOW;
417 			break;
418 		case ILSTR_ERROR_PRINTF:
419 		default:
420 			/*
421 			 * We don't expect to end up here but we should return
422 			 * an error if we somehow do.
423 			 */
424 			ret = EIO;
425 			break;
426 		}
427 #ifndef _KERNEL
428 		if (ret != 0) {
429 			errno = ret;
430 			ret = -1;
431 		}
432 #endif
433 	}
434 
435 	ilstr_fini(&hdp.hdp_buf);
436 	if (flags & HDF_DEDUP)
437 		ilstr_fini(&hdp.hdp_pbuf);
438 
439 	return (ret);
440 }
441 
442 int
hexdump(const uint8_t * data,size_t len,hexdump_flag_t flags,hexdump_cb_f cb,void * cbarg)443 hexdump(const uint8_t *data, size_t len, hexdump_flag_t flags, hexdump_cb_f cb,
444     void *cbarg)
445 {
446 	return (hexdumph(NULL, data, len, flags, cb, cbarg));
447 }
448 
449 #ifndef _KERNEL
450 static int
hexdump_file_cb(void * arg,uint64_t addr __unused,const char * str,size_t len __unused)451 hexdump_file_cb(void *arg, uint64_t addr __unused, const char *str,
452     size_t len __unused)
453 {
454 	FILE *fp = (FILE *)arg;
455 
456 	if (fprintf(fp, "%s\n", str) < 0)
457 		return (-1);
458 	return (0);
459 }
460 
461 int
hexdump_fileh(hexdump_t * h,const uint8_t * data,size_t len,hexdump_flag_t flags,FILE * fp)462 hexdump_fileh(hexdump_t *h, const uint8_t *data, size_t len,
463     hexdump_flag_t flags, FILE *fp)
464 {
465 	return (hexdumph(h, data, len, flags, hexdump_file_cb, fp));
466 }
467 
468 int
hexdump_file(const uint8_t * data,size_t len,hexdump_flag_t flags,FILE * fp)469 hexdump_file(const uint8_t *data, size_t len, hexdump_flag_t flags, FILE *fp)
470 {
471 	return (hexdumph(NULL, data, len, flags, hexdump_file_cb, fp));
472 }
473 #endif /* _KERNEL */
474