diff --git a/include/sys/cbprintf.h b/include/sys/cbprintf.h index 39aa37c65d5b3..e69f762c5a3bb 100644 --- a/include/sys/cbprintf.h +++ b/include/sys/cbprintf.h @@ -9,6 +9,7 @@ #include #include +#include #include #ifdef CONFIG_CBPRINTF_LIBC_SUBSTS @@ -44,6 +45,108 @@ extern "C" { */ typedef int (*cbprintf_cb)(/* int c, void *ctx */); +/** @brief Capture state required to output formatted data later. + * + * Like cbprintf() but instead of processing the arguments and emitting the + * formatted results immediately all arguments are captured so this can be + * done in a different context, e.g. when the output function can block. + * + * In addition to the values extracted from arguments this will ensure that + * copies are made of the necessary portions of any string parameters that are + * not confirmed to be stored in read-only memory (hence assumed to be safe to + * refer to directly later). + * + * @note This function is available only when `CONFIG_CBPRINTF_COMPLETE` is + * selected. + * + * @param packaged pointer to where the packaged data can be stored. Pass a + * null pointer to store nothing but still calculate the total space required. + * The data stored here is relocatable, that is it can be moved to another + * contiguous block of memory. + * + * @param len on input this must be set to the number of bytes available at @p + * packaged. If @p packaged is NULL the input value is ignored. On output + * the referenced value will be updated to the number of bytes required to + * completely store the packed information. The @p len parameter must not be + * null. + * + * @param format a standard ISO C format string with characters and conversion + * specifications. + * + * @param ... arguments corresponding to the conversion specifications found + * within @p format. + * + * @retval nonegative the number of bytes successfully stored at @p packaged. + * This will not exceed the input value of @p *len. + * @retval -EINVAL if @p format or @p len are not acceptable + * @retval -ENOSPC if @p packaged was not null and the space required to store + * exceed the input value of @p *len. + */ +__printf_like(3, 4) +int cbprintf_package(uint8_t *packaged, + size_t *len, + const char *format, + ...); + +/** @brief Capture state required to output formatted data later. + * + * Like cbprintf() but instead of processing the arguments and emitting the + * formatted results immediately all arguments are captured so this can be + * done in a different context, e.g. when the output function can block. + * + * In addition to the values extracted from arguments this will ensure that + * copies are made of the necessary portions of any string parameters that are + * not confirmed to be stored in read-only memory (hence assumed to be safe to + * refer to directly later). + * + * @note This function is available only when `CONFIG_CBPRINTF_COMPLETE` is + * selected. + * + * @param packaged pointer to where the packaged data can be stored. Pass a + * null pointer to store nothing but still calculate the total space required. + * The data stored here is relocatable, that is it can be moved to another + * contiguous block of memory. + * + * @param len on input this must be set to the number of bytes available at @p + * packaged. If @p packaged is NULL the input value is ignored. On output + * the referenced value will be updated to the number of bytes required to + * completely store the packed information. The @p len parameter must not be + * null. + * + * @param format a standard ISO C format string with characters and conversion + * specifications. + * + * @param ap captured stack arguments corresponding to the conversion + * specifications found within @p format. + * + * @retval nonegative the number of bytes successfully stored at @p packaged. + * This will not exceed the input value of @p *len. + * @retval -EINVAL if @p format or @p len are not acceptable + * @retval -ENOSPC if @p packaged was not null and the space required to store + * exceed the input value of @p *len. + */ +int cbvprintf_package(uint8_t *packaged, + size_t *len, + const char *format, + va_list ap); + +/** @brief Generate the output for a previously captured format + * operation. + * + * @param out the function used to emit each generated character. + * + * @param ctx context provided when invoking out + * + * @param packaged the data required to generate the formatted output, as + * captured by cbprintf_package() or cbvprintf_package(). + * + * @return the number of characters printed, or a negative error value + * returned from invoking @p out. + */ +int cbpprintf(cbprintf_cb out, + void *ctx, + const uint8_t *packaged); + /** @brief *printf-like output through a callback. * * This is essentially printf() except the output is generated diff --git a/lib/os/cbprintf_complete.c b/lib/os/cbprintf_complete.c index f2d7adb6b26f4..a5130e4cbddda 100644 --- a/lib/os/cbprintf_complete.c +++ b/lib/os/cbprintf_complete.c @@ -18,6 +18,7 @@ #include #include #include +#include /* newlib doesn't declare this function unless __POSIX_VISIBLE >= 200809. No * idea how to make that happen, so lets put it right here. @@ -178,6 +179,7 @@ union argument_value { /* For PTR conversions */ void *ptr; + const void *const_ptr; }; /* Structure capturing all attributes of a conversion @@ -307,6 +309,51 @@ struct conversion { }; }; +/* State used for processing format conversions with values taken from + * varargs. + */ +struct cbprintf_state { + /* Alternative sources for converison data. */ + union { + /* Arguments are extracted from the call stack. + * + * Use this member when #is_packaged is false. + */ + va_list ap; + + /* Arguments have been packaged into a contiguous region. + * + * Use this member when #is_packaged is true. + */ + const uint8_t *packaged; + }; + + /* A conversion specifier extracted with + * extract_conversion() + */ + struct conversion conv; + + /* The value from ap extracted with data from conv. */ + union argument_value value; + + /* The width to use when emitting the value. + * + * Negative values indicate this is not used. + */ + int width; + + /* The precision to use when emitting the value. + * + * Negative values indicate this is not used. + */ + int precision; + + /* Indicates whether the ap or packaged fields are used as the + * source of format data. + */ + bool is_packaged; +}; + /** Get a size represented as a sequence of decimal digits. * * @param[inout] str where to read from. Updated to point to the first @@ -1319,11 +1366,705 @@ static int outs(cbprintf_cb out, return (int)count; } -int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) +/* Pull from the stack all information related to the conversion + * specified in state. + */ +static void pull_va_args(struct cbprintf_state *state) +{ + struct conversion *const conv = &state->conv; + union argument_value *const value = &state->value; + + if (conv->width_star) { + state->width = va_arg(state->ap, int); + } else { + state->width = -1; + } + + if (conv->prec_star) { + state->precision = va_arg(state->ap, int); + } else { + state->precision = -1; + } + + enum specifier_cat_enum specifier_cat + = (enum specifier_cat_enum)conv->specifier_cat; + enum length_mod_enum length_mod + = (enum length_mod_enum)conv->length_mod; + + /* Extract the value based on the argument category and length. + * + * Note that the length modifier doesn't affect the value of a + * pointer argument. + */ + if (specifier_cat == SPECIFIER_SINT) { + switch (length_mod) { + default: + case LENGTH_NONE: + case LENGTH_HH: + case LENGTH_H: + value->sint = va_arg(state->ap, int); + break; + case LENGTH_L: + if (WCHAR_IS_SIGNED && (conv->specifier == 'c')) { + value->sint = (wchar_t)va_arg(state->ap, + WINT_TYPE); + } else { + value->sint = va_arg(state->ap, long); + } + break; + case LENGTH_LL: + value->sint = + (sint_value_type)va_arg(state->ap, long long); + break; + case LENGTH_J: + value->sint = + (sint_value_type)va_arg(state->ap, intmax_t); + break; + case LENGTH_Z: /* size_t */ + case LENGTH_T: /* ptrdiff_t */ + /* Though ssize_t is the signed equivalent of + * size_t for POSIX, there is no uptrdiff_t. + * Assume that size_t and ptrdiff_t are the + * unsigned and signed equivalents of each + * other. This can be checked in a platform + * test. + */ + value->sint = + (sint_value_type)va_arg(state->ap, ptrdiff_t); + break; + } + if (length_mod == LENGTH_HH) { + value->sint = (char)value->sint; + } else if (length_mod == LENGTH_H) { + value->sint = (short)value->sint; + } + } else if (specifier_cat == SPECIFIER_UINT) { + switch (length_mod) { + default: + case LENGTH_NONE: + case LENGTH_HH: + case LENGTH_H: + value->uint = va_arg(state->ap, unsigned int); + break; + case LENGTH_L: + if ((!WCHAR_IS_SIGNED) && (conv->specifier == 'c')) { + value->uint = (wchar_t)va_arg(state->ap, + WINT_TYPE); + } else { + value->uint = va_arg(state->ap, unsigned long); + } + break; + case LENGTH_LL: + value->uint = + (uint_value_type)va_arg(state->ap, + unsigned long long); + break; + case LENGTH_J: + value->uint = + (uint_value_type)va_arg(state->ap, + uintmax_t); + break; + case LENGTH_Z: /* size_t */ + case LENGTH_T: /* ptrdiff_t */ + value->uint = + (uint_value_type)va_arg(state->ap, size_t); + break; + } + if (length_mod == LENGTH_HH) { + value->uint = (unsigned char)value->uint; + } else if (length_mod == LENGTH_H) { + value->uint = (unsigned short)value->uint; + } + } else if (specifier_cat == SPECIFIER_FP) { + if (length_mod == LENGTH_UPPER_L) { + value->ldbl = va_arg(state->ap, long double); + } else { + value->dbl = va_arg(state->ap, double); + } + } else if (specifier_cat == SPECIFIER_PTR) { + value->ptr = va_arg(state->ap, void *); + } +} + +/** + * @brief Check if address is in read only section. + * + * @param addr Address. + * + * @return True if address identified within read only section. + */ +static inline bool ptr_in_rodata(const char *addr) +{ +#if defined(CBPRINTF_VIA_UNIT_TEST) + /* Unit test is X86 (or other host) but not using Zephyr + * linker scripts. + */ +#define RO_START 0 +#define RO_END 0 +#elif defined(CONFIG_ARC) || defined(CONFIG_ARM) || defined(CONFIG_X86) \ + || defined(CONFIG_RISCV) + extern char _image_rodata_start[]; + extern char _image_rodata_end[]; +#define RO_START _image_rodata_start +#define RO_END _image_rodata_end +#elif defined(CONFIG_NIOS2) || defined(CONFIG_RISCV) + extern char _image_rom_start[]; + extern char _image_rom_end[]; +#define RO_START _image_rom_start +#define RO_END _image_rom_end +#elif defined(CONFIG_XTENSA) + extern char _rodata_start[]; + extern char _rodata_end[]; +#define RO_START _rodata_start +#define RO_END _rodata_end +#else +#define RO_START 0 +#define RO_END 0 +#endif + + return ((addr >= (const char *)RO_START) && + (addr < (const char *)RO_END)); +} + +/* Structure capturing formatter state along with additional information + * required to package up the material necessary to perform the conversion. + */ +struct package_state { + /* Standard formatting state */ + struct cbprintf_state state; + + /* Pointer into a region where packaged data can be stored. + * + * This becomes null if packaging the arguments would overrun the + * capacity. + */ + uint8_t *pp; + + /* Number of bytes that can be stored at the originally provided + * buffer. + * + * This is not adjusted along with pp. + */ + size_t capacity; + + /* Number of bytes required to completely store the packaged data. + */ + size_t length; +}; + +/* Store a sequence of bytes into the package buffer, as long as there is room + * for it. + * + * @param pst pointer to the state. + * + * @param sp where the data comes from + * + * @param number of bytes to store. + */ +static void pack_sequence(struct package_state *pst, + const void *sp, + size_t len) +{ + /* If we're going to be asked to write more than there's room + * for the write will fail, so don't bother doing it, and + * prevent further writes. + */ + if ((pst->pp != NULL) + && ((pst->length + len) > pst->capacity)) { + pst->pp = NULL; + } + + /* Aggregate the full length. */ + pst->length += len; + + /* If we can write it, do so */ + if (pst->pp) { + memcpy(pst->pp, sp, len); + pst->pp += len; + } +} + +/* Pull data out of the package structure. + * + * @param state provides the pointer to the remaining packaged data. + * + * @param dp where the decoded data should be stored + * + * @param len number of bytes to be copied out. + */ +static void unpack_sequence(struct cbprintf_state *state, + void *dp, + size_t len) +{ + __ASSERT_NO_MSG(state->is_packaged); + + /* By construction, validation, and use we know the packaged state has + * the values, so we don't need to check for capacity or whether + * there's a valid source pointer. We just need to maintain the + * pointer to the unconsumed data. + */ + memcpy(dp, state->packaged, len); + state->packaged += len; +} + +/* Pack a single value of scalar type. + * + * @param _pst pointer to a package_state object. + * + * @param _val reference to a scalar value providing type, size, and value to + * be packed. + */ +#define PACK_VALUE(_pst, _val) pack_sequence((_pst), &(_val), sizeof(_val)) + +/* Pack a single value of scalar type after casting it to a target type. + * + * @param _pst pointer to a package_state object. + * + * @param _val reference to a scalar value + * + * @param _type the type to which _val should be cast prior to storing it. + */ +#define PACK_CAST_VALUE(_pst, _val, _type) do { \ + _type v = (_type)(_val); \ +\ + PACK_VALUE(_pst, v); \ +} while (0) + +/* Unpack a single value of scalar type. + * + * @param _state pointer to a cbprintf_state object. + * + * @param _val reference to a scalar value providing type, size, and + * destination for unpacked value. + */ +#define UNPACK_VALUE(_state, _val) \ + unpack_sequence((_state), &(_val), sizeof(_val)) + +/* Unpack a typed signed integral value into the argument value sint field. + * + * @param _state pointer to a cbprintf_state object. + * + * @param _type the type of the packaged signed value. + */ +#define UNPACK_CAST_SINT_VALUE(_state, _type) do { \ + _type v; \ +\ + UNPACK_VALUE(_state, v); \ + (_state)->value.sint = (sint_value_type)v; \ +} while (0) + +/* Unpack a typed unsigned integral value into the argument value uint field. + * + * @param _state pointer to a cbprintf_state object. + * + * @param _type the type of the packaged signed value. + */ +#define UNPACK_CAST_UINT_VALUE(_state, _type) do { \ + _type v; \ +\ + UNPACK_VALUE(_state, v); \ + (_state)->value.uint = (uint_value_type)v; \ +} while (0) + +/* Package a string value. + * + * String values are the only ones that are deferenced in the process of + * formatting. Where a string can be shown to be persistent (i.e. lives in a + * read-only memory) we need only store the pointer. Otherwise we can't rely + * on the pointed-to memory being accessible after the call returns, so we + * need to copy out the portion of the string that will be formatted. That + * would be the full ASCIIZ value unless a precision is provided, in which + * case the precision caps the maximum output. + * + * @param pst pointer to package state. The string to be formatted is present + * in the contained argument value. + */ +static void pack_str(struct package_state *pst) +{ + union argument_value *const value = &pst->state.value; + const struct cbprintf_state *const state = &pst->state; + const char *str = (const char *)value->ptr; + uint8_t inlined = !ptr_in_rodata(str); + + PACK_VALUE(pst, inlined); + if (inlined) { + uint8_t nul = 0; + size_t len; + + if (state->precision >= 0) { + len = strnlen(str, state->precision); + } else { + len = strlen(str); + } + + pack_sequence(pst, str, len); + PACK_VALUE(pst, nul); + } else { + PACK_VALUE(pst, value->ptr); + } +} + +/* Extract a pointer from packaged data. + * + * @param state pointer to the state, which contains a pointer to the + * unconsumed data. + * + * @return a pointer to the string, which is either inline in the packaged + * data or is a pointer extracted from the packaged data. + */ +static const char *unpack_str(struct cbprintf_state *state) +{ + const char *str; + uint8_t inlined; + + UNPACK_VALUE(state, inlined); + if (inlined) { + str = (const char *)state->packaged; + state->packaged += 1 + strlen(str); + } else { + UNPACK_VALUE(state, str); + } + + return str; +} + +/* Given a conversion and its argument store everything necessary to + * reconstruct the conversion and argument in another context. + * + * @param pst pointer to the package state, including the conversion, the + * argument value, and information about where to store the data. + */ +static void pack_conversion(struct package_state *pst) +{ + struct cbprintf_state *const state = &pst->state; + struct conversion *const conv = &state->conv; + union argument_value *const value = &state->value; + enum specifier_cat_enum specifier_cat + = (enum specifier_cat_enum)conv->specifier_cat; + enum length_mod_enum length_mod + = (enum length_mod_enum)conv->length_mod; + + /* If we got width and precision from the stack, it needs to go into + * the package. + */ + if (conv->width_star) { + PACK_VALUE(pst, state->width); + } + + if (conv->prec_star) { + PACK_VALUE(pst, state->precision); + } + + /* We package the argument in its native form, not the promoted form + * that was pushed on the stack nor the promoted form stored as the + * argument value. + */ + switch (specifier_cat) { + case SPECIFIER_SINT: + switch (length_mod) { + case LENGTH_NONE: + PACK_CAST_VALUE(pst, value->sint, int); + break; + case LENGTH_HH: + PACK_CAST_VALUE(pst, value->sint, signed char); + break; + case LENGTH_H: + PACK_CAST_VALUE(pst, value->sint, short); + break; + case LENGTH_L: + if (WCHAR_IS_SIGNED && (conv->specifier == 'c')) { + PACK_CAST_VALUE(pst, value->sint, WINT_TYPE); + } else { + PACK_CAST_VALUE(pst, value->sint, long); + } + break; + case LENGTH_LL: + PACK_CAST_VALUE(pst, value->sint, long long); + break; + case LENGTH_J: + PACK_CAST_VALUE(pst, value->sint, intmax_t); + break; + case LENGTH_Z: /* size_t */ + case LENGTH_T: /* ptrdiff_t */ + /* Though ssize_t is the signed equivalent of + * size_t for POSIX, there is no uptrdiff_t. + * Assume that size_t and ptrdiff_t are the + * unsigned and signed equivalents of each + * other. This can be checked in a platform + * test. + */ + PACK_CAST_VALUE(pst, value->sint, ptrdiff_t); + break; + default: + __ASSERT_NO_MSG(false); + } + break; + case SPECIFIER_UINT: + switch (length_mod) { + case LENGTH_NONE: + PACK_CAST_VALUE(pst, value->uint, unsigned int); + break; + case LENGTH_HH: + PACK_CAST_VALUE(pst, value->uint, unsigned char); + break; + case LENGTH_H: + PACK_CAST_VALUE(pst, value->uint, unsigned short); + break; + case LENGTH_L: + if ((!WCHAR_IS_SIGNED) && (conv->specifier == 'c')) { + /* NB: If wchar_t has lesser rank than + * WINT_TYPE the value here may be wrong as + * the pack macro won't cast through wchar_t + * as is done with pull_va_args. We don't + * care because we're not going to format the + * value anyway: we just need to correctly + * extract any following arguments. + */ + PACK_CAST_VALUE(pst, value->uint, WINT_TYPE); + } else { + PACK_CAST_VALUE(pst, value->uint, + unsigned long); + } + break; + case LENGTH_LL: + PACK_CAST_VALUE(pst, value->uint, unsigned long long); + break; + case LENGTH_J: + PACK_CAST_VALUE(pst, value->uint, uintmax_t); + break; + case LENGTH_Z: /* size_t */ + case LENGTH_T: /* ptrdiff_t */ + PACK_CAST_VALUE(pst, value->uint, size_t); + break; + default: + __ASSERT_NO_MSG(false); + } + break; + case SPECIFIER_PTR: + if (conv->specifier == 's') { + pack_str(pst); + } else { + PACK_VALUE(pst, value->ptr); + } + break; + case SPECIFIER_FP: + if (length_mod == LENGTH_UPPER_L) { + PACK_VALUE(pst, value->ldbl); + } else { + PACK_VALUE(pst, value->dbl); + } + break; + case SPECIFIER_INVALID: + /* No arguments */ + break; + default: + __ASSERT_NO_MSG(false); + break; + } +} + +/* Extract the arguments for a specific conversion from packaged data. + * + * On exit the argument_value in the state and other metadata has been updated + * based on material that was passed on the stack when the arguments were + * packaged. + * + * @param state pointer to the state, which contains the initial decoded + * conversion specifier and the unconsumed packaged data. + */ +static void pull_pkg_args(struct cbprintf_state *state) +{ + struct conversion *const conv = &state->conv; + union argument_value *const value = &state->value; + enum specifier_cat_enum specifier_cat + = (enum specifier_cat_enum)conv->specifier_cat; + enum length_mod_enum length_mod + = (enum length_mod_enum)conv->length_mod; + + if (conv->width_star) { + UNPACK_VALUE(state, state->width); + } else { + state->width = -1; + } + + if (conv->prec_star) { + UNPACK_VALUE(state, state->precision); + } else { + state->precision = -1; + } + + switch (specifier_cat) { + case SPECIFIER_SINT: + switch (length_mod) { + case LENGTH_NONE: + UNPACK_CAST_SINT_VALUE(state, int); + break; + case LENGTH_HH: + UNPACK_CAST_SINT_VALUE(state, signed char); + break; + case LENGTH_H: + UNPACK_CAST_SINT_VALUE(state, short); + break; + case LENGTH_L: + if (WCHAR_IS_SIGNED && (conv->specifier == 'c')) { + UNPACK_CAST_SINT_VALUE(state, WINT_TYPE); + } else { + UNPACK_CAST_SINT_VALUE(state, long); + } + break; + case LENGTH_LL: + UNPACK_CAST_SINT_VALUE(state, long long); + break; + case LENGTH_J: + UNPACK_CAST_SINT_VALUE(state, intmax_t); + break; + case LENGTH_Z: /* size_t */ + case LENGTH_T: /* ptrdiff_t */ + UNPACK_CAST_SINT_VALUE(state, ptrdiff_t); + break; + default: + __ASSERT_NO_MSG(false); + } + break; + case SPECIFIER_UINT: + switch (length_mod) { + case LENGTH_NONE: + UNPACK_CAST_UINT_VALUE(state, unsigned int); + break; + case LENGTH_HH: + UNPACK_CAST_UINT_VALUE(state, unsigned char); + break; + case LENGTH_H: + if ((!WCHAR_IS_SIGNED) && (conv->specifier == 'c')) { + UNPACK_CAST_UINT_VALUE(state, WINT_TYPE); + } else { + UNPACK_CAST_UINT_VALUE(state, unsigned short); + } + break; + case LENGTH_L: + UNPACK_CAST_UINT_VALUE(state, unsigned long); + break; + case LENGTH_LL: + UNPACK_CAST_UINT_VALUE(state, unsigned long long); + break; + case LENGTH_J: + UNPACK_CAST_UINT_VALUE(state, uintmax_t); + break; + case LENGTH_Z: /* size_t */ + case LENGTH_T: /* ptrdiff_t */ + UNPACK_CAST_UINT_VALUE(state, size_t); + break; + default: + __ASSERT_NO_MSG(false); + } + break; + case SPECIFIER_PTR: + if (conv->specifier == 's') { + value->const_ptr = unpack_str(state); + } else { + UNPACK_VALUE(state, value->ptr); + } + break; + case SPECIFIER_FP: + if (length_mod == LENGTH_UPPER_L) { + UNPACK_VALUE(state, value->ldbl); + } else { + UNPACK_VALUE(state, value->dbl); + } + break; + case SPECIFIER_INVALID: + /* No arguments */ + break; + default: + __ASSERT_NO_MSG(false); + break; + } +} + +int cbvprintf_package(uint8_t *packaged, + size_t *len, + const char *format, + va_list ap) +{ + if ((len == NULL) || (format == NULL)) { + return -EINVAL; + } + + struct package_state pstate = { + .pp = packaged, + .capacity = (packaged == NULL) ? 0 : *len, + }; + struct package_state *const pst = &pstate; + struct cbprintf_state *const state = &pst->state; + struct conversion *const conv = &state->conv; + union argument_value *const value = &state->value; + const char *fp = format; + int rv = 0; + + /* Make a copy of the arguments, because we can't pass ap to + * subroutines by value (caller wouldn't see the changes) nor + * by address (va_list may be an array type). + */ + va_copy(state->ap, ap); + + /* Synthesize a %s for the format itself. */ + *conv = (struct conversion){ + .specifier_cat = SPECIFIER_PTR, + .specifier = 's', + }; + *value = (union argument_value){ + .const_ptr = format, + }; + state->precision = -1; + + while (true) { + pack_conversion(pst); + + while ((*fp != 0) + && (*fp != '%')) { + ++fp; + } + + if (*fp == 0) { + break; + } + + fp = extract_conversion(conv, fp); + pull_va_args(state); + } + + *len = pstate.length; + + if (packaged != NULL) { + rv = (pstate.pp == NULL) ? -ENOSPC : (pstate.pp - packaged); + } + + return rv; +} + +int cbprintf_package(uint8_t *packaged, + size_t *len, + const char *format, + ...) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = cbvprintf_package(packaged, len, format, ap); + va_end(ap); + + return rc; +} + +static int process_conversion(cbprintf_cb out, void *ctx, const char *fp, + struct cbprintf_state *state) { char buf[CONVERTED_BUFLEN]; size_t count = 0; sint_value_type sint; + struct conversion *const conv = &state->conv; + union argument_value *const value = &state->value; /* Output character, returning EOF if output failed, otherwise * updating count. @@ -1358,56 +2099,35 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) continue; } - /* Force union into RAM with conversion state to - * mitigate LLVM code generation bug. - */ - struct { - union argument_value value; - struct conversion conv; - } state = { - .value = { - .uint = 0, - }, - }; - struct conversion *const conv = &state.conv; - union argument_value *const value = &state.value; const char *sp = fp; - int width = -1; - int precision = -1; - const char *bps = NULL; - const char *bpe = buf + sizeof(buf); - char sign = 0; fp = extract_conversion(conv, sp); + if (state->is_packaged) { + pull_pkg_args(state); + } else { + pull_va_args(state); + } - /* If dynamic width is specified, process it, - * otherwise set with if present. + /* Apply flag changes for negative width argument or + * copy out format value. */ if (conv->width_star) { - width = va_arg(ap, int); - - if (width < 0) { + if (state->width < 0) { conv->flag_dash = true; - width = -width; + state->width = -state->width; } } else if (conv->width_present) { - width = conv->width_value; + state->width = conv->width_value; } - /* If dynamic precision is specified, process it, otherwise - * set precision if present. For floating point where - * precision is not present use 6. - */ + /* Apply flag changes for negative precision argument */ if (conv->prec_star) { - int arg = va_arg(ap, int); - - if (arg < 0) { + if (state->precision < 0) { conv->prec_present = false; - } else { - precision = arg; + state->precision = -1; } } else if (conv->prec_present) { - precision = conv->prec_value; + state->precision = conv->prec_value; } /* Reuse width and precision memory in conv for value @@ -1421,117 +2141,10 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) && (conv->specifier_cat == SPECIFIER_FP) && !conv->prec_present) { if (conv->specifier_a) { - precision = FRACTION_HEX; - } else { - precision = 6; - } - } - - /* Get the value to be converted from the args. - * - * This can't be extracted to a helper function because - * passing a pointer to va_list doesn't work on x86_64. See - * https://stackoverflow.com/a/8048892. - */ - enum specifier_cat_enum specifier_cat - = (enum specifier_cat_enum)conv->specifier_cat; - enum length_mod_enum length_mod - = (enum length_mod_enum)conv->length_mod; - - /* Extract the value based on the argument category and length. - * - * Note that the length modifier doesn't affect the value of a - * pointer argument. - */ - if (specifier_cat == SPECIFIER_SINT) { - switch (length_mod) { - default: - case LENGTH_NONE: - case LENGTH_HH: - case LENGTH_H: - value->sint = va_arg(ap, int); - break; - case LENGTH_L: - if (WCHAR_IS_SIGNED - && (conv->specifier == 'c')) { - value->sint = (wchar_t)va_arg(ap, - WINT_TYPE); - } else { - value->sint = va_arg(ap, long); - } - break; - case LENGTH_LL: - value->sint = - (sint_value_type)va_arg(ap, long long); - break; - case LENGTH_J: - value->sint = - (sint_value_type)va_arg(ap, intmax_t); - break; - case LENGTH_Z: /* size_t */ - case LENGTH_T: /* ptrdiff_t */ - /* Though ssize_t is the signed equivalent of - * size_t for POSIX, there is no uptrdiff_t. - * Assume that size_t and ptrdiff_t are the - * unsigned and signed equivalents of each - * other. This can be checked in a platform - * test. - */ - value->sint = - (sint_value_type)va_arg(ap, ptrdiff_t); - break; - } - if (length_mod == LENGTH_HH) { - value->sint = (char)value->sint; - } else if (length_mod == LENGTH_H) { - value->sint = (short)value->sint; - } - } else if (specifier_cat == SPECIFIER_UINT) { - switch (length_mod) { - default: - case LENGTH_NONE: - case LENGTH_HH: - case LENGTH_H: - value->uint = va_arg(ap, unsigned int); - break; - case LENGTH_L: - if ((!WCHAR_IS_SIGNED) - && (conv->specifier == 'c')) { - value->uint = (wchar_t)va_arg(ap, - WINT_TYPE); - } else { - value->uint = va_arg(ap, unsigned long); - } - break; - case LENGTH_LL: - value->uint = - (uint_value_type)va_arg(ap, - unsigned long long); - break; - case LENGTH_J: - value->uint = - (uint_value_type)va_arg(ap, - uintmax_t); - break; - case LENGTH_Z: /* size_t */ - case LENGTH_T: /* ptrdiff_t */ - value->uint = - (uint_value_type)va_arg(ap, size_t); - break; - } - if (length_mod == LENGTH_HH) { - value->uint = (unsigned char)value->uint; - } else if (length_mod == LENGTH_H) { - value->uint = (unsigned short)value->uint; - } - } else if (specifier_cat == SPECIFIER_FP) { - if (length_mod == LENGTH_UPPER_L) { - value->ldbl = va_arg(ap, long double); + state->precision = FRACTION_HEX; } else { - value->dbl = va_arg(ap, double); + state->precision = 6; } - } else if (specifier_cat == SPECIFIER_PTR) { - value->ptr = va_arg(ap, void *); } /* We've now consumed all arguments related to this @@ -1544,6 +2157,12 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) continue; } + int width = state->width; + int precision = state->precision; + const char *bps = NULL; + const char *bpe = buf + sizeof(buf); + char sign = 0; + /* Do formatting, either into the buffer or * referencing external data. */ @@ -1804,3 +2423,39 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) #undef OUTS #undef OUTC } + +int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) +{ + /* Zero-initialized state. */ + struct cbprintf_state state = { + .width = 0, + }; + + /* Make a copy of the arguments, because we can't pass ap to + * subroutines by value (caller wouldn't see the changes) nor + * by address (va_list may be an array type). + */ + va_copy(state.ap, ap); + + return process_conversion(out, ctx, fp, &state); +} + +int cbpprintf(cbprintf_cb out, + void *ctx, + const uint8_t *packaged) +{ + if (packaged == NULL) { + return -EINVAL; + } + + /* Zero-initialized state. */ + struct cbprintf_state state = { + .width = 0, + .packaged = packaged, + .is_packaged = true, + }; + + const char *format = unpack_str(&state); + + return process_conversion(out, ctx, format, &state); +} diff --git a/tests/kernel/context/src/main.c b/tests/kernel/context/src/main.c index 5ad68f99b688e..50746a23c0dc4 100644 --- a/tests/kernel/context/src/main.c +++ b/tests/kernel/context/src/main.c @@ -35,7 +35,7 @@ #endif #define THREAD_STACKSIZE (512 + CONFIG_TEST_EXTRA_STACKSIZE) -#define THREAD_STACKSIZE2 (384 + CONFIG_TEST_EXTRA_STACKSIZE) +#define THREAD_STACKSIZE2 (448 + CONFIG_TEST_EXTRA_STACKSIZE) #define THREAD_PRIORITY 4 #define THREAD_SELF_CMD 0 diff --git a/tests/kernel/threads/tls/prj.conf b/tests/kernel/threads/tls/prj.conf index 36a39b3c777e8..2c5f8fd816af2 100644 --- a/tests/kernel/threads/tls/prj.conf +++ b/tests/kernel/threads/tls/prj.conf @@ -1,2 +1,4 @@ CONFIG_ZTEST=y CONFIG_THREAD_LOCAL_STORAGE=y +CONFIG_IDLE_STACK_SIZE=1024 +CONFIG_MAIN_STACK_SIZE=1280 diff --git a/tests/unit/cbprintf/main.c b/tests/unit/cbprintf/main.c index 3c562206d2e33..769b41f8f9dbc 100644 --- a/tests/unit/cbprintf/main.c +++ b/tests/unit/cbprintf/main.c @@ -16,6 +16,8 @@ #include #include +#define CBPRINTF_VIA_UNIT_TEST + /* Unit testing doesn't use Kconfig, so if we're not building from * twister force selection of all features. If we are use flags to * determine which features are desired. Yes, this is a mess. @@ -25,6 +27,7 @@ * This should be used with all options enabled. */ #define USE_LIBC 0 +#define USE_PACKAGED 0 #define CONFIG_CBPRINTF_COMPLETE 1 #define CONFIG_CBPRINTF_FULL_INTEGRAL 1 #define CONFIG_CBPRINTF_FP_SUPPORT 1 @@ -84,6 +87,9 @@ #if (VIA_TWISTER & 0x100) != 0 #define CONFIG_CBPRINTF_LIBC_SUBSTS 1 #endif +#if (VIA_TWISTER & 0x200) != 0 +#define USE_PACKAGED 1 +#endif #endif /* VIA_TWISTER */ @@ -96,6 +102,12 @@ #define ENABLED_USE_LIBC false #endif +#if USE_PACKAGED +#define ENABLED_USE_PACKAGED true +#else +#define ENABLED_USE_PACKAGED false +#endif + #include "../../../lib/os/cbprintf.c" #if defined(CONFIG_CBPRINTF_COMPLETE) @@ -115,6 +127,11 @@ static const unsigned int sfx_val = (unsigned int)0xe7e6e5e4e3e2e1e0; static const char sfx_str64[] = "e7e6e5e4e3e2e1e0"; static const char *sfx_str = sfx_str64; +/* Buffer adequate to hold packaged state for all tested + * configurations. + */ +static uint8_t packaged[256]; + #define WRAP_FMT(_fmt) "%x" _fmt "%x" #define PASS_ARG(...) pfx_val, __VA_ARGS__, sfx_val @@ -172,8 +189,18 @@ static int prf(const char *format, ...) #if USE_LIBC rv = vsnprintf(buf, sizeof(buf), format, ap); #else - reset_out(); +#if USE_PACKAGED + size_t len = sizeof(packaged); + + rv = cbvprintf_package(packaged, &len, format, ap); + if (len > sizeof(packaged)) { + rv = -ENOSPC; + } else { + rv = cbpprintf(out, NULL, packaged); + } +#else rv = cbvprintf(out, NULL, format, ap); +#endif if (bp == (buf + ARRAY_SIZE(buf))) { --bp; } @@ -189,7 +216,18 @@ static int rawprf(const char *format, ...) int rv; va_start(ap, format); +#if USE_PACKAGED + size_t len = sizeof(packaged); + + rv = cbvprintf_package(packaged, &len, format, ap); + if (len > sizeof(packaged)) { + rv = -ENOSPC; + } else { + rv = cbpprintf(out, NULL, packaged); + } +#else rv = cbvprintf(out, NULL, format, ap); +#endif va_end(ap); if (IS_ENABLED(CONFIG_CBPRINTF_NANO) @@ -1090,6 +1128,72 @@ static void test_libc_substs(void) } } +static void test_cbprintf_package(void) +{ + if (!IS_ENABLED(CONFIG_CBPRINTF_COMPLETE)) { + TC_PRINT("skipped on nano\n"); + return; + } + + size_t len = 0; + int rc; + char fmt[] = "/%i/"; /* not const */ + + /* Verify we can calculate length without storing */ + rc = cbprintf_package(NULL, &len, fmt, 3); + + zassert_equal(rc, 0, NULL); + zassert_true(len > sizeof(int), NULL); + + /* We can't tell whether the fmt will be stored inline (6 + * bytes) or as a pointer (1 + sizeof(void*)), but we know + * both of those will be at least 4 bytes. + */ + zassert_true(len >= (sizeof(int) + 4), NULL); + + /* Capture the base package information for future tests. */ + size_t lenp = len; + + /* Verify we get same length when storing */ + len = sizeof(packaged); + rc = cbprintf_package(packaged, &len, fmt, 3); + zassert_equal(rc, lenp, NULL); + zassert_equal(len, lenp, NULL); + + /* Verify we get an error if can't store */ + len = 1; + rc = cbprintf_package(packaged, &len, fmt, 3); + zassert_equal(rc, -ENOSPC, NULL); + zassert_equal(len, lenp, NULL); + + /* Verify we get an error if can't store */ + len = lenp - sizeof(int); + rc = cbprintf_package(packaged, &len, fmt, 3); + zassert_equal(rc, -ENOSPC, NULL); + zassert_equal(len, lenp, NULL); + + len = sizeof(fmt); + rc = cbprintf_package(packaged, &len, "%s", fmt); +} + +static void test_cbpprintf(void) +{ + if (!IS_ENABLED(CONFIG_CBPRINTF_COMPLETE)) { + TC_PRINT("skipped on nano\n"); + return; + } + + int rc; + + /* This only checks error conditions. Formatting is checked + * by diverting prf() and related helpers to use the packaged + * version. + */ + reset_out(); + rc = cbpprintf(out, NULL, NULL); + zassert_equal(rc, -EINVAL, NULL); +} + static void test_nop(void) { } @@ -1107,7 +1211,12 @@ void test_main(void) TC_PRINT(" LIBC"); } if (IS_ENABLED(CONFIG_CBPRINTF_COMPLETE)) { - TC_PRINT(" COMPLETE\n"); + TC_PRINT(" COMPLETE"); + if (ENABLED_USE_PACKAGED) { + TC_PRINT(" PACKAGED\n"); + } else { + TC_PRINT(" VA_LIST\n"); + } } else { TC_PRINT(" NANO\n"); } @@ -1153,6 +1262,8 @@ void test_main(void) ztest_unit_test(test_n), ztest_unit_test(test_p), ztest_unit_test(test_libc_substs), + ztest_unit_test(test_cbprintf_package), + ztest_unit_test(test_cbpprintf), ztest_unit_test(test_nop) ); ztest_run_test_suite(test_prf); diff --git a/tests/unit/cbprintf/testcase.yaml b/tests/unit/cbprintf/testcase.yaml index 01d9054babc1f..a5347ec619388 100644 --- a/tests/unit/cbprintf/testcase.yaml +++ b/tests/unit/cbprintf/testcase.yaml @@ -30,6 +30,18 @@ tests: utilities.prf.m32v181: # NANO + FULL + LIBC extra_args: M64_MODE=0 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x181 + utilities.prf.m32v200: # PACKAGED REDUCED + extra_args: M64_MODE=0 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x200 + + utilities.prf.m32v201: # PACKAGED FULL + extra_args: M64_MODE=0 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x201 + + utilities.prf.m32v207: # PACKAGED FULL + FP + FP_A + extra_args: M64_MODE=0 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x207 + + utilities.prf.m32v208: # PACKAGED %n + extra_args: M64_MODE=0 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x208 + utilities.prf.m64v00: # m64 extra_args: M64_MODE=1 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x00 @@ -50,3 +62,15 @@ tests: utilities.prf.m64v181: # NANO + FULL + LIBC extra_args: M64_MODE=1 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x181 + + utilities.prf.m64v200: # PACKAGED REDUCED + extra_args: M64_MODE=1 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x200 + + utilities.prf.m64v201: # PACKAGED FULL + extra_args: M64_MODE=1 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x201 + + utilities.prf.m64v207: # PACKAGED FULL + FP + FP_A + extra_args: M64_MODE=1 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x207 + + utilities.prf.m64v208: # PACKAGED %n + extra_args: M64_MODE=1 EXTRA_CPPFLAGS=-DVIA_TWISTER=0x208