xref: /illumos-gate/usr/src/common/hexdump/hexdump.c (revision 80b758da23abee3ef0e550f533aada9ce3b1b01f)
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_addrwidth(hexdump_t * h,uint8_t width)131 hexdump_set_addrwidth(hexdump_t *h, uint8_t width)
132 {
133 	h->h_addrwidth = width;
134 }
135 
136 void
hexdump_set_marker(hexdump_t * h,uint8_t marker)137 hexdump_set_marker(hexdump_t *h, uint8_t marker)
138 {
139 	h->h_marker = marker;
140 }
141 
142 void
hexdump_set_buf(hexdump_t * h,uint8_t * buf,size_t buflen)143 hexdump_set_buf(hexdump_t *h, uint8_t *buf, size_t buflen)
144 {
145 	h->h_buf = buf;
146 	h->h_buflen = buflen;
147 }
148 
149 static void
hexdump_space(hexdump_param_t * hdp,uint_t idx)150 hexdump_space(hexdump_param_t *hdp, uint_t idx)
151 {
152 	if (idx != 0 && idx % hdp->hdp_grouping == 0) {
153 		ilstr_append_char(&hdp->hdp_buf, ' ');
154 		/*
155 		 * If we are putting each byte in its own group, add an extra
156 		 * space every 8 bytes to improve readability.
157 		 */
158 		if (hdp->hdp_grouping == 1 && idx % 8 == 0)
159 			ilstr_append_char(&hdp->hdp_buf, ' ');
160 	}
161 	if (idx != 0 && hdp->hdp_flags & HDF_DOUBLESPACE)
162 		ilstr_append_char(&hdp->hdp_buf, ' ');
163 }
164 
165 static void
hexdump_header(hexdump_param_t * hdp)166 hexdump_header(hexdump_param_t *hdp)
167 {
168 	int markerpos = -1;
169 
170 	if (hdp->hdp_indent != 0)
171 		ilstr_aprintf(&hdp->hdp_buf, "%*s", hdp->hdp_indent, "");
172 
173 	if (hdp->hdp_flags & HDF_ADDRESS)
174 		ilstr_aprintf(&hdp->hdp_buf, "%*s   ", hdp->hdp_addrwidth, "");
175 
176 	for (uint_t i = 0; i < hdp->hdp_width; i++) {
177 		hexdump_space(hdp, i);
178 
179 		if ((hdp->hdp_marker > 0 && i == hdp->hdp_marker) ||
180 		    ((hdp->hdp_flags & HDF_ALIGN) &&
181 		    hdp->hdp_baseaddr + i == hdp->hdp_bufaddr)) {
182 			ilstr_append_str(&hdp->hdp_buf, "\\/");
183 			markerpos = i;
184 		} else {
185 			ilstr_aprintf(&hdp->hdp_buf, "%2x",
186 			    (i + hdp->hdp_offset) & 0xf);
187 		}
188 	}
189 
190 	if (hdp->hdp_flags & HDF_ASCII) {
191 		ilstr_append_str(&hdp->hdp_buf, "   ");
192 		for (uint_t i = 0; i < hdp->hdp_width; i++) {
193 			if (markerpos != -1 && markerpos == i) {
194 				ilstr_append_char(&hdp->hdp_buf, 'v');
195 			} else {
196 				ilstr_aprintf(&hdp->hdp_buf, "%x",
197 				    (i + hdp->hdp_offset) & 0xf);
198 			}
199 		}
200 	}
201 }
202 
203 static void
hexdump_data(hexdump_param_t * hdp)204 hexdump_data(hexdump_param_t *hdp)
205 {
206 	uint64_t addr = hdp->hdp_baseaddr + hdp->hdp_offset;
207 
208 	if (hdp->hdp_indent != 0)
209 		ilstr_aprintf(&hdp->hdp_buf, "%*s", hdp->hdp_indent, "");
210 
211 	if (hdp->hdp_flags & HDF_ADDRESS) {
212 		ilstr_aprintf(&hdp->hdp_buf, "%0*llx:  ",
213 		    hdp->hdp_addrwidth, addr);
214 	}
215 
216 	for (uint_t i = 0; i < hdp->hdp_width; i++) {
217 		if (hdp->hdp_offset + i >= hdp->hdp_len) {
218 			/*
219 			 * If we are not going to an ascii table, we don't need
220 			 * to pad this out with spaces to the right.
221 			 */
222 			if (!(hdp->hdp_flags & HDF_ASCII))
223 				break;
224 		}
225 		hexdump_space(hdp, i);
226 		if (addr + i < hdp->hdp_bufaddr ||
227 		    hdp->hdp_offset + i >= hdp->hdp_len) {
228 			ilstr_append_str(&hdp->hdp_buf, "  ");
229 		} else {
230 			ilstr_aprintf(&hdp->hdp_buf, "%02x",
231 			    hdp->hdp_data[hdp->hdp_offset + i - hdp->hdp_adj]);
232 		}
233 	}
234 
235 	if (hdp->hdp_flags & HDF_ASCII) {
236 		ilstr_append_str(&hdp->hdp_buf, " | ");
237 		for (uint_t i = 0; i < hdp->hdp_width; i++) {
238 			if (hdp->hdp_offset + i >= hdp->hdp_len)
239 				break;
240 			if (addr + i < hdp->hdp_bufaddr) {
241 				ilstr_append_char(&hdp->hdp_buf, ' ');
242 			} else {
243 				char c = hdp->hdp_data[hdp->hdp_offset + i
244 				    - hdp->hdp_adj];
245 
246 				ilstr_append_char(&hdp->hdp_buf,
247 				    HEXDUMP_PRINTABLE(c) ? c : '.');
248 			}
249 		}
250 	}
251 }
252 
253 static int
hexdump_output(hexdump_param_t * hdp,bool hdr,hexdump_cb_f cb,void * cbarg)254 hexdump_output(hexdump_param_t *hdp, bool hdr, hexdump_cb_f cb, void *cbarg)
255 {
256 	int ret;
257 
258 	/*
259 	 * The callback is invoked with an address of UINT64_MAX for the header
260 	 * row.
261 	 */
262 	ret = cb(cbarg, hdr ? UINT64_MAX : hdp->hdp_bufaddr + hdp->hdp_offset,
263 	    ilstr_cstr(&hdp->hdp_buf), ilstr_len(&hdp->hdp_buf));
264 	ilstr_reset(&hdp->hdp_buf);
265 
266 	return (ret);
267 }
268 
269 static bool
hexdump_squishable(hexdump_param_t * hdp)270 hexdump_squishable(hexdump_param_t *hdp)
271 {
272 	const char *str = ilstr_cstr(&hdp->hdp_buf);
273 	const char *pstr = ilstr_cstr(&hdp->hdp_pbuf);
274 
275 	if (hdp->hdp_flags & HDF_ADDRESS) {
276 		size_t strl = ilstr_len(&hdp->hdp_buf);
277 		size_t pstrl = ilstr_len(&hdp->hdp_pbuf);
278 
279 		if (strl <= hdp->hdp_addrwidth || pstrl <= hdp->hdp_addrwidth)
280 			return (false);
281 
282 		str += hdp->hdp_addrwidth;
283 		pstr += hdp->hdp_addrwidth;
284 	}
285 
286 	return (strcmp(str, pstr) == 0);
287 }
288 
289 int
hexdumph(hexdump_t * h,const uint8_t * data,size_t len,hexdump_flag_t flags,hexdump_cb_f cb,void * cbarg)290 hexdumph(hexdump_t *h, const uint8_t *data, size_t len, hexdump_flag_t flags,
291     hexdump_cb_f cb, void *cbarg)
292 {
293 	hexdump_param_t hdp = { 0 };
294 	int ret = 0;
295 
296 	if (data == NULL)
297 		return (0);
298 
299 	/*
300 	 * We can use a pre-allocated buffer for the constructed lines if the
301 	 * caller has provided one. One use of this is to invoke this routine
302 	 * early in boot before kmem is ready.
303 	 */
304 #ifdef _KERNEL
305 	if (kmem_ready == 0 && (h == NULL || h->h_buf == NULL)) {
306 		panic("hexdump before kmem is ready requires pre-allocated "
307 		    "buffer");
308 	}
309 #endif
310 	if (h != NULL && h->h_buf != NULL) {
311 		ilstr_init_prealloc(&hdp.hdp_buf, (char *)h->h_buf,
312 		    h->h_buflen);
313 		/* We do not support HDF_DEDUP with a pre-allocated buffer */
314 		flags &= ~HDF_DEDUP;
315 	} else {
316 		/*
317 		 * The KM_SLEEP flag is ignored outside kernel context. If a
318 		 * memory allocation fails in userland that will result in the
319 		 * caller ultimately receiving -1 with errno set to ENOMEM.
320 		 */
321 		ilstr_init(&hdp.hdp_buf, KM_SLEEP);
322 		if (flags & HDF_DEDUP)
323 			ilstr_init(&hdp.hdp_pbuf, KM_SLEEP);
324 	}
325 
326 	hdp.hdp_flags = flags;
327 	hdp.hdp_grouping = HD_DEFGROUPING;
328 	hdp.hdp_width = HD_DEFWIDTH;
329 	hdp.hdp_data = data;
330 	hdp.hdp_len = len;
331 
332 	hdp.hdp_bufaddr = hdp.hdp_baseaddr = 0;
333 	hdp.hdp_offset = hdp.hdp_marker = hdp.hdp_adj = 0;
334 
335 	if (h != NULL) {
336 		hdp.hdp_bufaddr = hdp.hdp_baseaddr = h->h_addr;
337 		hdp.hdp_width = h->h_width;
338 		hdp.hdp_grouping = h->h_grouping;
339 		hdp.hdp_indent = h->h_indent;
340 		hdp.hdp_marker = h->h_marker;
341 	}
342 
343 	if (hdp.hdp_width == 0)
344 		hdp.hdp_width = HD_DEFWIDTH;
345 	if (hdp.hdp_grouping == 0)
346 		hdp.hdp_grouping = HD_DEFGROUPING;
347 	if (hdp.hdp_marker > HD_DEFWIDTH)
348 		hdp.hdp_marker = 0;
349 
350 	/*
351 	 * If the grouping isn't a power of two, or the display width is not
352 	 * evenly divisible by the grouping we ignore the specified grouping
353 	 * and default to 4.
354 	 */
355 	if (!ISP2(hdp.hdp_grouping) || hdp.hdp_width % hdp.hdp_grouping != 0)
356 		hdp.hdp_grouping = 4;
357 
358 	/*
359 	 * Determine how much space is required for the address field.
360 	 * We need one character for every four bits of the final address.
361 	 */
362 	hdp.hdp_addrwidth = (flsll(hdp.hdp_baseaddr + len) + 3) / 4;
363 	if (h != NULL && h->h_addrwidth > hdp.hdp_addrwidth)
364 		hdp.hdp_addrwidth = h->h_addrwidth;
365 
366 	/*
367 	 * If alignment was requested and the address is not already aligned,
368 	 * adjust the starting address down to the nearest boundary. Missing
369 	 * data will be displayed as spaces.
370 	 */
371 	if (flags & HDF_ALIGN) {
372 		hdp.hdp_baseaddr = P2ALIGN(hdp.hdp_baseaddr, hdp.hdp_width);
373 		hdp.hdp_adj = hdp.hdp_bufaddr - hdp.hdp_baseaddr;
374 		hdp.hdp_len += hdp.hdp_adj;
375 	}
376 
377 	if (flags & HDF_HEADER) {
378 		hexdump_header(&hdp);
379 		if ((ret = hexdump_output(&hdp, true, cb, cbarg)) != 0)
380 			goto out;
381 	}
382 
383 	bool squishing = false;
384 
385 	while (hdp.hdp_offset < hdp.hdp_len) {
386 		hexdump_data(&hdp);
387 		if (flags & HDF_DEDUP) {
388 			if (hexdump_squishable(&hdp)) {
389 				ilstr_reset(&hdp.hdp_buf);
390 				if (squishing) {
391 					hdp.hdp_offset += hdp.hdp_width;
392 					continue;
393 				}
394 				ilstr_append_str(&hdp.hdp_buf, "*");
395 				squishing = true;
396 			} else {
397 				ilstr_reset(&hdp.hdp_pbuf);
398 				ilstr_append_str(&hdp.hdp_pbuf,
399 				    ilstr_cstr(&hdp.hdp_buf));
400 				squishing = false;
401 			}
402 		}
403 		if ((ret = hexdump_output(&hdp, false, cb, cbarg)) != 0)
404 			break;
405 		hdp.hdp_offset += hdp.hdp_width;
406 	}
407 
408 out:
409 	/*
410 	 * If ret is not zero then it is a value returned by a callback and we
411 	 * return that to the caller as-is. Otherwise we translate any errors
412 	 * from ilstr.
413 	 */
414 	if (ret == 0) {
415 		ilstr_errno_t ilerr = ilstr_errno(&hdp.hdp_buf);
416 
417 		switch (ilerr) {
418 		case ILSTR_ERROR_OK:
419 			break;
420 		case ILSTR_ERROR_NOMEM:
421 			ret = ENOMEM;
422 			break;
423 		case ILSTR_ERROR_OVERFLOW:
424 			ret = EOVERFLOW;
425 			break;
426 		case ILSTR_ERROR_PRINTF:
427 		default:
428 			/*
429 			 * We don't expect to end up here but we should return
430 			 * an error if we somehow do.
431 			 */
432 			ret = EIO;
433 			break;
434 		}
435 #ifndef _KERNEL
436 		if (ret != 0) {
437 			errno = ret;
438 			ret = -1;
439 		}
440 #endif
441 	}
442 
443 	ilstr_fini(&hdp.hdp_buf);
444 	if (flags & HDF_DEDUP)
445 		ilstr_fini(&hdp.hdp_pbuf);
446 
447 	return (ret);
448 }
449 
450 int
hexdump(const uint8_t * data,size_t len,hexdump_flag_t flags,hexdump_cb_f cb,void * cbarg)451 hexdump(const uint8_t *data, size_t len, hexdump_flag_t flags, hexdump_cb_f cb,
452     void *cbarg)
453 {
454 	return (hexdumph(NULL, data, len, flags, cb, cbarg));
455 }
456 
457 #ifndef _KERNEL
458 static int
hexdump_file_cb(void * arg,uint64_t addr __unused,const char * str,size_t len __unused)459 hexdump_file_cb(void *arg, uint64_t addr __unused, const char *str,
460     size_t len __unused)
461 {
462 	FILE *fp = (FILE *)arg;
463 
464 	if (fprintf(fp, "%s\n", str) < 0)
465 		return (-1);
466 	return (0);
467 }
468 
469 int
hexdump_fileh(hexdump_t * h,const uint8_t * data,size_t len,hexdump_flag_t flags,FILE * fp)470 hexdump_fileh(hexdump_t *h, const uint8_t *data, size_t len,
471     hexdump_flag_t flags, FILE *fp)
472 {
473 	return (hexdumph(h, data, len, flags, hexdump_file_cb, fp));
474 }
475 
476 int
hexdump_file(const uint8_t * data,size_t len,hexdump_flag_t flags,FILE * fp)477 hexdump_file(const uint8_t *data, size_t len, hexdump_flag_t flags, FILE *fp)
478 {
479 	return (hexdumph(NULL, data, len, flags, hexdump_file_cb, fp));
480 }
481 #endif /* _KERNEL */
482