From c770a4eb7104feea3eb8ebf754bcf861023f6111 Mon Sep 17 00:00:00 2001 From: Peter Bigot Date: Fri, 11 Dec 2020 08:16:50 -0600 Subject: [PATCH 1/5] lib: cbprintf: refactor for upcoming enhancement The extraction of values from varargs was inlined in cbvprintf() because va_list arguments cannot be passed into subroutines by reference, and there was only one place that needed this functionality. An upcoming enhancement requires extracting the arguments but processing them later, so outline the extraction code. Signed-off-by: Peter Bigot --- lib/os/cbprintf_complete.c | 378 ++++++++++++++++++++----------------- 1 file changed, 206 insertions(+), 172 deletions(-) diff --git a/lib/os/cbprintf_complete.c b/lib/os/cbprintf_complete.c index f2d7adb6b26f4..29e2d74e6c6d4 100644 --- a/lib/os/cbprintf_complete.c +++ b/lib/os/cbprintf_complete.c @@ -307,6 +307,38 @@ struct conversion { }; }; +/* State used for processing format conversions with values taken from + * varargs. + */ +struct cbprintf_state { + /* A copy of the captured arguments on the call stack. + * + * See https://stackoverflow.com/a/8048892 for why we can't + * just pass a pointer to the passed ap argument. + */ + va_list ap; + + /* 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; +}; + /** 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 +1351,178 @@ static int outs(cbprintf_cb out, return (int)count; } +/* 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; + + state->width = -1; + state->precision = -1; + + /* If dynamic width is specified, process it, otherwise set + * with if present. + */ + if (conv->width_star) { + state->width = va_arg(state->ap, int); + + if (state->width < 0) { + conv->flag_dash = true; + state->width = -state->width; + } + } else if (conv->width_present) { + 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. + */ + if (conv->prec_star) { + int arg = va_arg(state->ap, int); + + if (arg < 0) { + conv->prec_present = false; + } else { + state->precision = arg; + } + } else if (conv->prec_present) { + state->precision = conv->prec_value; + } + + /* Reuse width and precision memory in conv for value + * padding counts. + */ + conv->pad0_value = 0; + conv->pad0_pre_exp = 0; + + /* FP conversion requires knowing the precision. */ + if (IS_ENABLED(CONFIG_CBPRINTF_FP_SUPPORT) + && (conv->specifier_cat == SPECIFIER_FP) + && !conv->prec_present) { + if (conv->specifier_a) { + state->precision = FRACTION_HEX; + } else { + state->precision = 6; + } + } + + 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 *); + } +} + int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) { char buf[CONVERTED_BUFLEN]; size_t count = 0; sint_value_type sint; + struct cbprintf_state state; + struct conversion *const conv = &state.conv; + union argument_value *const value = &state.value; + + /* 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); /* Output character, returning EOF if output failed, otherwise * updating count. @@ -1358,181 +1557,10 @@ 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 dynamic width is specified, process it, - * otherwise set with if present. - */ - if (conv->width_star) { - width = va_arg(ap, int); - - if (width < 0) { - conv->flag_dash = true; - width = -width; - } - } else if (conv->width_present) { - 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. - */ - if (conv->prec_star) { - int arg = va_arg(ap, int); - - if (arg < 0) { - conv->prec_present = false; - } else { - precision = arg; - } - } else if (conv->prec_present) { - precision = conv->prec_value; - } - - /* Reuse width and precision memory in conv for value - * padding counts. - */ - conv->pad0_value = 0; - conv->pad0_pre_exp = 0; - - /* FP conversion requires knowing the precision. */ - if (IS_ENABLED(CONFIG_CBPRINTF_FP_SUPPORT) - && (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); - } else { - value->dbl = va_arg(ap, double); - } - } else if (specifier_cat == SPECIFIER_PTR) { - value->ptr = va_arg(ap, void *); - } + pull_va_args(&state); /* We've now consumed all arguments related to this * specification. If the conversion is invalid, or is @@ -1544,6 +1572,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. */ From 23fa93cd77f1b086b3398f5bba882dab90c40c2a Mon Sep 17 00:00:00 2001 From: Peter Bigot Date: Sat, 12 Dec 2020 08:43:42 -0600 Subject: [PATCH 2/5] lib: cbprintf: refactor handling of dynamic format flags Format width and precision can be extracted from arguments. A negative value is interpreted by changing flag state and using the non-negative value. Refactor pull_va_args() so the raw value from the stack is preserved in the state so it can be packed, deferring the interpretation to point-of-use. Signed-off-by: Peter Bigot --- lib/os/cbprintf_complete.c | 87 +++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/lib/os/cbprintf_complete.c b/lib/os/cbprintf_complete.c index 29e2d74e6c6d4..a197ab32fd83c 100644 --- a/lib/os/cbprintf_complete.c +++ b/lib/os/cbprintf_complete.c @@ -1359,54 +1359,16 @@ static void pull_va_args(struct cbprintf_state *state) struct conversion *const conv = &state->conv; union argument_value *const value = &state->value; - state->width = -1; - state->precision = -1; - - /* If dynamic width is specified, process it, otherwise set - * with if present. - */ if (conv->width_star) { state->width = va_arg(state->ap, int); - - if (state->width < 0) { - conv->flag_dash = true; - state->width = -state->width; - } - } else if (conv->width_present) { - state->width = conv->width_value; + } else { + state->width = -1; } - /* If dynamic precision is specified, process it, otherwise - * set precision if present. For floating point where - * precision is not present use 6. - */ if (conv->prec_star) { - int arg = va_arg(state->ap, int); - - if (arg < 0) { - conv->prec_present = false; - } else { - state->precision = arg; - } - } else if (conv->prec_present) { - state->precision = conv->prec_value; - } - - /* Reuse width and precision memory in conv for value - * padding counts. - */ - conv->pad0_value = 0; - conv->pad0_pre_exp = 0; - - /* FP conversion requires knowing the precision. */ - if (IS_ENABLED(CONFIG_CBPRINTF_FP_SUPPORT) - && (conv->specifier_cat == SPECIFIER_FP) - && !conv->prec_present) { - if (conv->specifier_a) { - state->precision = FRACTION_HEX; - } else { - state->precision = 6; - } + state->precision = va_arg(state->ap, int); + } else { + state->precision = -1; } enum specifier_cat_enum specifier_cat @@ -1562,6 +1524,45 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) fp = extract_conversion(conv, sp); pull_va_args(&state); + /* Apply flag changes for negative width argument or + * copy out format value. + */ + if (conv->width_star) { + if (state.width < 0) { + conv->flag_dash = true; + state.width = -state.width; + } + } else if (conv->width_present) { + state.width = conv->width_value; + } + + /* Apply flag changes for negative precision argument */ + if (conv->prec_star) { + if (state.precision < 0) { + conv->prec_present = false; + state.precision = -1; + } + } else if (conv->prec_present) { + state.precision = conv->prec_value; + } + + /* Reuse width and precision memory in conv for value + * padding counts. + */ + conv->pad0_value = 0; + conv->pad0_pre_exp = 0; + + /* FP conversion requires knowing the precision. */ + if (IS_ENABLED(CONFIG_CBPRINTF_FP_SUPPORT) + && (conv->specifier_cat == SPECIFIER_FP) + && !conv->prec_present) { + if (conv->specifier_a) { + state.precision = FRACTION_HEX; + } else { + state.precision = 6; + } + } + /* We've now consumed all arguments related to this * specification. If the conversion is invalid, or is * something we don't support, then output the original From 42eeb7adae09221cfb8383829f9e4b78c1b7f4cd Mon Sep 17 00:00:00 2001 From: Peter Bigot Date: Sat, 12 Dec 2020 09:40:33 -0600 Subject: [PATCH 3/5] lib: cbprintf: factor out core conversion implementation An upcoming enhancement supports conversion with information provided from sources other than a va_list. Add a level of indirection so the core conversion operation isn't tied to the stdarg API. Signed-off-by: Peter Bigot --- lib/os/cbprintf_complete.c | 52 +++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/lib/os/cbprintf_complete.c b/lib/os/cbprintf_complete.c index a197ab32fd83c..246e9fe164693 100644 --- a/lib/os/cbprintf_complete.c +++ b/lib/os/cbprintf_complete.c @@ -1471,20 +1471,14 @@ static void pull_va_args(struct cbprintf_state *state) } } -int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) +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 cbprintf_state state; - struct conversion *const conv = &state.conv; - union argument_value *const value = &state.value; - - /* 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); + struct conversion *const conv = &state->conv; + union argument_value *const value = &state->value; /* Output character, returning EOF if output failed, otherwise * updating count. @@ -1522,28 +1516,28 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) const char *sp = fp; fp = extract_conversion(conv, sp); - pull_va_args(&state); + pull_va_args(state); /* Apply flag changes for negative width argument or * copy out format value. */ if (conv->width_star) { - if (state.width < 0) { + if (state->width < 0) { conv->flag_dash = true; - state.width = -state.width; + state->width = -state->width; } } else if (conv->width_present) { - state.width = conv->width_value; + state->width = conv->width_value; } /* Apply flag changes for negative precision argument */ if (conv->prec_star) { - if (state.precision < 0) { + if (state->precision < 0) { conv->prec_present = false; - state.precision = -1; + state->precision = -1; } } else if (conv->prec_present) { - state.precision = conv->prec_value; + state->precision = conv->prec_value; } /* Reuse width and precision memory in conv for value @@ -1557,9 +1551,9 @@ 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) { - state.precision = FRACTION_HEX; + state->precision = FRACTION_HEX; } else { - state.precision = 6; + state->precision = 6; } } @@ -1573,8 +1567,8 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap) continue; } - int width = state.width; - int precision = state.precision; + int width = state->width; + int precision = state->precision; const char *bps = NULL; const char *bpe = buf + sizeof(buf); char sign = 0; @@ -1839,3 +1833,19 @@ 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); +} From 7e0e4467688c31d7e7815f19713661494f062f70 Mon Sep 17 00:00:00 2001 From: Peter Bigot Date: Sun, 13 Dec 2020 17:34:33 -0600 Subject: [PATCH 4/5] tests: increase stack requirements on some tests Several tests require additional stack space to accommodate a deeper call stack due to inability to inline functions in an upcoming enhancement. Signed-off-by: Peter Bigot --- tests/kernel/context/src/main.c | 2 +- tests/kernel/threads/tls/prj.conf | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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 From 7534d1fcbb0e94d729a56f1820c150e6ecd34ede Mon Sep 17 00:00:00 2001 From: Peter Bigot Date: Sat, 12 Dec 2020 13:44:36 -0600 Subject: [PATCH 5/5] lib: cbprintf: add support for deferred formatting In applications like logging the call site where arguments to formatting are available may not be suitable for performing the formatting, e.g. when the output operation can sleep. Add API that supports capturing data that may be transient into a buffer that can be saved, and API that then produces the output later using the packaged arguments. Signed-off-by: Peter Bigot --- include/sys/cbprintf.h | 103 +++++ lib/os/cbprintf_complete.c | 624 +++++++++++++++++++++++++++++- tests/unit/cbprintf/main.c | 115 +++++- tests/unit/cbprintf/testcase.yaml | 24 ++ 4 files changed, 857 insertions(+), 9 deletions(-) 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 246e9fe164693..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 @@ -311,12 +313,20 @@ struct conversion { * varargs. */ struct cbprintf_state { - /* A copy of the captured arguments on the call stack. - * - * See https://stackoverflow.com/a/8048892 for why we can't - * just pass a pointer to the passed ap argument. - */ - va_list ap; + /* 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() @@ -337,6 +347,11 @@ struct cbprintf_state { * 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. @@ -1471,6 +1486,577 @@ static void pull_va_args(struct cbprintf_state *state) } } +/** + * @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) { @@ -1516,7 +2102,11 @@ static int process_conversion(cbprintf_cb out, void *ctx, const char *fp, const char *sp = fp; fp = extract_conversion(conv, sp); - pull_va_args(state); + if (state->is_packaged) { + pull_pkg_args(state); + } else { + pull_va_args(state); + } /* Apply flag changes for negative width argument or * copy out format value. @@ -1849,3 +2439,23 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list 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/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