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