From 79acbcf2550f3a55108240558efb8b9c36eb8399 Mon Sep 17 00:00:00 2001 From: Eugene Syromyatnikov Date: Tue, 19 Feb 2019 03:10:11 +0100 Subject: [PATCH] util: update dumpstr Use a buffer of a limited size, use proper type for dump amount, avoid hard-coding of byte counts, calculate output buffer size more accurately and minimise its rewriting, pad offset with zeros in accordance with expected output amount. * defs.h (dumpstr): Change the type of len argument from int to kernel_ulong_t. * macros.h (ROUNDUP_DIV): New macro. (ROUNDUP): Rewrite using ROUNDUP_DIV. * util.c (ILOG2_ITER_): New macro. (ilog2_64, ilog2_32): New functions. (ilog2_klong): New macro, wrapper around ilog2_32/ilog2_64, so (potentially more expensive) ilog2_64 is not used for ilog2 calculation of a kernel_ulong_t-typed variable on architectures with 32-bit kernel long. (dumpstr): Update. Co-Authored-by: Dmitry V. Levin --- defs.h | 2 +- macros.h | 6 +- util.c | 205 +++++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 169 insertions(+), 44 deletions(-) diff --git a/defs.h b/defs.h index 42f09fed..f526f073 100644 --- a/defs.h +++ b/defs.h @@ -871,7 +871,7 @@ extern void dumpiov_upto(struct tcb *, int len, kernel_ulong_t addr, kernel_ulong_t data_size); extern void -dumpstr(struct tcb *, kernel_ulong_t addr, int len); +dumpstr(struct tcb *, kernel_ulong_t addr, kernel_ulong_t len); extern int printstr_ex(struct tcb *, kernel_ulong_t addr, kernel_ulong_t len, diff --git a/macros.h b/macros.h index 7f019480..61abf826 100644 --- a/macros.h +++ b/macros.h @@ -28,8 +28,12 @@ # endif # define CLAMP(val, min, max) MIN(MAX(min, val), max) +# ifndef ROUNDUP_DIV +# define ROUNDUP_DIV(val_, div_) (((val_) + (div_) - 1) / (div_)) +# endif + # ifndef ROUNDUP -# define ROUNDUP(val_, div_) ((((val_) + (div_) - 1) / (div_)) * (div_)) +# define ROUNDUP(val_, div_) (ROUNDUP_DIV((val_), (div_)) * (div_)) # endif # ifndef offsetofend diff --git a/util.c b/util.c index 9e88b3a2..62f411de 100644 --- a/util.c +++ b/util.c @@ -25,6 +25,7 @@ #include "largefile_wrappers.h" #include "print_utils.h" +#include "static_assert.h" #include "xlat.h" #include "xstring.h" @@ -924,52 +925,166 @@ dumpiov_upto(struct tcb *const tcp, const int len, const kernel_ulong_t addr, #undef iov } +#define ILOG2_ITER_(val_, ret_, bit_) \ + do { \ + typeof(ret_) shift_ = \ + ((val_) > ((((typeof(val_)) 1) \ + << (1 << (bit_))) - 1)) << (bit_); \ + (val_) >>= shift_; \ + (ret_) |= shift_; \ + } while (0) + +/** + * Calculate floor(log2(val)), with the exception of val == 0, for which 0 + * is returned as well. + * + * @param val 64-bit value to calculate integer base-2 logarithm for. + * @return (unsigned int) floor(log2(val)) if val > 0, 0 if val == 0. + */ +static inline unsigned int +ilog2_64(uint64_t val) +{ + unsigned int ret = 0; + + ILOG2_ITER_(val, ret, 5); + ILOG2_ITER_(val, ret, 4); + ILOG2_ITER_(val, ret, 3); + ILOG2_ITER_(val, ret, 2); + ILOG2_ITER_(val, ret, 1); + ILOG2_ITER_(val, ret, 0); + + return ret; +} + +/** + * Calculate floor(log2(val)), with the exception of val == 0, for which 0 + * is returned as well. + * + * @param val 32-bit value to calculate integer base-2 logarithm for. + * @return (unsigned int) floor(log2(val)) if val > 0, 0 if val == 0. + */ +static inline unsigned int +ilog2_32(uint32_t val) +{ + unsigned int ret = 0; + + ILOG2_ITER_(val, ret, 4); + ILOG2_ITER_(val, ret, 3); + ILOG2_ITER_(val, ret, 2); + ILOG2_ITER_(val, ret, 1); + ILOG2_ITER_(val, ret, 0); + + return ret; +} + +#undef ILOG2_ITER_ + +#if SIZEOF_KERNEL_LONG_T > 4 +# define ilog2_klong ilog2_64 +#else +# define ilog2_klong ilog2_32 +#endif + void -dumpstr(struct tcb *const tcp, const kernel_ulong_t addr, const int len) +dumpstr(struct tcb *const tcp, const kernel_ulong_t addr, + const kernel_ulong_t len) { - static int strsize = -1; - static unsigned char *str; + /* xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx 1234567890123456 */ + enum { + HEX_BIT = 4, - char outbuf[ - ( - (sizeof( - "xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx " - "1234567890123456") + /*in case I'm off by few:*/ 4) - /*align to 8 to make memset easier:*/ + 7) & -8 - ]; - const unsigned char *src; - int i; + DUMPSTR_GROUP_BYTES = 8, + DUMPSTR_GROUPS = 2, + DUMPSTR_WIDTH_BYTES = DUMPSTR_GROUP_BYTES * DUMPSTR_GROUPS, - if ((len < 0) || (len > INT_MAX - 16)) + /** Width of formatted dump in characters. */ + DUMPSTR_WIDTH_CHARS = DUMPSTR_WIDTH_BYTES + + sizeof("xx") * DUMPSTR_WIDTH_BYTES + DUMPSTR_GROUPS, + + DUMPSTR_GROUP_MASK = DUMPSTR_GROUP_BYTES - 1, + DUMPSTR_BYTES_MASK = DUMPSTR_WIDTH_BYTES - 1, + + /** Minimal width of the offset field in the output. */ + DUMPSTR_OFFS_MIN_CHARS = 5, + + /** Arbitrarily chosen internal dumpstr buffer limit. */ + DUMPSTR_BUF_MAXSZ = 1 << 16, + }; + + static_assert(!(DUMPSTR_BUF_MAXSZ % DUMPSTR_WIDTH_BYTES), + "Maximum internal buffer size should be divisible " + "by amount of bytes dumped per line"); + static_assert(!(DUMPSTR_GROUP_BYTES & DUMPSTR_GROUP_MASK), + "DUMPSTR_GROUP_BYTES is not power of 2"); + static_assert(!(DUMPSTR_WIDTH_BYTES & DUMPSTR_BYTES_MASK), + "DUMPSTR_WIDTH_BYTES is not power of 2"); + + if (len > len + DUMPSTR_WIDTH_BYTES || addr + len < addr) { + debug_func_msg("len %" PRI_klu " at addr %#" PRI_klx + " is too big, skipped", len, addr); return; + } - memset(outbuf, ' ', sizeof(outbuf)); + static kernel_ulong_t strsize; + static unsigned char *str; + + const kernel_ulong_t alloc_size = + MIN(ROUNDUP(len, DUMPSTR_WIDTH_BYTES), DUMPSTR_BUF_MAXSZ); - if (strsize < len + 16) { + if (strsize < alloc_size) { free(str); - str = malloc(len + 16); + str = malloc(alloc_size); if (!str) { - strsize = -1; + strsize = 0; error_func_msg("memory exhausted when tried to allocate" - " %zu bytes", (size_t) (len + 16)); + " %" PRI_klu " bytes", alloc_size); return; } - strsize = len + 16; + strsize = alloc_size; } - if (umoven(tcp, addr, len, str) < 0) - return; - - /* Space-pad to 16 bytes */ - i = len; - while (i & 0xf) - str[i++] = ' '; + /** + * Characters needed in order to print the offset field. We calculate + * it this way in order to avoid ilog2_64 call most of the time. + */ + const int offs_chars = len > (1 << (DUMPSTR_OFFS_MIN_CHARS * HEX_BIT)) + ? 1 + ilog2_klong(len - 1) / HEX_BIT : DUMPSTR_OFFS_MIN_CHARS; + kernel_ulong_t i = 0; + const unsigned char *src; - i = 0; - src = str; while (i < len) { + /* + * It is important to overwrite all the byte values, as we + * re-use the buffer in order to avoid its re-initialisation. + */ + static char outbuf[] = { + [0 ... DUMPSTR_WIDTH_CHARS - 1] = ' ', + '\0' + }; char *dst = outbuf; - /* Hex dump */ + + /* Fetching data from tracee. */ + if (!i || (i % DUMPSTR_BUF_MAXSZ) == 0) { + kernel_ulong_t fetch_size = MIN(len - i, alloc_size); + + if (umoven(tcp, addr + i, fetch_size, str) < 0) { + /* + * Don't silently abort if we have printed + * something already. + */ + if (i) + tprintf(" | \n", + fetch_size, + fetch_size == 1 ? "" : "s", + tcp->pid, addr + i); + return; + } + src = str; + } + + /* hex dump */ do { if (i < len) { dst = sprint_byte_hex(dst, *src); @@ -977,24 +1092,30 @@ dumpstr(struct tcb *const tcp, const kernel_ulong_t addr, const int len) *dst++ = ' '; *dst++ = ' '; } - dst++; /* space is there by memset */ + dst++; /* space is there */ i++; - if ((i & 7) == 0) - dst++; /* space is there by memset */ + if ((i & DUMPSTR_GROUP_MASK) == 0) + dst++; /* space is there */ src++; - } while (i & 0xf); + } while (i & DUMPSTR_BYTES_MASK); + /* ASCII dump */ - i -= 16; - src -= 16; + i -= DUMPSTR_WIDTH_BYTES; + src -= DUMPSTR_WIDTH_BYTES; do { - if (is_print(*src)) - *dst++ = *src; - else - *dst++ = '.'; + if (i < len) { + if (is_print(*src)) + *dst++ = *src; + else + *dst++ = '.'; + } else { + *dst++ = ' '; + } src++; - } while (++i & 0xf); - *dst = '\0'; - tprintf(" | %05x %s |\n", i - 16, outbuf); + } while (++i & DUMPSTR_BYTES_MASK); + + tprintf(" | %0*" PRI_klx " %s |\n", + offs_chars, i - DUMPSTR_WIDTH_BYTES, outbuf); } } -- 2.40.0