| /* |
| * Copyright (c) 2015 Hauke Mehrtens <hauke@hauke-m.de> |
| * |
| * Backport functionality introduced in Linux 4.0. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| #include <linux/sched.h> |
| #include <linux/ctype.h> |
| #include <linux/printk.h> |
| #include <linux/export.h> |
| #include <linux/trace_seq.h> |
| #include <linux/ftrace_event.h> |
| #include <asm/unaligned.h> |
| |
| /** |
| * hex_dump_to_buffer - convert a blob of data to "hex ASCII" in memory |
| * @buf: data blob to dump |
| * @len: number of bytes in the @buf |
| * @rowsize: number of bytes to print per line; must be 16 or 32 |
| * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1) |
| * @linebuf: where to put the converted data |
| * @linebuflen: total size of @linebuf, including space for terminating NUL |
| * @ascii: include ASCII after the hex output |
| * |
| * hex_dump_to_buffer() works on one "line" of output at a time, i.e., |
| * 16 or 32 bytes of input data converted to hex + ASCII output. |
| * |
| * Given a buffer of u8 data, hex_dump_to_buffer() converts the input data |
| * to a hex + ASCII dump at the supplied memory location. |
| * The converted output is always NUL-terminated. |
| * |
| * E.g.: |
| * hex_dump_to_buffer(frame->data, frame->len, 16, 1, |
| * linebuf, sizeof(linebuf), true); |
| * |
| * example output buffer: |
| * 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO |
| * |
| * Return: |
| * The amount of bytes placed in the buffer without terminating NUL. If the |
| * output was truncated, then the return value is the number of bytes |
| * (excluding the terminating NUL) which would have been written to the final |
| * string if enough space had been available. |
| */ |
| int hex_dump_to_buffer(const void *buf, size_t len, int rowsize, int groupsize, |
| char *linebuf, size_t linebuflen, bool ascii) |
| { |
| const u8 *ptr = buf; |
| int ngroups; |
| u8 ch; |
| int j, lx = 0; |
| int ascii_column; |
| int ret; |
| |
| if (rowsize != 16 && rowsize != 32) |
| rowsize = 16; |
| |
| if (len > rowsize) /* limit to one line at a time */ |
| len = rowsize; |
| if (!is_power_of_2(groupsize) || groupsize > 8) |
| groupsize = 1; |
| if ((len % groupsize) != 0) /* no mixed size output */ |
| groupsize = 1; |
| |
| ngroups = len / groupsize; |
| ascii_column = rowsize * 2 + rowsize / groupsize + 1; |
| |
| if (!linebuflen) |
| goto overflow1; |
| |
| if (!len) |
| goto nil; |
| |
| if (groupsize == 8) { |
| const u64 *ptr8 = buf; |
| |
| for (j = 0; j < ngroups; j++) { |
| ret = snprintf(linebuf + lx, linebuflen - lx, |
| "%s%16.16llx", j ? " " : "", |
| get_unaligned(ptr8 + j)); |
| if (ret >= linebuflen - lx) |
| goto overflow1; |
| lx += ret; |
| } |
| } else if (groupsize == 4) { |
| const u32 *ptr4 = buf; |
| |
| for (j = 0; j < ngroups; j++) { |
| ret = snprintf(linebuf + lx, linebuflen - lx, |
| "%s%8.8x", j ? " " : "", |
| get_unaligned(ptr4 + j)); |
| if (ret >= linebuflen - lx) |
| goto overflow1; |
| lx += ret; |
| } |
| } else if (groupsize == 2) { |
| const u16 *ptr2 = buf; |
| |
| for (j = 0; j < ngroups; j++) { |
| ret = snprintf(linebuf + lx, linebuflen - lx, |
| "%s%4.4x", j ? " " : "", |
| get_unaligned(ptr2 + j)); |
| if (ret >= linebuflen - lx) |
| goto overflow1; |
| lx += ret; |
| } |
| } else { |
| for (j = 0; j < len; j++) { |
| if (linebuflen < lx + 3) |
| goto overflow2; |
| ch = ptr[j]; |
| linebuf[lx++] = hex_asc_hi(ch); |
| linebuf[lx++] = hex_asc_lo(ch); |
| linebuf[lx++] = ' '; |
| } |
| if (j) |
| lx--; |
| } |
| if (!ascii) |
| goto nil; |
| |
| while (lx < ascii_column) { |
| if (linebuflen < lx + 2) |
| goto overflow2; |
| linebuf[lx++] = ' '; |
| } |
| for (j = 0; j < len; j++) { |
| if (linebuflen < lx + 2) |
| goto overflow2; |
| ch = ptr[j]; |
| linebuf[lx++] = (isascii(ch) && isprint(ch)) ? ch : '.'; |
| } |
| nil: |
| linebuf[lx] = '\0'; |
| return lx; |
| overflow2: |
| linebuf[lx++] = '\0'; |
| overflow1: |
| return ascii ? ascii_column + len : (groupsize * 2 + 1) * ngroups - 1; |
| } |
| EXPORT_SYMBOL_GPL(hex_dump_to_buffer); |
| |
| #if LINUX_VERSION_IS_LESS(3,17,0) |
| static inline unsigned char * |
| trace_seq_buffer_ptr(struct trace_seq *s) |
| { |
| return s->buffer + s->len; |
| } |
| #endif |
| |
| const char * |
| ftrace_print_array_seq(struct trace_seq *p, const void *buf, int buf_len, |
| size_t el_size) |
| { |
| const char *ret = trace_seq_buffer_ptr(p); |
| const char *prefix = ""; |
| void *ptr = (void *)buf; |
| |
| trace_seq_putc(p, '{'); |
| |
| while (ptr < buf + buf_len) { |
| switch (el_size) { |
| case 1: |
| trace_seq_printf(p, "%s0x%x", prefix, |
| *(u8 *)ptr); |
| break; |
| case 2: |
| trace_seq_printf(p, "%s0x%x", prefix, |
| *(u16 *)ptr); |
| break; |
| case 4: |
| trace_seq_printf(p, "%s0x%x", prefix, |
| *(u32 *)ptr); |
| break; |
| case 8: |
| trace_seq_printf(p, "%s0x%llx", prefix, |
| *(u64 *)ptr); |
| break; |
| default: |
| trace_seq_printf(p, "BAD SIZE:%zu 0x%x", el_size, |
| *(u8 *)ptr); |
| el_size = 1; |
| } |
| prefix = ","; |
| ptr += el_size; |
| } |
| |
| trace_seq_putc(p, '}'); |
| trace_seq_putc(p, 0); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(ftrace_print_array_seq); |