diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 000000000..204b93da4 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,19 @@ +MIT License Copyright (c) + +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. diff --git a/include/stdarg.h b/include/stdarg.h index 11aeb60cc..fc1a0af1b 100644 --- a/include/stdarg.h +++ b/include/stdarg.h @@ -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; diff --git a/src/machine/io.c b/src/machine/io.c index a47ef8553..414ab189f 100644 --- a/src/machine/io.c +++ b/src/machine/io.c @@ -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 @@ -10,6 +16,7 @@ #ifdef CONFIG_PRINTING #include +#include /* * 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, ...)