[U-Boot] [PATCH 0/2 v2] Introduce hexdump

This tiny series introduces functionality for either convertion a blob of data to "hex ASCII" in memory or direct printing in stdout.
The first patch is required to safe existing users of dummy blobs (as of today) that will become a real functions.
Note even though entire series is tagges as v2
Cc: Stefan Roese sr@denx.de Cc: Tom Rini trini@konsulko.com
Alexey Brodkin (2): linux-compat: Use original kern_levels.h for kernel debug levels lib: Add hexdump
Changes v1 -> v2: * Introduced linux/kern_levels.h so KERNEL_XXX symbols are really defined * Move hexdump definitions to a separate header. * Move helper funcions to the header so they might be used even if CONFIG_HEXDUMP is not enabled and if not used they will not occupy extra bytes of memory being "static inline" ones. * While at it replace its own hex_asc[], hex_asc_{lo|hi}() and pack_hex_byte() in lib/vsprintf.c with generic ones from hexdump. * In print_hex_dump() revert back first parametr for debug level, otherwise existing users of the function will be broken.
include/common.h | 1 + include/hexdump.h | 108 ++++++++++++++++++++ include/linux/compat.h | 10 +- include/linux/kern_levels.h | 38 ++++++++ lib/Kconfig | 5 + lib/Makefile | 1 + lib/hexdump.c | 233 ++++++++++++++++++++++++++++++++++++++++++++ lib/vsprintf.c | 17 +--- 8 files changed, 390 insertions(+), 23 deletions(-) create mode 100644 include/hexdump.h create mode 100644 include/linux/kern_levels.h create mode 100644 lib/hexdump.c

Even currently in U-Boot we don't really use kernel debug levels we do have quite a lot of code that was copy-pasted from Linux kernel and so contains symbols like KERN_DEBUG and alike.
We used to just define those symbols which is fine if it is used in constructions like: ----------------->8-------------- printk(KERN_DEBUG "My debug message"); ----------------->8--------------
But in other places where the symbol gets passed as a separate argument (which we also have but luckily all of them are intself dummy stubs in U-Boot) that won't work.
Now as a preparation to introduction of a real implementation of print_hex_dump() we need those symbols to be defined as something real so we're taking them as they are from Linux kernel.
Signed-off-by: Alexey Brodkin abrodkin@synopsys.com Cc: Stefan Roese sr@denx.de Cc: Tom Rini trini@konsulko.com ---
Changes v1 -> v2: * This patch was only introduced in v2.
include/linux/compat.h | 9 +-------- include/linux/kern_levels.h | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 include/linux/kern_levels.h
diff --git a/include/linux/compat.h b/include/linux/compat.h index e561ee3..0ce7d65 100644 --- a/include/linux/compat.h +++ b/include/linux/compat.h @@ -28,14 +28,7 @@ extern struct p_current *current; #define printk printf #define printk_once printf
-#define KERN_EMERG -#define KERN_ALERT -#define KERN_CRIT -#define KERN_ERR -#define KERN_WARNING -#define KERN_NOTICE -#define KERN_INFO -#define KERN_DEBUG +#include <linux/kern_levels.h>
#define GFP_ATOMIC ((gfp_t) 0) #define GFP_KERNEL ((gfp_t) 0) diff --git a/include/linux/kern_levels.h b/include/linux/kern_levels.h new file mode 100644 index 0000000..c2ce155 --- /dev/null +++ b/include/linux/kern_levels.h @@ -0,0 +1,38 @@ +#ifndef __KERN_LEVELS_H__ +#define __KERN_LEVELS_H__ + +#define KERN_SOH "\001" /* ASCII Start Of Header */ +#define KERN_SOH_ASCII '\001' + +#define KERN_EMERG KERN_SOH "0" /* system is unusable */ +#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */ +#define KERN_CRIT KERN_SOH "2" /* critical conditions */ +#define KERN_ERR KERN_SOH "3" /* error conditions */ +#define KERN_WARNING KERN_SOH "4" /* warning conditions */ +#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */ +#define KERN_INFO KERN_SOH "6" /* informational */ +#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */ + +#define KERN_DEFAULT KERN_SOH "d" /* the default kernel loglevel */ + +/* + * Annotation for a "continued" line of log printout (only done after a + * line that had no enclosing \n). Only to be used by core/arch code + * during early bootup (a continued line is not SMP-safe otherwise). + */ +#define KERN_CONT "" + +/* integer equivalents of KERN_<LEVEL> */ +#define LOGLEVEL_SCHED -2 /* Deferred messages from sched code + * are set to this special level */ +#define LOGLEVEL_DEFAULT -1 /* default (or last) loglevel */ +#define LOGLEVEL_EMERG 0 /* system is unusable */ +#define LOGLEVEL_ALERT 1 /* action must be taken immediately */ +#define LOGLEVEL_CRIT 2 /* critical conditions */ +#define LOGLEVEL_ERR 3 /* error conditions */ +#define LOGLEVEL_WARNING 4 /* warning conditions */ +#define LOGLEVEL_NOTICE 5 /* normal but significant condition */ +#define LOGLEVEL_INFO 6 /* informational */ +#define LOGLEVEL_DEBUG 7 /* debug-level messages */ + +#endif

Often during debugging session it's very interesting to see what data we were dealing with. For example what we write or read to/from memory or peripherals.
This change introduces functions that allow to dump binary data with one simple function invocation like: ------------------->8---------------- print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len); ------------------->8----------------
which gives us the following: ------------------->8---------------- 00000000: f2 b7 c9 88 62 61 75 64 72 61 74 65 3d 31 31 35 ....baudrate=115 00000010: 32 30 30 00 62 6f 6f 74 61 72 67 73 3d 63 6f 6e 200.bootargs=con 00000020: 73 6f 6c 65 3d 74 74 79 53 33 2c 31 31 35 32 30 sole=ttyS3,11520 00000030: 30 6e 38 00 62 6f 6f 74 64 65 6c 61 79 3d 33 00 0n8.bootdelay=3. 00000040: 62 6f 6f 74 66 69 6c 65 3d 75 49 6d 61 67 65 00 bootfile=uImage. 00000050: 66 64 74 63 6f 6e 74 72 6f 6c 61 64 64 72 3d 39 fdtcontroladdr=9 00000060: 66 66 62 31 62 61 30 00 6c 6f 61 64 61 64 64 72 ffb1ba0.loadaddr 00000070: 3d 30 78 38 32 30 30 30 30 30 30 00 73 74 64 65 =0x82000000.stde 00000080: 72 72 3d 73 65 72 69 61 6c 30 40 65 30 30 32 32 rr=serial0@e0022 00000090: 30 30 30 00 73 74 64 69 6e 3d 73 65 72 69 61 6c 000.stdin=serial 000000a0: 30 40 65 30 30 32 32 30 30 30 00 73 74 64 6f 75 0@e0022000.stdou 000000b0: 74 3d 73 65 72 69 61 6c 30 40 65 30 30 32 32 30 t=serial0@e00220 000000c0: 30 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00.............. ... ------------------->8----------------
Source of hexdump.c was copied from Linux kernel v4.7-rc2.
Signed-off-by: Alexey Brodkin abrodkin@synopsys.com Reviewed-by: Stefan Roese sr@denx.de Cc: Tom Rini trini@konsulko.com
---
Changes v1 -> v2: * Move hexdump definitions to a separate header. * Move helper funcions to the header so they might be used even if CONFIG_HEXDUMP is not enabled and if not used they will not occupy extra bytes of memory being "static inline" ones. * While at it replace its own hex_asc[], hex_asc_{lo|hi}() and pack_hex_byte() in lib/vsprintf.c with generic ones from hexdump. * In print_hex_dump() revert back first parametr for debug level, otherwise existing users of the function will be broken.
include/common.h | 1 + include/hexdump.h | 108 +++++++++++++++++++++++ include/linux/compat.h | 1 - lib/Kconfig | 5 ++ lib/Makefile | 1 + lib/hexdump.c | 233 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/vsprintf.c | 17 +--- 7 files changed, 351 insertions(+), 15 deletions(-) create mode 100644 include/hexdump.h create mode 100644 lib/hexdump.c
diff --git a/include/common.h b/include/common.h index f9f4605..51a438c 100644 --- a/include/common.h +++ b/include/common.h @@ -23,6 +23,7 @@ typedef volatile unsigned char vu_char; #include <linux/stringify.h> #include <asm/ptrace.h> #include <stdarg.h> +#include <hexdump.h> #include <linux/kernel.h> #if defined(CONFIG_PCI) && defined(CONFIG_4xx) #include <pci.h> diff --git a/include/hexdump.h b/include/hexdump.h new file mode 100644 index 0000000..de2e565 --- /dev/null +++ b/include/hexdump.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016 Synopsys, Inc. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef HEXDUMP_H +#define HEXDUMP_H + +#include <linux/ctype.h> + +enum { + DUMP_PREFIX_NONE, + DUMP_PREFIX_ADDRESS, + DUMP_PREFIX_OFFSET +}; + +extern const char hex_asc[]; +#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] +#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] + +static inline char *hex_byte_pack(char *buf, u8 byte) +{ + *buf++ = hex_asc_hi(byte); + *buf++ = hex_asc_lo(byte); + return buf; +} + +/** + * hex_to_bin - convert a hex digit to its real value + * @ch: ascii character represents hex digit + * + * hex_to_bin() converts one hex digit to its actual value or -1 in case of bad + * input. + */ +static inline int hex_to_bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + return -1; +} + +/** + * hex2bin - convert an ascii hexadecimal string to its binary representation + * @dst: binary result + * @src: ascii hexadecimal string + * @count: result length + * + * Return 0 on success, -1 in case of bad input. + */ +static inline int hex2bin(u8 *dst, const char *src, size_t count) +{ + while (count--) { + int hi = hex_to_bin(*src++); + int lo = hex_to_bin(*src++); + + if ((hi < 0) || (lo < 0)) + return -1; + + *dst++ = (hi << 4) | lo; + } + return 0; +} + +/** + * bin2hex - convert binary data to an ascii hexadecimal string + * @dst: ascii hexadecimal result + * @src: binary data + * @count: binary data length + */ +static inline char *bin2hex(char *dst, const void *src, size_t count) +{ + const unsigned char *_src = src; + + while (count--) + dst = hex_byte_pack(dst, *_src++); + return dst; +} + + +#ifdef CONFIG_HEXDUMP +int hex_dump_to_buffer(const void *buf, size_t len, int rowsize, int groupsize, + char *linebuf, size_t linebuflen, bool ascii); +void print_hex_dump(const char *level, const char *prefix_str, int prefix_type, + int rowsize, int groupsize, const void *buf, size_t len, + bool ascii); +void print_hex_dump_bytes(const char *prefix_str, int prefix_type, + const void *buf, size_t len); +#else /* !CONFIG_HEXDUMP */ +/* + * Some code in U-Boot copy-pasted from Linux kernel uses both + * functions below so to keep stuff compilable we keep these stubs here. + */ +static inline void print_hex_dump(const char *level, const char *prefix_str, + int prefix_type, int rowsize, int groupsize, + const void *buf, size_t len, bool ascii) +{ +} +static inline void print_hex_dump_bytes(const char *prefix_str, int prefix_type, + const void *buf, size_t len) +{ +} +#endif /* CONFIG_HEXDUMP */ + +#endif /* HEXDUMP_H */ diff --git a/include/linux/compat.h b/include/linux/compat.h index 0ce7d65..074a85e 100644 --- a/include/linux/compat.h +++ b/include/linux/compat.h @@ -193,7 +193,6 @@ typedef unsigned long blkcnt_t; #define init_waitqueue_head(...) do { } while (0) #define wait_event_interruptible(...) 0 #define wake_up_interruptible(...) do { } while (0) -#define print_hex_dump(...) do { } while (0) #define dump_stack(...) do { } while (0)
#define task_pid_nr(x) 0 diff --git a/lib/Kconfig b/lib/Kconfig index 2b97c2b..a563b5e 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -128,6 +128,11 @@ config ERRNO_STR - if errno is null or positive number - a pointer to "Success" message - if errno is negative - a pointer to errno related message
+config HEXDUMP + bool "Enable hexdump" + help + This enables functions for printing dumps of binary data. + config OF_LIBFDT bool "Enable the FDT library" default y if OF_CONTROL diff --git a/lib/Makefile b/lib/Makefile index 02dfa29..1bf7c37 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -76,6 +76,7 @@ obj-y += membuff.o obj-$(CONFIG_REGEX) += slre.o obj-y += string.o obj-y += time.o +obj-y += hexdump.o obj-$(CONFIG_TRACE) += trace.o obj-$(CONFIG_LIB_UUID) += uuid.o obj-$(CONFIG_LIB_RAND) += rand.o diff --git a/lib/hexdump.c b/lib/hexdump.c new file mode 100644 index 0000000..ffee5ce --- /dev/null +++ b/lib/hexdump.c @@ -0,0 +1,233 @@ +/* + * lib/hexdump.c + * + * 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. See README and COPYING for + * more details. + */ + +#include <common.h> +#include <linux/ctype.h> +#include <linux/compat.h> +#include <linux/log2.h> +#include <asm/unaligned.h> + +const char hex_asc[] = "0123456789abcdef"; +const char hex_asc_upper[] = "0123456789ABCDEF"; + +#ifdef CONFIG_HEXDUMP +/** + * 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 + 2) + goto overflow2; + ch = ptr[j]; + linebuf[lx++] = hex_asc_hi(ch); + if (linebuflen < lx + 2) + goto overflow2; + linebuf[lx++] = hex_asc_lo(ch); + if (linebuflen < lx + 2) + goto overflow2; + 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; +} + +/** + * print_hex_dump - print a text hex dump to syslog for a binary blob of data + * @level: kernel log level (e.g. KERN_DEBUG) + * @prefix_str: string to prefix each line with; + * caller supplies trailing spaces for alignment if desired + * @prefix_type: controls whether prefix of an offset, address, or none + * is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE) + * @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) + * @buf: data blob to dump + * @len: number of bytes in the @buf + * @ascii: include ASCII after the hex output + * + * Given a buffer of u8 data, print_hex_dump() prints a hex + ASCII dump + * to the kernel log at the specified kernel log level, with an optional + * leading prefix. + * + * print_hex_dump() works on one "line" of output at a time, i.e., + * 16 or 32 bytes of input data converted to hex + ASCII output. + * print_hex_dump() iterates over the entire input @buf, breaking it into + * "line size" chunks to format and print. + * + * E.g.: + * print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS, + * 16, 1, frame->data, frame->len, true); + * + * Example output using %DUMP_PREFIX_OFFSET and 1-byte mode: + * 0009ab42: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO + * Example output using %DUMP_PREFIX_ADDRESS and 4-byte mode: + * ffffffff88089af0: 73727170 77767574 7b7a7978 7f7e7d7c pqrstuvwxyz{|}~. + */ +void print_hex_dump(const char *level, const char *prefix_str, int prefix_type, + int rowsize, int groupsize, + const void *buf, size_t len, bool ascii) +{ + const u8 *ptr = buf; + int i, linelen, remaining = len; + char linebuf[32 * 3 + 2 + 32 + 1]; + + if (rowsize != 16 && rowsize != 32) + rowsize = 16; + + for (i = 0; i < len; i += rowsize) { + linelen = min(remaining, rowsize); + remaining -= rowsize; + + hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize, + linebuf, sizeof(linebuf), ascii); + + switch (prefix_type) { + case DUMP_PREFIX_ADDRESS: + printf("%s%p: %s\n", prefix_str, ptr + i, linebuf); + break; + case DUMP_PREFIX_OFFSET: + printf("%s%.8x: %s\n", prefix_str, i, linebuf); + break; + default: + printf("%s%s\n", prefix_str, linebuf); + break; + } + } +} + +/** + * print_hex_dump_bytes - shorthand form of print_hex_dump() with default params + * @prefix_str: string to prefix each line with; + * caller supplies trailing spaces for alignment if desired + * @prefix_type: controls whether prefix of an offset, address, or none + * is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE) + * @buf: data blob to dump + * @len: number of bytes in the @buf + * + * Calls print_hex_dump(), with log level of KERN_DEBUG, + * rowsize of 16, groupsize of 1, and ASCII output included. + */ +void print_hex_dump_bytes(const char *prefix_str, int prefix_type, + const void *buf, size_t len) +{ + print_hex_dump(KERN_DEBUG, prefix_str, prefix_type, 16, 1, + buf, len, true); +} + +#endif /* CONFIG_HEXDUMP */ diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 874a295..c0a0aaa 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -271,17 +271,6 @@ static char *string(char *buf, char *end, char *s, int field_width, }
#ifdef CONFIG_CMD_NET -static const char hex_asc[] = "0123456789abcdef"; -#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] -#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] - -static inline char *pack_hex_byte(char *buf, u8 byte) -{ - *buf++ = hex_asc_hi(byte); - *buf++ = hex_asc_lo(byte); - return buf; -} - static char *mac_address_string(char *buf, char *end, u8 *addr, int field_width, int precision, int flags) { @@ -291,7 +280,7 @@ static char *mac_address_string(char *buf, char *end, u8 *addr, int field_width, int i;
for (i = 0; i < 6; i++) { - p = pack_hex_byte(p, addr[i]); + p = hex_byte_pack(p, addr[i]); if (!(flags & SPECIAL) && i != 5) *p++ = ':'; } @@ -310,8 +299,8 @@ static char *ip6_addr_string(char *buf, char *end, u8 *addr, int field_width, int i;
for (i = 0; i < 8; i++) { - p = pack_hex_byte(p, addr[2 * i]); - p = pack_hex_byte(p, addr[2 * i + 1]); + p = hex_byte_pack(p, addr[2 * i]); + p = hex_byte_pack(p, addr[2 * i + 1]); if (!(flags & SPECIAL) && i != 7) *p++ = ':'; }
participants (1)
-
Alexey Brodkin