Make printf use the platform UART. For historical reasons, this had its own hard-coded version of a 16550. Doing this required making the file C++. At the same time, fix a load of style issue and replace a void* and function pointer with the function wrapper from the network stack. This provides a type-erased way of calling a lambda, so we can clean up the code a bit. Also remove ambient Uart access from this compartment and move the UART access into the header.
diff --git a/sdk/include/function_wrapper.hh b/sdk/include/function_wrapper.hh new file mode 100644 index 0000000..50ca248 --- /dev/null +++ b/sdk/include/function_wrapper.hh
@@ -0,0 +1,109 @@ +#include <cdefs.h> +#include <functional> +#include <tuple> + +/** + * Base template for `FunctionWrapper`, never used. + */ +template<typename FnType> +class FunctionWrapper; + +/** + * A non-owning type-erased reference to a callable object. This is used + * to pass lambdas (and similar) down the stack without increasing code + * size by template specialisation. Instances of this class must not be + * stored, they should be used only for passing type-erased callable objects + * down the stack. + * + * Instances of this class are two words: a reference to the lambda, and a + * wrapper callback that invokes the lambda. + * + * This is similar to `std::function` but is non-owning and so is guaranteed + * not to allocate memory (the called function may capture memory). + */ +template<class R, class... Args> +class FunctionWrapper<R(Args...)> +{ + /** + * Storage for the type-erased function. This holds the reference to + * lambda and to the invoke function. + */ + alignas(void *) char storage[2 * sizeof(void *)]; + + /** + * Base type for the type-erased function. This defines the virtual + * function that is used to invoke the captured lambda. + */ + struct ErasedFunctionWrapperBase + { + virtual R operator()(Args... args) = 0; + }; + + /** + * Returns a pointer to the storage, cast to the type-erased function + * type. + */ + ErasedFunctionWrapperBase &stored_function() + { + return *reinterpret_cast<ErasedFunctionWrapperBase *>(storage); + } + + /** + * Templated subclass that is specialised for each concrete callable + * type `T` that is passed. One instance of this will be created for + * each lambda type, with a single method in its vtable that invokes + * the lambda. + */ + template<typename T> + class ErasedFunctionWrapper : public ErasedFunctionWrapperBase + { + /// Pointer to the captured lambda. + T &&fn; + + public: + /** + * Invoke function. This is virtual and overrides the version in + * the parent class, allowing this to be called from code that does + * not know the cocrete type of the lambda. + */ + R operator()(Args... args) override + { + return fn(std::forward<Args>(args)...); + } + + /** + * Construct the type-erased function wrapper, capturing the + * lambda. + */ + ErasedFunctionWrapper(T &&fn) : fn{std::forward<T>(fn)} {} + }; + + public: + /** + * This is a non-owning reference, delete its copy and move + * constructors to avoid accidental copies. + */ + FunctionWrapper(FunctionWrapper &) = delete; + FunctionWrapper(FunctionWrapper &&) = delete; + FunctionWrapper &operator=(FunctionWrapper &&) = delete; + + /** + * Construct the type-erased function wrapper, capturing the lambda. + */ + template<typename T> + __always_inline FunctionWrapper(T &&fn) + { + // Make sure that we got the size for the storage right! + static_assert(sizeof(storage) >= sizeof(ErasedFunctionWrapper<T>)); + // Construct the type-erased function in place. + new (storage) ErasedFunctionWrapper<T>(std::forward<T>(fn)); + } + + /** + * Invoke the captured lambda. + */ + __always_inline R operator()(Args... args) + { + return stored_function()(std::forward<Args>(args)...); + } +};
diff --git a/sdk/include/stdio.h b/sdk/include/stdio.h index 4d15320..fd6b605 100644 --- a/sdk/include/stdio.h +++ b/sdk/include/stdio.h
@@ -5,6 +5,7 @@ #define __STDIO_H__ #include <cdefs.h> +#include <compartment-macros.h> #include <stdarg.h> #include <stddef.h> @@ -12,13 +13,56 @@ #define EOF (-1) __BEGIN_DECLS -int __cheri_libcall printf(const char *fmt, ...); -#define name_printf(fmt, ...) \ - printf(__XSTRING(__CHERI_COMPARTMENT__) ": " fmt, ##__VA_ARGS__) +/** + * This is a very simple implementation of a subset of stdio and supports only + * UARTs. The Uart type is often a C++ template type and so we can't forward + * declare it in a C header and so we use a volatile void* instead, which can + * be cast to the correct type inside the library. + */ +typedef volatile void FILE; + +#if DEVICE_EXISTS(uart0) +# define stdout MMIO_CAPABILITY(void, uart0) +# define stdin MMIO_CAPABILITY(void, uart0) +#elif DEVICE_EXISTS(uart) +# define stdout MMIO_CAPABILITY(void, uart) +# define stdin MMIO_CAPABILITY(void uart) +#endif + +#if DEVICE_EXISTS(uart1) +# define stderr MMIO_CAPABILITY(void, uart1) +#elif defined(stdout) +# define stderr stdout +#endif + +int __cheri_libcall vfprintf(FILE *stream, const char *fmt, va_list ap); + +static inline int fprintf(FILE *stream, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + int ret = vfprintf(stream, format, ap); + va_end(ap); + return ret; +} + +static inline int printf(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + int ret = vfprintf(stdout, format, ap); + va_end(ap); + return ret; +} int __cheri_libcall snprintf(char *str, size_t size, const char *format, ...); -int __cheri_libcall vsnprintf(char *str, size_t size, const char *format, va_list ap); +int __cheri_libcall vsnprintf(const char *str, + size_t size, + const char *format, + va_list ap); __END_DECLS #endif /* !__STDIO_H__ */
diff --git a/sdk/lib/stdio/printf.c b/sdk/lib/stdio/printf.c deleted file mode 100644 index fc210a1..0000000 --- a/sdk/lib/stdio/printf.c +++ /dev/null
@@ -1,558 +0,0 @@ -/*- - * Copyright (c) 1986, 1988, 1991, 1993 - * The Regents of the University of California. All rights reserved. - * (c) UNIX System Laboratories, Inc. - * All or some portions of this file are derived from material licensed - * to the University of California by American Telephone and Telegraph - * Co. or Unix System Laboratories, Inc. and are reproduced herein with - * the permission of UNIX System Laboratories, Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)subr_prf.c 8.3 (Berkeley) 1/21/94 - */ - -/* __FBSDID("$FreeBSD: stable/8/sys/kern/subr_prf.c 210305 2010-07-20 18:55:13Z - * jkim $"); */ - -#include <cdefs.h> -#include <cheri-builtins.h> -#include <compartment.h> -#include <inttypes.h> -#include <stdarg.h> -#include <stdio.h> -#include <string.h> - -/* - * Definitions snarfed from various parts of the FreeBSD headers: - */ -#define toupper(c) ((c)-0x20 * (((c) >= 'a') && ((c) <= 'z'))) - -static char hex2ascii(uintmax_t in) -{ - return (in < 10) ? '0' + in : 'a' + in - 10; -} - -/* Max number conversion buffer length: a u_quad_t in base 2, plus NUL byte. */ -#define MAXNBUF (sizeof(intmax_t) * CHAR_BIT + 1) - -struct snprintf_arg -{ - char * str; - size_t remain; -}; - -static void snprintf_func(int ch, void *arg) -{ - struct snprintf_arg *const info = arg; - - if (info->remain >= 2) - { - *info->str++ = ch; - info->remain--; - } -} - -/* - * Put a NUL-terminated ASCII number (base <= 36) in a buffer in reverse - * order; return an optional length and a pointer to the last character - * written in the buffer (i.e., the first character of the string). - * The buffer pointed to by `nbuf' must have length >= MAXNBUF. - */ -// FIXME: Using `unsigned` for `num` instead of `uintmax_t` means that we are -// going to truncate large numbers, but it avoids needing a library routine to -// handle division. -static char *ksprintn(char *nbuf, unsigned num, int base, int *lenp, int upper) -{ - char *p, c; - - p = nbuf; - *p = '\0'; - do - { - c = hex2ascii(num % base); - *++p = upper ? toupper(c) : c; - } while (num /= base); - if (lenp) - *lenp = p - nbuf; - return (p); -} - -/* - * Scaled down version of printf(3). - * - * Two additional formats: - * - * The format %b is supported to decode error registers. - * Its usage is: - * - * printf("reg=%b\n", regval, "<base><arg>*"); - * - * where <base> is the output base expressed as a control character, e.g. - * \10 gives octal; \20 gives hex. Each arg is a sequence of characters, - * the first of which gives the bit number to be inspected (origin 1), and - * the next characters (up to a control character, i.e. a character <= 32), - * give the name of the register. Thus: - * - * kvprintf("reg=%b\n", 3, "\10\2BITTWO\1BITONE\n"); - * - * would produce output: - * - * reg=3<BITTWO,BITONE> - * - * %D -- Hexdump, takes pointer and separator string: - * ("%6D", ptr, ":") -> XX:XX:XX:XX:XX:XX - * ("%*D", len, ptr, " " -> XX XX XX XX ... - */ -static int kvprintf(char const *fmt, - void (*func)(int, void *), - void * arg, - int radix, - va_list ap) -{ -#define PCHAR(c) \ - { \ - int cc = (c); \ - if (func) \ - (*func)(cc, arg); \ - else \ - *d++ = cc; \ - retval++; \ - } - char nbuf[MAXNBUF]; - char * d; - const char * p, *percent, *q; - unsigned char *up; - int ch, n; - uintmax_t num; - int base, lflag, qflag, tmp, width, ladjust, sharpflag, neg, sign, dot; - int cflag, hflag, jflag, tflag, zflag; - int dwidth, upper; - char padc; - int stop = 0, retval = 0; - char null_str[1] = {'\0'}; - - num = 0; - if (!func) - d = (char *)arg; - else - d = NULL; - - if (fmt == NULL) - fmt = null_str; - - if (radix < 2 || radix > 36) - radix = 10; - - for (;;) - { - padc = ' '; - width = 0; - while ((ch = (unsigned char)*fmt++) != '%' || stop) - { - if (ch == '\0') - return (retval); - PCHAR(ch); - } - percent = fmt - 1; - qflag = 0; - lflag = 0; - ladjust = 0; - sharpflag = 0; - neg = 0; - sign = 0; - dot = 0; - dwidth = 0; - upper = 0; - cflag = 0; - hflag = 0; - jflag = 0; - tflag = 0; - zflag = 0; - reswitch: - switch (ch = (unsigned char)*fmt++) - { - case '.': - dot = 1; - goto reswitch; - case '#': - sharpflag = 1; - goto reswitch; - case '+': - sign = 1; - goto reswitch; - case '-': - ladjust = 1; - goto reswitch; - case '%': - PCHAR(ch); - break; - case '*': - if (!dot) - { - width = va_arg(ap, int); - if (width < 0) - { - ladjust = !ladjust; - width = -width; - } - } - else - { - dwidth = va_arg(ap, int); - } - goto reswitch; - case '0': - if (!dot) - { - padc = '0'; - goto reswitch; - } - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - for (n = 0;; ++fmt) - { - n = n * 10 + ch - '0'; - ch = *fmt; - if (ch < '0' || ch > '9') - break; - } - if (dot) - dwidth = n; - else - width = n; - goto reswitch; - case 'b': - num = (unsigned int)va_arg(ap, int); - p = va_arg(ap, char *); - for (q = ksprintn(nbuf, num, *p++, NULL, 0); *q;) - PCHAR(*q--); - - if (num == 0) - break; - - for (tmp = 0; *p;) - { - n = *p++; - if (num & (1 << (n - 1))) - { - PCHAR(tmp ? ',' : '<'); - for (; (n = *p) > ' '; ++p) - PCHAR(n); - tmp = 1; - } - else - for (; *p > ' '; ++p) - continue; - } - if (tmp) - PCHAR('>'); - break; - case 'c': - PCHAR(va_arg(ap, int)); - break; - case 'D': - up = va_arg(ap, unsigned char *); - p = va_arg(ap, char *); - if (!width) - width = 16; - while (width--) - { - PCHAR(hex2ascii(*up >> 4)); - PCHAR(hex2ascii(*up & 0x0f)); - up++; - if (width) - for (q = p; *q; q++) - PCHAR(*q); - } - break; - case 'd': - case 'i': - base = 10; - sign = 1; - goto handle_sign; - case 'h': - if (hflag) - { - hflag = 0; - cflag = 1; - } - else - hflag = 1; - goto reswitch; - case 'j': - jflag = 1; - goto reswitch; - case 'l': - if (lflag) - { - lflag = 0; - qflag = 1; - } - else - lflag = 1; - goto reswitch; - case 'n': - if (jflag) - *(va_arg(ap, intmax_t *)) = retval; - else if (qflag) - *(va_arg(ap, long long *)) = retval; - else if (lflag) - *(va_arg(ap, long *)) = retval; - else if (zflag) - *(va_arg(ap, size_t *)) = retval; - else if (hflag) - *(va_arg(ap, short *)) = retval; - else if (cflag) - *(va_arg(ap, char *)) = retval; - else - *(va_arg(ap, int *)) = retval; - break; - case 'o': - base = 8; - goto handle_nosign; - case 'p': - base = 16; - sharpflag = (width == 0); - sign = 0; - num = (size_t)va_arg(ap, void *); - goto number; - case 'q': - qflag = 1; - goto reswitch; - case 'r': - base = radix; - if (sign) - goto handle_sign; - goto handle_nosign; - case 's': - p = va_arg(ap, char *); - if (p == NULL) - p = null_str; - if (!dot) - n = strlen(p); - else - for (n = 0; n < dwidth && p[n]; n++) - continue; - - width -= n; - - if (!ladjust && width > 0) - while (width--) - PCHAR(padc); - while (n--) - PCHAR(*p++); - if (ladjust && width > 0) - while (width--) - PCHAR(padc); - break; - case 't': - tflag = 1; - goto reswitch; - case 'u': - base = 10; - goto handle_nosign; - case 'X': - upper = 1; - case 'x': - base = 16; - goto handle_nosign; - case 'y': - base = 16; - sign = 1; - goto handle_sign; - case 'z': - zflag = 1; - goto reswitch; - handle_nosign: - sign = 0; - if (jflag) - num = va_arg(ap, uintmax_t); - else if (qflag) - num = va_arg(ap, unsigned long long); - else if (tflag) - num = va_arg(ap, ptrdiff_t); - else if (lflag) - num = va_arg(ap, unsigned long); - else if (zflag) - num = va_arg(ap, size_t); - else if (hflag) - num = (unsigned short)va_arg(ap, int); - else if (cflag) - num = (unsigned char)va_arg(ap, int); - else - num = va_arg(ap, unsigned int); - goto number; - handle_sign: - if (jflag) - num = va_arg(ap, intmax_t); - else if (qflag) - num = va_arg(ap, long long); - else if (tflag) - num = va_arg(ap, ptrdiff_t); - else if (lflag) - num = va_arg(ap, long); - else if (zflag) - num = va_arg(ap, ssize_t); - else if (hflag) - num = (short)va_arg(ap, int); - else if (cflag) - num = (char)va_arg(ap, int); - else - num = va_arg(ap, int); - number: - if (sign && (intmax_t)num < 0) - { - neg = 1; - num = -(intmax_t)num; - } - p = ksprintn(nbuf, num, base, &n, upper); - tmp = 0; - if (sharpflag && num != 0) - { - if (base == 8) - tmp++; - else if (base == 16) - tmp += 2; - } - if (neg) - tmp++; - - if (!ladjust && padc == '0') - dwidth = width - tmp; - width -= tmp + (dwidth > n ? dwidth : n); - dwidth -= n; - if (!ladjust) - while (width-- > 0) - PCHAR(' '); - if (neg) - PCHAR('-'); - if (sharpflag && num != 0) - { - if (base == 8) - { - PCHAR('0'); - } - else if (base == 16) - { - PCHAR('0'); - PCHAR('x'); - } - } - while (dwidth-- > 0) - PCHAR('0'); - - while (*p) - PCHAR(*p--); - - if (ladjust) - while (width-- > 0) - PCHAR(' '); - - break; - default: - while (percent < fmt) - PCHAR(*percent++); - /* - * Since we ignore an formatting argument it is no - * longer safe to obey the remaining formatting - * arguments as the arguments will no longer match - * the format specs. - */ - stop = 1; - break; - } - } -#undef PCHAR -} - -/* - * Scaled down version of vsnprintf(3). - */ -int __cheri_libcall vsnprintf(char *str, size_t size, const char *format, va_list ap) -{ - struct snprintf_arg info; - int retval; - - info.str = str; - info.remain = size; - retval = kvprintf(format, snprintf_func, &info, 10, ap); - if (info.remain >= 1) - *info.str++ = '\0'; - return (retval); -} - -#define UART_REG_QUEUE 0 -#define UART_REG_LINESTAT (5) -#define UART_REG_STATUS_RX (0x01) -#define UART_REG_STATUS_TX (0x20) - - -[[cheri::interrupt_state(disabled)]] -static void uart16550_txbuffer(const char *ptr) -{ - long flags; - volatile uint32_t *uart16550 = MMIO_CAPABILITY(uint32_t, uart); - - for (int i = 0; i < PRT_MAX_SIZE && ptr[i]; i++) - { - while ((uart16550[UART_REG_LINESTAT] & UART_REG_STATUS_TX) == 0) {} - - uart16550[UART_REG_QUEUE] = ptr[i]; - } -} - -int __cheri_libcall printf(const char *format, ...) -{ - char buf[PRT_MAX_SIZE]; - int rv; - va_list ap; - - va_start(ap, format); - rv = vsnprintf(buf, PRT_MAX_SIZE, format, ap); - va_end(ap); - uart16550_txbuffer(buf); - - return rv; -} - -int __cheri_libcall snprintf(char *str, size_t size, const char *format, ...) -{ - int rv; - va_list ap; - - va_start(ap, format); - rv = vsnprintf(str, size, format, ap); - va_end(ap); - - return rv; -}
diff --git a/sdk/lib/stdio/printf.cc b/sdk/lib/stdio/printf.cc new file mode 100644 index 0000000..8fc5f51 --- /dev/null +++ b/sdk/lib/stdio/printf.cc
@@ -0,0 +1,552 @@ +/*- + * Copyright (c) 1986, 1988, 1991, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)subr_prf.c 8.3 (Berkeley) 1/21/94 + */ + +/* __FBSDID("$FreeBSD: stable/8/sys/kern/subr_prf.c 210305 2010-07-20 18:55:13Z + * jkim $"); */ + +#include <cdefs.h> +#include <cheri-builtins.h> +#include <compartment.h> +#include <cstdint> +#include <function_wrapper.hh> +#include <inttypes.h> +#include <platform-uart.hh> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +/* + * Definitions snarfed from various parts of the FreeBSD headers: + */ +namespace +{ + __always_inline char toupper(char c) + { + return ((c)-0x20 * (((c) >= 'a') && ((c) <= 'z'))); + } + + static char hex2ascii(uintmax_t in) + { + return (in < 10) ? '0' + in : 'a' + in - 10; + } + +/* Max number conversion buffer length: a u_quad_t in base 2, plus NUL byte. */ +#define MAXNBUF (sizeof(intmax_t) * CHAR_BIT + 1) + + /* + * Put a NUL-terminated ASCII number (base <= 36) in a buffer in reverse + * order; return an optional length and a pointer to the last character + * written in the buffer (i.e., the first character of the string). + * The buffer pointed to by `nbuf' must have length >= MAXNBUF. + */ + // FIXME: Using `unsigned` for `num` instead of `uintmax_t` means that we + // are going to truncate large numbers, but it avoids needing a library + // routine to handle division. + static char * + ksprintn(char *nbuf, unsigned num, int base, int *lenp, int upper) + { + char *p, c; + + p = nbuf; + *p = '\0'; + do + { + c = hex2ascii(num % base); + *++p = upper ? toupper(c) : c; + } while (num /= base); + if (lenp) + *lenp = p - nbuf; + return (p); + } + + /* + * Scaled down version of printf(3). + * + * Two additional formats: + * + * The format %b is supported to decode error registers. + * Its usage is: + * + * printf("reg=%b\n", regval, "<base><arg>*"); + * + * where <base> is the output base expressed as a control character, e.g. + * \10 gives octal; \20 gives hex. Each arg is a sequence of characters, + * the first of which gives the bit number to be inspected (origin 1), and + * the next characters (up to a control character, i.e. a character <= 32), + * give the name of the register. Thus: + * + * kvprintf("reg=%b\n", 3, "\10\2BITTWO\1BITONE\n"); + * + * would produce output: + * + * reg=3<BITTWO,BITONE> + * + * %D -- Hexdump, takes pointer and separator string: + * ("%6D", ptr, ":") -> XX:XX:XX:XX:XX:XX + * ("%*D", len, ptr, " " -> XX XX XX XX ... + */ + __noinline int kvprintf(char const *fmt, + FunctionWrapper<void(int)> func, + void *arg, + int radix, + va_list ap) + { + char nbuf[MAXNBUF]; + const char *p, *percent, *q; + unsigned char *up; + int ch, n; + uintmax_t num; + int base, lflag, qflag, tmp, width, ladjust, sharpflag, neg, sign, dot; + int cflag, hflag, jflag, tflag, zflag; + int dwidth, upper; + char padc; + int stop = 0, retval = 0; + const char *emptyString = ""; + + auto putchar = [&](int c) { + func(c); + retval++; + }; + + if (fmt == nullptr) + { + fmt = emptyString; + } + + if (radix < 2 || radix > 36) + { + radix = 10; + } + + for (;;) + { + padc = ' '; + width = 0; + while ((ch = static_cast<unsigned char>(*fmt++)) != '%' || stop) + { + if (ch == '\0') + return (retval); + putchar(ch); + } + percent = fmt - 1; + qflag = 0; + lflag = 0; + ladjust = 0; + sharpflag = 0; + neg = 0; + sign = 0; + dot = 0; + dwidth = 0; + upper = 0; + cflag = 0; + hflag = 0; + jflag = 0; + tflag = 0; + zflag = 0; + reswitch: + switch (ch = static_cast<unsigned char>(*fmt++)) + { + case '.': + dot = 1; + goto reswitch; // NOLINT + case '#': + sharpflag = 1; + goto reswitch; // NOLINT + case '+': + sign = 1; + goto reswitch; // NOLINT + case '-': + ladjust = 1; + goto reswitch; // NOLINT + case '%': + putchar(ch); + break; + case '*': + if (!dot) + { + width = va_arg(ap, int); + if (width < 0) + { + ladjust = !ladjust; + width = -width; + } + } + else + { + dwidth = va_arg(ap, int); + } + goto reswitch; // NOLINT + case '0': + if (!dot) + { + padc = '0'; + goto reswitch; // NOLINT + } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + for (n = 0;; ++fmt) + { + n = n * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') + break; + } + if (dot) + dwidth = n; + else + width = n; + goto reswitch; // NOLINT + case 'b': + num = static_cast<unsigned int>(va_arg(ap, int)); + p = va_arg(ap, char *); + for (q = ksprintn(nbuf, num, *p++, nullptr, 0); *q;) + putchar(*q--); + + if (num == 0) + break; + + for (tmp = 0; *p;) + { + n = *p++; + if (num & (1 << (n - 1))) + { + putchar(tmp ? ',' : '<'); + for (; (n = *p) > ' '; ++p) + putchar(n); + tmp = 1; + } + else + for (; *p > ' '; ++p) + continue; + } + if (tmp) + putchar('>'); + break; + case 'c': + putchar(va_arg(ap, int)); + break; + case 'D': + up = va_arg(ap, unsigned char *); + p = va_arg(ap, char *); + if (!width) + width = 16; + while (width--) + { + putchar(hex2ascii(*up >> 4)); + putchar(hex2ascii(*up & 0x0f)); + up++; + if (width) + for (q = p; *q; q++) + putchar(*q); + } + break; + case 'd': + case 'i': + base = 10; + sign = 1; + goto handle_sign; // NOLINT + case 'h': + if (hflag) + { + hflag = 0; + cflag = 1; + } + else + hflag = 1; + goto reswitch; // NOLINT + case 'j': + jflag = 1; + goto reswitch; // NOLINT + case 'l': + if (lflag) + { + lflag = 0; + qflag = 1; + } + else + lflag = 1; + goto reswitch; // NOLINT + case 'n': + if (jflag) + *(va_arg(ap, intmax_t *)) = retval; + else if (qflag) + *(va_arg(ap, long long *)) = retval; + else if (lflag) + *(va_arg(ap, long *)) = retval; + else if (zflag) + *(va_arg(ap, size_t *)) = retval; + else if (hflag) + *(va_arg(ap, short *)) = static_cast<short>(retval); + else if (cflag) + *(va_arg(ap, char *)) = retval; + else + *(va_arg(ap, int *)) = retval; + break; + case 'o': + base = 8; + goto handle_nosign; // NOLINT + case 'p': + base = 16; + sharpflag = (width == 0); + sign = 0; + num = static_cast<size_t>( + reinterpret_cast<uintptr_t>(va_arg(ap, void *))); + goto number; // NOLINT + case 'q': + qflag = 1; + goto reswitch; // NOLINT + case 'r': + base = radix; + if (sign) + goto handle_sign; // NOLINT + goto handle_nosign; // NOLINT + case 's': + p = va_arg(ap, char *); + if (p == nullptr) + { + p = emptyString; + } + if (!dot) + { + n = strlen(p); + } + else + { + for (n = 0; n < dwidth && p[n]; n++) + { + continue; + } + } + + width -= n; + + if (!ladjust && width > 0) + { + while (width--) + { + putchar(padc); + } + } + while (n--) + { + putchar(*p++); + } + if (ladjust && width > 0) + { + while (width--) + { + putchar(padc); + } + } + break; + case 't': + tflag = 1; + goto reswitch; // NOLINT + case 'u': + base = 10; + goto handle_nosign; // NOLINT + case 'X': + upper = 1; + case 'x': + base = 16; + goto handle_nosign; // NOLINT + case 'y': + base = 16; + sign = 1; + goto handle_sign; // NOLINT + case 'z': + zflag = 1; + goto reswitch; // NOLINT + handle_nosign: + sign = 0; + if (jflag) + num = va_arg(ap, uintmax_t); + else if (qflag) + num = va_arg(ap, unsigned long long); + else if (tflag) + num = va_arg(ap, ptrdiff_t); + else if (lflag) + num = va_arg(ap, unsigned long); + else if (zflag) + num = va_arg(ap, size_t); + else if (hflag) + num = static_cast<unsigned short>(va_arg(ap, int)); + else if (cflag) + num = static_cast<unsigned char>(va_arg(ap, int)); + else + num = va_arg(ap, unsigned int); + goto number; // NOLINT + handle_sign: + if (jflag) + num = va_arg(ap, intmax_t); + else if (qflag) + num = va_arg(ap, long long); + else if (tflag) + num = va_arg(ap, ptrdiff_t); + else if (lflag) + num = va_arg(ap, long); + else if (zflag) + num = va_arg(ap, ssize_t); + else if (hflag) + num = static_cast<short>(va_arg(ap, int)); + else if (cflag) + num = static_cast<char>(va_arg(ap, int)); + else + num = va_arg(ap, int); + number: + if (sign && static_cast<intmax_t>(num) < 0) + { + neg = 1; + num = -static_cast<intmax_t>(num); + } + p = ksprintn(nbuf, num, base, &n, upper); + tmp = 0; + if (sharpflag && num != 0) + { + if (base == 8) + tmp++; + else if (base == 16) + tmp += 2; + } + if (neg) + tmp++; + + if (!ladjust && padc == '0') + dwidth = width - tmp; + width -= tmp + (dwidth > n ? dwidth : n); + dwidth -= n; + if (!ladjust) + while (width-- > 0) + putchar(' '); + if (neg) + putchar('-'); + if (sharpflag && num != 0) + { + if (base == 8) + { + putchar('0'); + } + else if (base == 16) + { + putchar('0'); + putchar('x'); + } + } + while (dwidth-- > 0) + putchar('0'); + + while (*p) + putchar(*p--); + + if (ladjust) + while (width-- > 0) + putchar(' '); + + break; + default: + while (percent < fmt) + putchar(*percent++); + /* + * Since we ignore an formatting argument it is no + * longer safe to obey the remaining formatting + * arguments as the arguments will no longer match + * the format specs. + */ + stop = 1; + break; + } + } + } + +} // namespace + +/* + * Scaled down version of vsnprintf(3). + */ +int __cheri_libcall +vsnprintf(char *str, // NOLINT (clang-tidy spuriously thinks this should be + // const, even though it's being written to) + size_t size, + const char *format, + va_list ap) +{ + struct Buffer + { + char *str; + size_t remain; + } info = {str, size}; + auto callback = [&](int ch) { + if (info.remain >= 2) + { + *info.str++ = ch; + info.remain--; + } + }; + int retval = kvprintf(format, callback, &info, 10, ap); + if (info.remain >= 1) + *info.str++ = '\0'; + return (retval); +} + +[[cheri::interrupt_state(disabled)]] int __cheri_libcall +vfprintf(FILE *stream, const char *fmt, va_list ap) +{ + return kvprintf( + fmt, + [=](int ch) { static_cast<volatile Uart *>(stream)->blocking_write(ch); }, + nullptr, + 10, + ap); +} + +int __cheri_libcall snprintf(char *str, size_t size, const char *format, ...) +{ + int rv; + va_list ap; + + va_start(ap, format); + rv = vsnprintf(str, size, format, ap); + va_end(ap); + + return rv; +}
diff --git a/sdk/lib/stdio/xmake.lua b/sdk/lib/stdio/xmake.lua index 01e39cc..2596268 100644 --- a/sdk/lib/stdio/xmake.lua +++ b/sdk/lib/stdio/xmake.lua
@@ -1,4 +1,4 @@ library("stdio") add_deps("string") set_default(false) - add_files("printf.c") + add_files("printf.cc")
diff --git a/tests/stdio-test.cc b/tests/stdio-test.cc new file mode 100644 index 0000000..a23aa0b --- /dev/null +++ b/tests/stdio-test.cc
@@ -0,0 +1,22 @@ +#include <cstdio> +#define TEST_NAME "stdio" +#include "tests.hh" +#include <stdio.h> + +void test_stdio() +{ + debug_log("Printing 'Hello, world!' to stdout"); + printf("Hello, world!\n"); + debug_log("Printing 'Hello, world!' to stderr"); + fprintf(stderr, "Hello, world!\n"); + const size_t BufferSize = 64; + char buffer[BufferSize]; + snprintf(buffer, BufferSize, "%d", 42); + TEST(strcmp(buffer, "42") == 0, + "snprintf(\"%d\", 42) gave {}", + std::string_view{buffer, BufferSize}); + snprintf(buffer, BufferSize, "%d", -42); + TEST(strcmp(buffer, "-42") == 0, + "snprintf(\"%d\", -42) gave {}", + std::string_view{buffer, BufferSize}); +}
diff --git a/tests/test-runner.cc b/tests/test-runner.cc index d96a8e0..b59a823 100644 --- a/tests/test-runner.cc +++ b/tests/test-runner.cc
@@ -108,6 +108,7 @@ run_timed("All tests", []() { run_timed("MMIO", test_mmio); + run_timed("stdio", test_stdio); run_timed("Static sealing", test_static_sealing); run_timed("Crash recovery", test_crash_recovery); run_timed("Compartment calls", test_compartment_call);
diff --git a/tests/tests.hh b/tests/tests.hh index 4fec070..ffb71d0 100644 --- a/tests/tests.hh +++ b/tests/tests.hh
@@ -19,6 +19,7 @@ __cheri_compartment("check_pointer_test") void test_check_pointer(); __cheri_compartment("misc_test") void test_misc(); __cheri_compartment("static_sealing_test") void test_static_sealing(); +__cheri_compartment("stdio_test") void test_stdio(); // Simple tests don't need a separate compartment. void test_global_constructors();
diff --git a/tests/xmake.lua b/tests/xmake.lua index 4cb7213..48c294a 100644 --- a/tests/xmake.lua +++ b/tests/xmake.lua
@@ -49,9 +49,11 @@ -- Test the futex implementation test("futex") -- Test locks built on top of the futex -test("queue") --- Test queues test("locks") +-- Test queues +test("queue") +-- Test minimal stdio implementation +test("stdio") -- Test the static sealing types test("static_sealing") compartment("static_sealing_inner") @@ -92,6 +94,7 @@ -- Helper libraries add_deps("freestanding", "string", "crt", "cxxrt", "atomic_fixed", "compartment_helpers", "debug") add_deps("message_queue", "locks", "event_group") + add_deps("stdio") -- Tests add_deps("mmio_test") add_deps("eventgroup_test") @@ -108,6 +111,7 @@ add_deps("compartment_calls_test", "compartment_calls_inner", "compartment_calls_inner_with_handler") add_deps("check_pointer_test") add_deps("misc_test") + add_deps("stdio_test") -- Set the thread entry point to the test runner. on_load(function(target) target:values_set("board", "$(board)")