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