Revised kernel printf implementation

Adapted musl printf implementation using our output abstraction.
Floating point specifiers are not supported in this adaptation.
Modified the code to also match our style and make it
less unnecessarily complex.

Signed-off-by: Saer Debel <saer.debel@data61.csiro.au>
This commit is contained in:
Saer Debel 2020-05-06 13:45:50 +10:00
parent 77a4a1d64c
commit 7dc4209f89
3 changed files with 520 additions and 251 deletions

19
LICENSES/MIT.txt Normal file
View file

@ -0,0 +1,19 @@
MIT License Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -7,6 +7,7 @@
#pragma once
#define va_start(v,l) __builtin_va_start(v,l)
#define va_copy(d,s) __builtin_va_copy(d,s)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v,l) __builtin_va_arg(v,l)
typedef __builtin_va_list va_list;

View file

@ -2,6 +2,12 @@
* Copyright 2014, General Dynamics C4 Systems
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Portions derived from musl:
*
* Copyright © 2005-2020 Rich Felker, et al.
*
* SPDX-License-Identifier: MIT
*/
#include <config.h>
@ -10,6 +16,7 @@
#ifdef CONFIG_PRINTING
#include <stdarg.h>
#include <stdint.h>
/*
* a handle defining how to output a character
@ -48,270 +55,493 @@ void putchar(char c)
putDebugChar(c);
}
static unsigned int print_spaces(out_wrap_t *out, int n)
{
for (int i = 0; i < n; i++) {
putchar_wrap(out, ' ');
}
return n;
}
static unsigned int print_string(out_wrap_t *out, const char *s)
{
unsigned int n;
for (n = 0; *s; s++, n++) {
putchar_wrap(out, *s);
}
return n;
}
static unsigned long xdiv(unsigned long x, unsigned int denom)
{
switch (denom) {
case 16:
return x / 16;
case 10:
return x / 10;
default:
return 0;
}
}
static unsigned long xmod(unsigned long x, unsigned int denom)
{
switch (denom) {
case 16:
return x % 16;
case 10:
return x % 10;
default:
return 0;
}
}
static word_t print_unsigned_long(out_wrap_t *out_wrap, unsigned long x, word_t ui_base)
{
char out[sizeof(unsigned long) * 2 + 3];
word_t i, j;
unsigned int d;
/*
* Only base 10 and 16 supported for now. We want to avoid invoking the
* compiler's support libraries through doing arbitrary divisions.
*/
if (ui_base != 10 && ui_base != 16) {
return 0;
}
if (x == 0) {
putchar_wrap(out_wrap, '0');
return 1;
}
for (i = 0; x; x = xdiv(x, ui_base), i++) {
d = xmod(x, ui_base);
if (d >= 10) {
out[i] = 'a' + d - 10;
} else {
out[i] = '0' + d;
}
}
for (j = i; j > 0; j--) {
putchar_wrap(out_wrap, out[j - 1]);
}
return i;
}
/* The print_unsigned_long_long function assumes that an unsinged int
is half the size of an unsigned long long */
compile_assert(print_unsigned_long_long_sizes, sizeof(unsigned int) * 2 == sizeof(unsigned long long))
static unsigned int
print_unsigned_long_long(out_wrap_t *out, unsigned long long x, unsigned int ui_base)
{
unsigned int upper, lower;
unsigned int n = 0;
unsigned int mask = 0xF0000000u;
unsigned int shifts = 0;
/* only implemented for hex, decimal is harder without 64 bit division */
if (ui_base != 16) {
return 0;
}
/* we can't do 64 bit division so break it up into two hex numbers */
upper = (unsigned int)(x >> 32llu);
lower = (unsigned int) x & 0xffffffff;
/* print first 32 bits if they exist */
if (upper > 0) {
n += print_unsigned_long(out, upper, ui_base);
/* print leading 0s */
while (!(mask & lower)) {
putchar_wrap(out, '0');
n++;
mask = mask >> 4;
shifts++;
if (shifts == 8) {
break;
}
}
}
/* print last 32 bits */
n += print_unsigned_long(out, lower, ui_base);
return n;
}
static inline bool_t isdigit(char c)
{
return c >= '0' &&
c <= '9';
}
static inline int atoi(char c)
/* Convenient bit representation for modifier flags, which all fall
* within 31 codepoints of the space character. */
#define MASK_TYPE(a) (1U<<( a -' '))
#define ALT_FORM (1U<<('#'-' '))
#define ZERO_PAD (1U<<('0'-' '))
#define LEFT_ADJ (1U<<('-'-' '))
#define PAD_POS (1U<<(' '-' '))
#define MARK_POS (1U<<('+'-' '))
#define GROUPED (1U<<('\''-' '))
#define FLAGMASK (ALT_FORM|ZERO_PAD|LEFT_ADJ|PAD_POS|MARK_POS|GROUPED)
#define INTMAX_MAX INT32_MAX
#define INT_MAX 0x7fffffff
#define ULONG_MAX ((unsigned long)(-1))
/* State machine to accept length modifiers + conversion specifiers.
* Result is 0 on failure, or an argument type to pop on success. */
enum {
BARE, LPRE, LLPRE, HPRE, HHPRE, BIGLPRE,
ZTPRE, JPRE,
STOP,
PTR, INT, UINT, ULLONG,
LONG, ULONG,
SHORT, USHORT, CHAR, UCHAR,
WORDT, LLONG,
#define IMAX LLONG
#define UMAX ULLONG
#define PDIFF LONG
#define UIPTR ULONG
NOARG,
MAXSTATE
};
#define S(x) [(x)-'A']
static const unsigned char states[]['z' - 'A' + 1] = {
{ /* 0: bare types */
S('d') = INT, S('i') = INT,
S('o') = UINT, S('u') = UINT, S('x') = UINT, S('X') = UINT,
S('c') = CHAR,
S('s') = PTR, S('p') = UIPTR, S('n') = PTR,
S('l') = LPRE, S('h') = HPRE,
S('z') = ZTPRE, S('j') = JPRE, S('t') = ZTPRE,
}, { /* 1: l-prefixed */
S('d') = LONG, S('i') = LONG,
S('o') = ULONG, S('u') = ULONG, S('x') = ULONG, S('X') = ULONG,
S('n') = PTR,
S('l') = LLPRE,
}, { /* 2: ll-prefixed */
S('d') = LLONG, S('i') = LLONG,
S('o') = ULLONG, S('u') = ULLONG,
S('x') = ULLONG, S('X') = ULLONG,
S('n') = PTR,
}, { /* 3: h-prefixed */
S('d') = SHORT, S('i') = SHORT,
S('o') = USHORT, S('u') = USHORT,
S('x') = USHORT, S('X') = USHORT,
S('n') = PTR,
S('h') = HHPRE,
}, { /* 4: hh-prefixed */
S('d') = CHAR, S('i') = CHAR,
S('o') = UCHAR, S('u') = UCHAR,
S('x') = UCHAR, S('X') = UCHAR,
S('n') = PTR,
}, { /* 5: L-prefixed not supported */
}, { /* 6: z- or t-prefixed (assumed to be same size) */
S('d') = PDIFF, S('i') = PDIFF,
S('o') = WORDT, S('u') = WORDT,
S('x') = WORDT, S('X') = WORDT,
S('n') = PTR,
}, { /* 7: j-prefixed */
S('d') = IMAX, S('i') = IMAX,
S('o') = UMAX, S('u') = UMAX,
S('x') = UMAX, S('X') = UMAX,
S('n') = PTR,
}
};
#define OOB(x) ((unsigned)(x)-'A' > 'z'-'A')
#define DIGIT(c) (c - '0')
union arg {
word_t i;
long double f;
void *p;
};
static void pop_arg(union arg *arg, int type, va_list *ap)
{
return c - '0';
switch (type) {
case PTR:
arg->p = va_arg(*ap, void *);
break;
case INT:
arg->i = va_arg(*ap, int);
break;
case UINT:
arg->i = va_arg(*ap, unsigned int);
break;
case LONG:
arg->i = va_arg(*ap, long);
break;
case ULONG:
arg->i = va_arg(*ap, unsigned long);
break;
case LLONG:
arg->i = va_arg(*ap, long long);
break;
case ULLONG:
arg->i = va_arg(*ap, unsigned long long);
break;
case SHORT:
arg->i = (short)va_arg(*ap, int);
break;
case USHORT:
arg->i = (unsigned short)va_arg(*ap, int);
break;
case CHAR:
arg->i = (signed char)va_arg(*ap, int);
break;
case UCHAR:
arg->i = (unsigned char)va_arg(*ap, int);
break;
case WORDT:
arg->i = va_arg(*ap, word_t);
}
}
static int vprintf(out_wrap_t *out, const char *format, va_list ap)
static void out(out_wrap_t *f, const char *s, word_t l)
{
unsigned int n;
unsigned int formatting;
int nspaces = 0;
for (word_t i = 0; i < l; i++) {
putchar_wrap(f, s[i]);
}
}
if (!format) {
static void pad(out_wrap_t *f, char c, int w, int l, int fl)
{
char pad[256];
if (fl & (LEFT_ADJ | ZERO_PAD) || l >= w) {
return;
}
l = w - l;
memset(pad, c, l > sizeof pad ? sizeof pad : l);
for (; l >= sizeof pad; l -= sizeof pad) {
out(f, pad, sizeof pad);
}
out(f, pad, l);
}
static const char xdigits[16] = {
"0123456789ABCDEF"
};
static char *fmt_x(word_t x, char *s, int lower)
{
for (; x; x >>= 4) {
*--s = xdigits[(x & 15)] | lower;
}
return s;
}
static char *fmt_o(word_t x, char *s)
{
for (; x; x >>= 3) {
*--s = '0' + (x & 7);
}
return s;
}
static char *fmt_u(word_t x, char *s)
{
unsigned long y;
for (; x > ULONG_MAX; x /= 10) {
*--s = '0' + x % 10;
}
for (y = x; y; y /= 10) {
*--s = '0' + y % 10;
}
return s;
}
// Maximum buffer size taken to ensure correct adaptation
// However, it could be reduced/removed if we could measure
// the buf length under all code paths
#define LDBL_MANT_DIG 113
#define NL_ARGMAX 9
static int getint(char **s)
{
int i;
for (i = 0; isdigit(**s); (*s)++) {
if (i > INT_MAX / 10U || DIGIT(**s) > INT_MAX - 10 * i) {
i = -1;
} else {
i = 10 * i + DIGIT(**s);
}
}
return i;
}
static int printf_core(out_wrap_t *f, const char *fmt, va_list *ap, union arg *nl_arg, int *nl_type)
{
char *a, *z, *s = (char *)fmt;
unsigned l10n = 0, fl;
int w, p, xp;
union arg arg;
int argpos;
unsigned st, ps;
int cnt = 0, l = 0;
word_t i;
char buf[sizeof(word_t) * 3 + 3 + LDBL_MANT_DIG / 4];
const char *prefix;
int t, pl;
for (;;) {
/* This error is only specified for snprintf, but since it's
* unspecified for other forms, do the same. Stop immediately
* on overflow; otherwise %n could produce wrong results. */
if (l > INT_MAX - cnt) {
goto overflow;
}
/* Update output count, end loop when fmt is exhausted */
cnt += l;
if (!*s) {
break;
}
/* Handle literal text and %% format specifiers */
for (a = s; *s && *s != '%'; s++);
for (z = s; s[0] == '%' && s[1] == '%'; z++, s += 2);
if (z - a > INT_MAX - cnt) {
goto overflow;
}
l = z - a;
if (f) {
out(f, a, l);
}
if (l) {
continue;
}
if (isdigit(s[1]) && s[2] == '$') {
l10n = 1;
argpos = DIGIT(s[1]);
s += 3;
} else {
argpos = -1;
s++;
}
/* Read modifier flags */
for (fl = 0; (unsigned)*s - ' ' < 32 && (FLAGMASK & MASK_TYPE(*s)); s++) {
fl |= MASK_TYPE(*s);
}
/* Read field width */
if (*s == '*') {
if (isdigit(s[1]) && s[2] == '$') {
l10n = 1;
nl_type[DIGIT(s[1])] = INT;
w = nl_arg[DIGIT(s[1])].i;
s += 3;
} else if (!l10n) {
w = f ? va_arg(*ap, int) : 0;
s++;
} else {
goto inval;
}
if (w < 0) {
fl |= LEFT_ADJ;
w = -w;
}
} else if ((w = getint(&s)) < 0) {
goto overflow;
}
/* Read precision */
if (*s == '.' && s[1] == '*') {
if (isdigit(s[2]) && s[3] == '$') {
nl_type[DIGIT(s[2])] = INT;
p = nl_arg[DIGIT(s[2])].i;
s += 4;
} else if (!l10n) {
p = f ? va_arg(*ap, int) : 0;
s += 2;
} else {
goto inval;
}
xp = (p >= 0);
} else if (*s == '.') {
s++;
p = getint(&s);
xp = 1;
} else {
p = -1;
xp = 0;
}
/* Format specifier state machine */
st = 0;
do {
if (OOB(*s)) {
goto inval;
}
ps = st;
st = states[st]S(*s++);
} while (st - 1 < STOP);
if (!st) {
goto inval;
}
/* Check validity of argument type (nl/normal) */
if (st == NOARG) {
if (argpos >= 0) {
goto inval;
}
} else {
if (argpos >= 0) {
nl_type[argpos] = st;
arg = nl_arg[argpos];
} else if (f) {
pop_arg(&arg, st, ap);
} else {
return 0;
}
}
if (!f) {
continue;
}
z = buf + sizeof(buf);
prefix = "-+ 0X0x";
pl = 0;
t = s[-1];
/* - and 0 flags are mutually exclusive */
if (fl & LEFT_ADJ) {
fl &= ~ZERO_PAD;
}
if (t == 'n') {
if (!arg.p) {
continue;
}
switch (ps) {
case BARE:
*(int *)arg.p = cnt;
break;
case LPRE:
*(long *)arg.p = cnt;
break;
case LLPRE:
*(long long *)arg.p = cnt;
break;
case HPRE:
*(unsigned short *)arg.p = cnt;
break;
case HHPRE:
*(unsigned char *)arg.p = cnt;
break;
case ZTPRE:
*(word_t *)arg.p = cnt;
break;
case JPRE:
*(word_t *)arg.p = cnt;
break;
}
continue;
} else if (t == 'c') {
p = 1;
a = z - p;
*a = arg.i;
fl &= ~ZERO_PAD;
} else if (t == 's') {
a = arg.p ? arg.p : "(null)";
z = a + strnlen(a, p < 0 ? INT_MAX : p);
if (p < 0 && *z) {
goto overflow;
}
p = z - a;
fl &= ~ZERO_PAD;
} else {
switch (t) {
case 'p':
p = MAX(p, 2 * sizeof(void *));
t = 'x';
fl |= ALT_FORM;
case 'x':
case 'X':
a = fmt_x(arg.i, z, t & 32);
if (arg.i && (fl & ALT_FORM)) {
prefix += (t >> 4);
pl = 2;
}
break;
case 'o':
a = fmt_o(arg.i, z);
if ((fl & ALT_FORM) && p < (z - a + 1)) {
p = z - a + 1;
}
break;
case 'd':
case 'i':
pl = 1;
if (arg.i > INTMAX_MAX) {
arg.i = -arg.i;
} else if (fl & MARK_POS) {
prefix++;
} else if (fl & PAD_POS) {
prefix += 2;
} else {
pl = 0;
}
case 'u':
a = fmt_u(arg.i, z);
break;
}
if (xp && p < 0) {
goto overflow;
}
if (xp) {
fl &= ~ZERO_PAD;
}
if (!arg.i && !p) {
a = z;
} else {
p = MAX(p, z - a + !arg.i);
}
}
if (p < z - a) {
p = z - a;
}
if (p > INT_MAX - pl) {
goto overflow;
}
if (w < pl + p) {
w = pl + p;
}
if (w > INT_MAX - cnt) {
goto overflow;
}
pad(f, ' ', w, pl + p, fl);
out(f, prefix, pl);
pad(f, '0', w, pl + p, fl ^ ZERO_PAD);
pad(f, '0', p, z - a, 0);
out(f, a, z - a);
pad(f, ' ', w, pl + p, fl ^ LEFT_ADJ);
l = w;
}
if (f) {
return cnt;
}
if (!l10n) {
return 0;
}
n = 0;
formatting = 0;
while (*format) {
if (formatting) {
while (isdigit(*format)) {
nspaces = nspaces * 10 + atoi(*format);
format++;
if (format == NULL) {
break;
}
}
switch (*format) {
case '%':
putchar_wrap(out, '%');
n++;
format++;
break;
case 'd': {
int x = va_arg(ap, int);
if (x < 0) {
putchar_wrap(out, '-');
n++;
x = -x;
}
n += print_unsigned_long(out, x, 10);
format++;
break;
}
case 'u':
n += print_unsigned_long(out, va_arg(ap, unsigned int), 10);
format++;
break;
case 'x':
n += print_unsigned_long(out, va_arg(ap, unsigned int), 16);
format++;
break;
case 'p': {
unsigned long p = va_arg(ap, unsigned long);
if (p == 0) {
n += print_string(out, "(nil)");
} else {
n += print_string(out, "0x");
n += print_unsigned_long(out, p, 16);
}
format++;
break;
}
case 's':
n += print_string(out, va_arg(ap, char *));
format++;
break;
case 'l':
format++;
switch (*format) {
case 'd': {
long x = va_arg(ap, long);
if (x < 0) {
putchar_wrap(out, '-');
n++;
x = -x;
}
n += print_unsigned_long(out, (unsigned long)x, 10);
format++;
}
break;
case 'l':
if (*(format + 1) == 'x') {
n += print_unsigned_long_long(out, va_arg(ap, unsigned long long), 16);
}
format += 2;
break;
case 'u':
n += print_unsigned_long(out, va_arg(ap, unsigned long), 10);
format++;
break;
case 'x':
n += print_unsigned_long(out, va_arg(ap, unsigned long), 16);
format++;
break;
default:
/* format not supported */
return -1;
}
break;
default:
/* format not supported */
return -1;
}
if (nspaces > n) {
n += print_spaces(out, nspaces - n);
}
nspaces = 0;
formatting = 0;
} else {
switch (*format) {
case '%':
formatting = 1;
format++;
break;
default:
putchar_wrap(out, *format);
n++;
format++;
break;
}
}
for (i = 1; i <= NL_ARGMAX && nl_type[i]; i++) {
pop_arg(nl_arg + i, nl_type[i], ap);
}
for (; i <= NL_ARGMAX && !nl_type[i]; i++);
if (i <= NL_ARGMAX) {
goto inval;
}
return 1;
return n;
// goto for potential debug error support
inval:
overflow:
return -1;
}
// sprintf fills its buf with the given character
@ -335,17 +565,36 @@ word_t puts(const char *s)
return 0;
}
static int vprintf(out_wrap_t *out, const char *fmt, va_list ap)
{
va_list ap2;
int nl_type[NL_ARGMAX + 1] = {0};
union arg nl_arg[NL_ARGMAX + 1];
int ret;
// validate format string
va_copy(ap2, ap);
if (printf_core(0, fmt, &ap2, nl_arg, nl_type) < 0) {
va_end(ap2);
return -1;
}
ret = printf_core(out, fmt, &ap2, nl_arg, nl_type);
va_end(ap2);
return ret;
}
word_t kprintf(const char *format, ...)
{
va_list args;
word_t i;
word_t ret;
out_wrap_t out = { kernel_out_fn, NULL, 0, -1 };
va_start(args, format);
i = vprintf(&out, format, args);
ret = vprintf(&out, format, args);
va_end(args);
return i;
return ret;
}
word_t ksnprintf(char *str, word_t size, const char *format, ...)