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 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 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 96 hexdump_fini(hexdump_t *h __unused) 97 { 98 } 99 100 void 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 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 119 hexdump_set_indent(hexdump_t *h, uint8_t indent) 120 { 121 h->h_indent = indent; 122 } 123 124 void 125 hexdump_set_addr(hexdump_t *h, uint64_t addr) 126 { 127 h->h_addr = addr; 128 } 129 130 void 131 hexdump_set_addrwidth(hexdump_t *h, uint8_t width) 132 { 133 h->h_addrwidth = width; 134 } 135 136 void 137 hexdump_set_marker(hexdump_t *h, uint8_t marker) 138 { 139 h->h_marker = marker; 140 } 141 142 void 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 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 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 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 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 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 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 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 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 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 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