#include <sys/ilstr.h>
#include <sys/kmem.h>
#include <sys/stdbool.h>
#include <sys/sysmacros.h>
#include <sys/hexdump.h>
#ifdef _KERNEL
#include <sys/sunddi.h>
#include <sys/errno.h>
#else
#include <stdint.h>
#include <stdio.h>
#include <strings.h>
#include <errno.h>
#endif
#define HD_DEFGROUPING 1
#define HD_DEFWIDTH 16
#define HEXDUMP_PRINTABLE(c) ((c) >= ' ' && (c) <= '~')
typedef struct {
uint64_t hdp_flags;
uint8_t hdp_grouping;
uint8_t hdp_width;
uint8_t hdp_indent;
const uint8_t *hdp_data;
uint64_t hdp_len;
uint8_t hdp_addrwidth;
uint64_t hdp_bufaddr;
uint64_t hdp_baseaddr;
uint64_t hdp_adj;
ilstr_t hdp_buf;
ilstr_t hdp_pbuf;
uint64_t hdp_offset;
uint8_t hdp_marker;
} hexdump_param_t;
#ifdef _KERNEL
static int
flsll(unsigned long x)
{
int pos = 0;
while (x != 0) {
pos++;
x >>= 1;
}
return (pos);
}
#endif
void
hexdump_init(hexdump_t *h)
{
bzero(h, sizeof (*h));
h->h_grouping = HD_DEFGROUPING;
h->h_width = HD_DEFWIDTH;
}
void
hexdump_fini(hexdump_t *h __unused)
{
}
void
hexdump_set_width(hexdump_t *h, uint8_t width)
{
if (width == 0)
h->h_width = HD_DEFWIDTH;
else
h->h_width = width;
}
void
hexdump_set_grouping(hexdump_t *h, uint8_t grouping)
{
if (grouping == 0)
h->h_grouping = 1;
else
h->h_grouping = grouping;
}
void
hexdump_set_indent(hexdump_t *h, uint8_t indent)
{
h->h_indent = indent;
}
void
hexdump_set_addr(hexdump_t *h, uint64_t addr)
{
h->h_addr = addr;
}
void
hexdump_set_addrwidth(hexdump_t *h, uint8_t width)
{
h->h_addrwidth = width;
}
void
hexdump_set_marker(hexdump_t *h, uint8_t marker)
{
h->h_marker = marker;
}
void
hexdump_set_buf(hexdump_t *h, uint8_t *buf, size_t buflen)
{
h->h_buf = buf;
h->h_buflen = buflen;
}
static void
hexdump_space(hexdump_param_t *hdp, uint_t idx)
{
if (idx != 0 && idx % hdp->hdp_grouping == 0) {
ilstr_append_char(&hdp->hdp_buf, ' ');
if (hdp->hdp_grouping == 1 && idx % 8 == 0)
ilstr_append_char(&hdp->hdp_buf, ' ');
}
if (idx != 0 && hdp->hdp_flags & HDF_DOUBLESPACE)
ilstr_append_char(&hdp->hdp_buf, ' ');
}
static void
hexdump_header(hexdump_param_t *hdp)
{
int markerpos = -1;
if (hdp->hdp_indent != 0)
ilstr_aprintf(&hdp->hdp_buf, "%*s", hdp->hdp_indent, "");
if (hdp->hdp_flags & HDF_ADDRESS)
ilstr_aprintf(&hdp->hdp_buf, "%*s ", hdp->hdp_addrwidth, "");
for (uint_t i = 0; i < hdp->hdp_width; i++) {
hexdump_space(hdp, i);
if ((hdp->hdp_marker > 0 && i == hdp->hdp_marker) ||
((hdp->hdp_flags & HDF_ALIGN) &&
hdp->hdp_baseaddr + i == hdp->hdp_bufaddr)) {
ilstr_append_str(&hdp->hdp_buf, "\\/");
markerpos = i;
} else {
ilstr_aprintf(&hdp->hdp_buf, "%2x",
(i + hdp->hdp_offset) & 0xf);
}
}
if (hdp->hdp_flags & HDF_ASCII) {
ilstr_append_str(&hdp->hdp_buf, " ");
for (uint_t i = 0; i < hdp->hdp_width; i++) {
if (markerpos != -1 && markerpos == i) {
ilstr_append_char(&hdp->hdp_buf, 'v');
} else {
ilstr_aprintf(&hdp->hdp_buf, "%x",
(i + hdp->hdp_offset) & 0xf);
}
}
}
}
static void
hexdump_data(hexdump_param_t *hdp)
{
uint64_t addr = hdp->hdp_baseaddr + hdp->hdp_offset;
if (hdp->hdp_indent != 0)
ilstr_aprintf(&hdp->hdp_buf, "%*s", hdp->hdp_indent, "");
if (hdp->hdp_flags & HDF_ADDRESS) {
ilstr_aprintf(&hdp->hdp_buf, "%0*llx: ",
hdp->hdp_addrwidth, addr);
}
for (uint_t i = 0; i < hdp->hdp_width; i++) {
if (hdp->hdp_offset + i >= hdp->hdp_len) {
if (!(hdp->hdp_flags & HDF_ASCII))
break;
}
hexdump_space(hdp, i);
if (addr + i < hdp->hdp_bufaddr ||
hdp->hdp_offset + i >= hdp->hdp_len) {
ilstr_append_str(&hdp->hdp_buf, " ");
} else {
ilstr_aprintf(&hdp->hdp_buf, "%02x",
hdp->hdp_data[hdp->hdp_offset + i - hdp->hdp_adj]);
}
}
if (hdp->hdp_flags & HDF_ASCII) {
ilstr_append_str(&hdp->hdp_buf, " | ");
for (uint_t i = 0; i < hdp->hdp_width; i++) {
if (hdp->hdp_offset + i >= hdp->hdp_len)
break;
if (addr + i < hdp->hdp_bufaddr) {
ilstr_append_char(&hdp->hdp_buf, ' ');
} else {
char c = hdp->hdp_data[hdp->hdp_offset + i
- hdp->hdp_adj];
ilstr_append_char(&hdp->hdp_buf,
HEXDUMP_PRINTABLE(c) ? c : '.');
}
}
}
}
static int
hexdump_output(hexdump_param_t *hdp, bool hdr, hexdump_cb_f cb, void *cbarg)
{
int ret;
ret = cb(cbarg, hdr ? UINT64_MAX : hdp->hdp_bufaddr + hdp->hdp_offset,
ilstr_cstr(&hdp->hdp_buf), ilstr_len(&hdp->hdp_buf));
ilstr_reset(&hdp->hdp_buf);
return (ret);
}
static bool
hexdump_squishable(hexdump_param_t *hdp)
{
const char *str = ilstr_cstr(&hdp->hdp_buf);
const char *pstr = ilstr_cstr(&hdp->hdp_pbuf);
if (hdp->hdp_flags & HDF_ADDRESS) {
size_t strl = ilstr_len(&hdp->hdp_buf);
size_t pstrl = ilstr_len(&hdp->hdp_pbuf);
if (strl <= hdp->hdp_addrwidth || pstrl <= hdp->hdp_addrwidth)
return (false);
str += hdp->hdp_addrwidth;
pstr += hdp->hdp_addrwidth;
}
return (strcmp(str, pstr) == 0);
}
int
hexdumph(hexdump_t *h, const uint8_t *data, size_t len, hexdump_flag_t flags,
hexdump_cb_f cb, void *cbarg)
{
hexdump_param_t hdp = { 0 };
int ret = 0;
if (data == NULL)
return (0);
#ifdef _KERNEL
if (kmem_ready == 0 && (h == NULL || h->h_buf == NULL)) {
panic("hexdump before kmem is ready requires pre-allocated "
"buffer");
}
#endif
if (h != NULL && h->h_buf != NULL) {
ilstr_init_prealloc(&hdp.hdp_buf, (char *)h->h_buf,
h->h_buflen);
flags &= ~HDF_DEDUP;
} else {
ilstr_init(&hdp.hdp_buf, KM_SLEEP);
if (flags & HDF_DEDUP)
ilstr_init(&hdp.hdp_pbuf, KM_SLEEP);
}
hdp.hdp_flags = flags;
hdp.hdp_grouping = HD_DEFGROUPING;
hdp.hdp_width = HD_DEFWIDTH;
hdp.hdp_data = data;
hdp.hdp_len = len;
hdp.hdp_bufaddr = hdp.hdp_baseaddr = 0;
hdp.hdp_offset = hdp.hdp_marker = hdp.hdp_adj = 0;
if (h != NULL) {
hdp.hdp_bufaddr = hdp.hdp_baseaddr = h->h_addr;
hdp.hdp_width = h->h_width;
hdp.hdp_grouping = h->h_grouping;
hdp.hdp_indent = h->h_indent;
hdp.hdp_marker = h->h_marker;
}
if (hdp.hdp_width == 0)
hdp.hdp_width = HD_DEFWIDTH;
if (hdp.hdp_grouping == 0)
hdp.hdp_grouping = HD_DEFGROUPING;
if (hdp.hdp_marker > HD_DEFWIDTH)
hdp.hdp_marker = 0;
if (!ISP2(hdp.hdp_grouping) || hdp.hdp_width % hdp.hdp_grouping != 0)
hdp.hdp_grouping = 4;
hdp.hdp_addrwidth = (flsll(hdp.hdp_baseaddr + len) + 3) / 4;
if (h != NULL && h->h_addrwidth > hdp.hdp_addrwidth)
hdp.hdp_addrwidth = h->h_addrwidth;
if (flags & HDF_ALIGN) {
hdp.hdp_baseaddr = P2ALIGN(hdp.hdp_baseaddr, hdp.hdp_width);
hdp.hdp_adj = hdp.hdp_bufaddr - hdp.hdp_baseaddr;
hdp.hdp_len += hdp.hdp_adj;
}
if (flags & HDF_HEADER) {
hexdump_header(&hdp);
if ((ret = hexdump_output(&hdp, true, cb, cbarg)) != 0)
goto out;
}
bool squishing = false;
while (hdp.hdp_offset < hdp.hdp_len) {
hexdump_data(&hdp);
if (flags & HDF_DEDUP) {
if (hexdump_squishable(&hdp)) {
ilstr_reset(&hdp.hdp_buf);
if (squishing) {
hdp.hdp_offset += hdp.hdp_width;
continue;
}
ilstr_append_str(&hdp.hdp_buf, "*");
squishing = true;
} else {
ilstr_reset(&hdp.hdp_pbuf);
ilstr_append_str(&hdp.hdp_pbuf,
ilstr_cstr(&hdp.hdp_buf));
squishing = false;
}
}
if ((ret = hexdump_output(&hdp, false, cb, cbarg)) != 0)
break;
hdp.hdp_offset += hdp.hdp_width;
}
out:
if (ret == 0) {
ilstr_errno_t ilerr = ilstr_errno(&hdp.hdp_buf);
switch (ilerr) {
case ILSTR_ERROR_OK:
break;
case ILSTR_ERROR_NOMEM:
ret = ENOMEM;
break;
case ILSTR_ERROR_OVERFLOW:
ret = EOVERFLOW;
break;
case ILSTR_ERROR_PRINTF:
default:
ret = EIO;
break;
}
#ifndef _KERNEL
if (ret != 0) {
errno = ret;
ret = -1;
}
#endif
}
ilstr_fini(&hdp.hdp_buf);
if (flags & HDF_DEDUP)
ilstr_fini(&hdp.hdp_pbuf);
return (ret);
}
int
hexdump(const uint8_t *data, size_t len, hexdump_flag_t flags, hexdump_cb_f cb,
void *cbarg)
{
return (hexdumph(NULL, data, len, flags, cb, cbarg));
}
#ifndef _KERNEL
static int
hexdump_file_cb(void *arg, uint64_t addr __unused, const char *str,
size_t len __unused)
{
FILE *fp = (FILE *)arg;
if (fprintf(fp, "%s\n", str) < 0)
return (-1);
return (0);
}
int
hexdump_fileh(hexdump_t *h, const uint8_t *data, size_t len,
hexdump_flag_t flags, FILE *fp)
{
return (hexdumph(h, data, len, flags, hexdump_file_cb, fp));
}
int
hexdump_file(const uint8_t *data, size_t len, hexdump_flag_t flags, FILE *fp)
{
return (hexdumph(NULL, data, len, flags, hexdump_file_cb, fp));
}
#endif