From 5650ca76f837f174335741915c63091912da9e0c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 19 Apr 2012 17:11:14 -0700 Subject: [PATCH] PR13999: Let "%#c" add escapes for nonprintables The special '#' flag previously didn't do anything for characters. Now it signals that non-printable characters should be escaped in the output string, either using C shortcuts or octal values. * runtime/vsprintf.c (_stp_vsprint_char_size, _stp_vsprint_char): New functions to size and fill the buffer, accounting for escape chars. (_stp_vsnprintf): Use the new char functions. * translate.cxx (c_unparser::emit_compiled_printfs): Ditto. * testsuite/systemtap.printf/char2.*: Test %#c * NEWS, stap.1: Document it. --- NEWS | 2 + runtime/vsprintf.c | 153 ++++++++++++++++++++++----- runtime/vsprintf.h | 4 + stap.1 | 10 ++ testsuite/systemtap.printf/char2.exp | 7 ++ testsuite/systemtap.printf/char2.stp | 17 +++ translate.cxx | 12 +-- 7 files changed, 173 insertions(+), 32 deletions(-) create mode 100644 testsuite/systemtap.printf/char2.exp create mode 100644 testsuite/systemtap.printf/char2.stp diff --git a/NEWS b/NEWS index d04ddeb2d..da1f101d1 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,7 @@ * What's new in version 1.8 +- Printf formats can now use "%#c" to escape non-printing characters. + - The systemtap compile-server and client now support IPv6 networks. - IPv6 addresses may now be specified on the --use-server option and will be displayed by --list-servers, if the avahi-daemon service is running and diff --git a/runtime/vsprintf.c b/runtime/vsprintf.c index 18fe176ce..d3ed0574b 100644 --- a/runtime/vsprintf.c +++ b/runtime/vsprintf.c @@ -198,6 +198,133 @@ static int number_size(uint64_t num, int base, int size, int precision, enum pri } + +/* + * Output one character into the buffer. Usually this is just a + * straight copy, padded left or right up to 'width', but if the user + * gave the '#' flag then we need to escape special characters. + */ +static char * +_stp_vsprint_char(char * str, char * end, char c, + int width, enum print_flag flags) +{ + int size = _stp_vsprint_char_size(c, 0, flags); + + if (!(flags & STP_LEFT)) { + while (width-- > size) { + if (str <= end) + *str = ' '; + ++str; + } + } + + if (size == 1) { + if (str <= end) + *str = c; + ++str; + } + else { + /* Other sizes mean this is not a printable character. + * First try to match up C escape characters: */ + char escape = 0; + switch (c) { + case '\a': + escape = 'a'; + break; + case '\b': + escape = 'b'; + break; + case '\f': + escape = 'f'; + break; + case '\n': + escape = 'n'; + break; + case '\r': + escape = 'r'; + break; + case '\t': + escape = 't'; + break; + case '\v': + escape = 'v'; + break; + case '\'': + escape = '\''; + break; + case '\\': + escape = '\\'; + break; + } + + if (str <= end) + *str = '\\'; + ++str; + if (escape) { + if (str <= end) + *str = escape; + ++str; + } + else { + /* Fall back to octal for everything else */ + if (str <= end) + *str = to_oct_digit((c >> 6) & 03); + ++str; + if (str <= end) + *str = to_oct_digit((c >> 3) & 07); + ++str; + if (str <= end) + *str = to_oct_digit(c & 07); + ++str; + } + } + + while (width-- > size) { + if (str <= end) + *str = ' '; + ++str; + } + + return str; +} + + +/* + * Compute the size of a given character in the buffer. Usually this is + * just 1 (padded up to 'width'), but if the user gave the '#' flag then + * we need to escape special characters. + */ +static int +_stp_vsprint_char_size(char c, int width, enum print_flag flags) +{ + int size = 1; + + /* look for quoteworthy characters */ + if ((flags & STP_SPECIAL) && + (!(isprint(c) && isascii(c)) || c == '\'' || c == '\\')) + switch (c) { + case '\a': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + case '\'': + case '\\': + /* backslash and one escape character */ + size = 2; + break; + default: + /* backslash and three octal digits */ + size = 4; + break; + } + + return max(size, width); +} + + static char * _stp_vsprint_memory(char * str, char * end, const char * ptr, int width, int precision, @@ -540,16 +667,8 @@ static int _stp_vsnprintf(char *buf, size_t size, const char *fmt, va_list args) break; case 'c': - if (!(flags & STP_LEFT)) { - while (--field_width > 0) { - num_bytes++; - } - } c = (unsigned char) va_arg(args_copy, int); - num_bytes++; - while (--field_width > 0) { - num_bytes++; - } + num_bytes += _stp_vsprint_char_size(c, field_width, flags); continue; default: @@ -739,22 +858,8 @@ static int _stp_vsnprintf(char *buf, size_t size, const char *fmt, va_list args) break; case 'c': - if (!(flags & STP_LEFT)) { - while (--field_width > 0) { - if (str <= end) - *str = ' '; - ++str; - } - } c = (unsigned char) va_arg(args, int); - if (str <= end) - *str = c; - ++str; - while (--field_width > 0) { - if (str <= end) - *str = ' '; - ++str; - } + str = _stp_vsprint_char(str, end, c, field_width, flags); continue; default: diff --git a/runtime/vsprintf.h b/runtime/vsprintf.h index 57b5e484b..2f948912f 100644 --- a/runtime/vsprintf.h +++ b/runtime/vsprintf.h @@ -18,6 +18,10 @@ static char *number(char * buf, char * end, uint64_t num, int base, static int number_size(uint64_t num, int base, int size, int precision, enum print_flag type); +static char *_stp_vsprint_char(char * str, char * end, char c, + int width, enum print_flag flags); +static int _stp_vsprint_char_size(char c, int width, enum print_flag flags); + static char *_stp_vsprint_memory(char * str, char * end, const char * ptr, int width, int precision, char format, enum print_flag flags); diff --git a/stap.1 b/stap.1 index 8f51e333f..aea093641 100644 --- a/stap.1 +++ b/stap.1 @@ -1112,6 +1112,12 @@ Unsigned hex value, in all upper-case. Writes a %. .RE .PP +The +.IR # +flag selects the alternate forms. For octal, this prefixes a 0. For hex, this +prefixes 0x or 0X, depending on case. For characters, this escapes +non-printing values with either C-like escapes or raw octal. +.PP Examples: .SAMPLE a = "alice", b = "bob", p = 0x1234abcd, i = 123, j = \-1, id[a] = 1234, id[b] = 4567 @@ -1129,6 +1135,10 @@ Examples: Prints: 2 byte of kernel buffer at address 0x1234abcd: printf("%4b", p) Prints (these values as binary data): 0x1234abcd + printf("%#o %#x %#X\\n", 1, 2, 3) + Prints: 01 0x2 0X3 + printf("%#c %#c %#c\\n", 0, 9, 42) + Prints: \\000 \\t * .ESAMPLE .SS STATISTICS diff --git a/testsuite/systemtap.printf/char2.exp b/testsuite/systemtap.printf/char2.exp new file mode 100644 index 000000000..c91367e19 --- /dev/null +++ b/testsuite/systemtap.printf/char2.exp @@ -0,0 +1,7 @@ +set test "char2" +set ::result_string {abc-ABC 123! +\a\b\f\n\r\t\v\'\\ +\000\033\177\222\333\377} + +stap_run2 $srcdir/$subdir/$test.stp +stap_run2 $srcdir/$subdir/$test.stp -DSTP_LEGACY_PRINT diff --git a/testsuite/systemtap.printf/char2.stp b/testsuite/systemtap.printf/char2.stp new file mode 100644 index 000000000..9ccd421ba --- /dev/null +++ b/testsuite/systemtap.printf/char2.stp @@ -0,0 +1,17 @@ +probe begin +{ + chars = "abc-ABC 123!" + for (i = 0; i < strlen(chars); ++i) + printf("%#c", stringat(chars, i)) + print("\n") + + chars = "\a\b\f\n\r\t\v\'\\" + for (i = 0; i < strlen(chars); ++i) + printf("%#c", stringat(chars, i)) + print("\n") + + printf("%#c%#c%#c%#c%#c%#c\n", + 0, 033, 0177, 0222, 0333, 0377) + + exit() +} diff --git a/translate.cxx b/translate.cxx index 68b8105ac..c1e58fa6c 100644 --- a/translate.cxx +++ b/translate.cxx @@ -1259,7 +1259,8 @@ c_unparser::emit_compiled_printfs () break; case print_format::conv_char: - o->newline() << "num_bytes += max(width, 1);"; + o->newline() << "num_bytes += _stp_vsprint_char_size(" + << value << ", width, " << c->flags << ");"; break; case print_format::conv_string: @@ -1363,13 +1364,8 @@ c_unparser::emit_compiled_printfs () break; case print_format::conv_char: - if (c->test_flag(print_format::fmt_flag_left)) - o->newline() << "*str++ = " << value << ";"; - o->newline() << "while (width-- > 1 && str <= end)"; - o->newline(1) << "*str++ = ' ';"; - o->indent(-1); - if (!c->test_flag(print_format::fmt_flag_left)) - o->newline() << "*str++ = " << value << ";"; + o->newline() << "str = _stp_vsprint_char(str, end, " + << value << ", width, " << c->flags << ");"; break; case print_format::conv_string: -- 2.43.5