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_marker(hexdump_t *h, uint8_t marker) 132 { 133 h->h_marker = marker; 134 } 135 136 void 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 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 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 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 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 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 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 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 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 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 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