556 lines
12 KiB
C
556 lines
12 KiB
C
/*
|
|
* Copyright (c) 2021 BayLibre, SAS
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <sys/cbprintf.h>
|
|
#include <sys/types.h>
|
|
#include <sys/util.h>
|
|
#include <sys/__assert.h>
|
|
|
|
|
|
/**
|
|
* @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) || defined(CONFIG_ARM64)
|
|
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));
|
|
}
|
|
|
|
/*
|
|
* va_list creation
|
|
*/
|
|
|
|
#if defined(__aarch64__)
|
|
/*
|
|
* Reference:
|
|
*
|
|
* Procedure Call Standard for the ARM 64-bit Architecture
|
|
*/
|
|
|
|
struct __va_list {
|
|
void *__stack;
|
|
void *__gr_top;
|
|
void *__vr_top;
|
|
int __gr_offs;
|
|
int __vr_offs;
|
|
};
|
|
|
|
BUILD_ASSERT(sizeof(va_list) == sizeof(struct __va_list),
|
|
"architecture specific support is wrong");
|
|
|
|
static int cbprintf_via_va_list(cbprintf_cb out, void *ctx,
|
|
const char *fmt, void *buf)
|
|
{
|
|
union {
|
|
va_list ap;
|
|
struct __va_list __ap;
|
|
} u;
|
|
|
|
/* create a valid va_list with our buffer */
|
|
u.__ap.__stack = buf;
|
|
u.__ap.__gr_top = NULL;
|
|
u.__ap.__vr_top = NULL;
|
|
u.__ap.__gr_offs = 0;
|
|
u.__ap.__vr_offs = 0;
|
|
|
|
return cbvprintf(out, ctx, fmt, u.ap);
|
|
}
|
|
|
|
#elif defined(__x86_64__)
|
|
/*
|
|
* Reference:
|
|
*
|
|
* System V Application Binary Interface
|
|
* AMD64 Architecture Processor Supplement
|
|
*/
|
|
|
|
struct __va_list {
|
|
unsigned int gp_offset;
|
|
unsigned int fp_offset;
|
|
void *overflow_arg_area;
|
|
void *reg_save_area;
|
|
};
|
|
|
|
BUILD_ASSERT(sizeof(va_list) == sizeof(struct __va_list),
|
|
"architecture specific support is wrong");
|
|
|
|
static int cbprintf_via_va_list(cbprintf_cb out, void *ctx,
|
|
const char *fmt, void *buf)
|
|
{
|
|
union {
|
|
va_list ap;
|
|
struct __va_list __ap;
|
|
} u;
|
|
|
|
/* create a valid va_list with our buffer */
|
|
u.__ap.overflow_arg_area = buf;
|
|
u.__ap.reg_save_area = NULL;
|
|
u.__ap.gp_offset = (6 * 8);
|
|
u.__ap.fp_offset = (6 * 8 + 16 * 16);
|
|
|
|
return cbvprintf(out, ctx, fmt, u.ap);
|
|
}
|
|
|
|
#elif defined(__xtensa__)
|
|
/*
|
|
* Reference:
|
|
*
|
|
* gcc source code (gcc/config/xtensa/xtensa.c)
|
|
* xtensa_build_builtin_va_list(), xtensa_va_start(),
|
|
* xtensa_gimplify_va_arg_expr()
|
|
*/
|
|
|
|
struct __va_list {
|
|
void *__va_stk;
|
|
void *__va_reg;
|
|
int __va_ndx;
|
|
};
|
|
|
|
BUILD_ASSERT(sizeof(va_list) == sizeof(struct __va_list),
|
|
"architecture specific support is wrong");
|
|
|
|
static int cbprintf_via_va_list(cbprintf_cb out, void *ctx,
|
|
const char *fmt, void *buf)
|
|
{
|
|
union {
|
|
va_list ap;
|
|
struct __va_list __ap;
|
|
} u;
|
|
|
|
/* create a valid va_list with our buffer */
|
|
u.__ap.__va_stk = (char *)buf - 32;
|
|
u.__ap.__va_reg = NULL;
|
|
u.__ap.__va_ndx = (6 + 2) * 4;
|
|
|
|
return cbvprintf(out, ctx, fmt, u.ap);
|
|
}
|
|
|
|
#else
|
|
/*
|
|
* Default implementation shared by many architectures like
|
|
* 32-bit ARM and Intel.
|
|
*
|
|
* We assume va_list is a simple pointer.
|
|
*/
|
|
|
|
BUILD_ASSERT(sizeof(va_list) == sizeof(void *),
|
|
"architecture specific support is needed");
|
|
|
|
static int cbprintf_via_va_list(cbprintf_cb out, void *ctx,
|
|
const char *fmt, void *buf)
|
|
{
|
|
union {
|
|
va_list ap;
|
|
void *ptr;
|
|
} u;
|
|
|
|
u.ptr = buf;
|
|
|
|
return cbvprintf(out, ctx, fmt, u.ap);
|
|
}
|
|
|
|
#endif
|
|
|
|
int cbvprintf_package(void *packaged, size_t len,
|
|
const char *fmt, va_list ap)
|
|
{
|
|
char *buf = packaged, *buf0 = buf;
|
|
unsigned int align, size, i, s_idx = 0;
|
|
uint8_t str_ptr_pos[16];
|
|
const char *s;
|
|
bool parsing = false;
|
|
|
|
/* Buffer must be aligned at least to size of a pointer. */
|
|
if ((uintptr_t)packaged & (sizeof(void *) - 1)) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
#if defined(__xtensa__)
|
|
/* Xtensa requires package to be 16 bytes aligned. */
|
|
if ((uintptr_t)packaged & (CBPRINTF_PACKAGE_ALIGNMENT - 1)) {
|
|
return -EFAULT;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Make room to store the arg list size and the number of
|
|
* appended strings. They both occupy 1 byte each.
|
|
*
|
|
* Given the next value to store is the format string pointer
|
|
* which is guaranteed to be at least 4 bytes, we just reserve
|
|
* a pointer size for the above to preserve alignment.
|
|
*/
|
|
buf += sizeof(char *);
|
|
|
|
/*
|
|
* When buf0 is NULL we don't store anything.
|
|
* Instead we count the needed space to store the data.
|
|
* In this case, incoming len argument indicates the anticipated
|
|
* buffer "misalignment" offset.
|
|
*/
|
|
if (!buf0) {
|
|
#if defined(__xtensa__)
|
|
if (len % CBPRINTF_PACKAGE_ALIGNMENT) {
|
|
return -EFAULT;
|
|
}
|
|
#endif
|
|
buf += len % CBPRINTF_PACKAGE_ALIGNMENT;
|
|
len = -(len % CBPRINTF_PACKAGE_ALIGNMENT);
|
|
}
|
|
|
|
/*
|
|
* Otherwise we must ensure we can store at least
|
|
* thepointer to the format string itself.
|
|
*/
|
|
if (buf0 && buf - buf0 + sizeof(char *) > len) {
|
|
return -ENOSPC;
|
|
}
|
|
|
|
/*
|
|
* Then process the format string itself.
|
|
* Here we branch directly into the code processing strings
|
|
* which is in the middle of the following while() loop. That's the
|
|
* reason for the post-decrement on fmt as it will be incremented
|
|
* prior to the next (actually first) round of that loop.
|
|
*/
|
|
s = fmt--;
|
|
align = VA_STACK_ALIGN(char *);
|
|
size = sizeof(char *);
|
|
goto process_string;
|
|
|
|
/* Scan the format string */
|
|
while (*++fmt) {
|
|
if (!parsing) {
|
|
if (*fmt == '%') {
|
|
parsing = true;
|
|
align = VA_STACK_ALIGN(int);
|
|
size = sizeof(int);
|
|
}
|
|
continue;
|
|
}
|
|
switch (*fmt) {
|
|
case '%':
|
|
parsing = false;
|
|
continue;
|
|
|
|
case '#':
|
|
case '-':
|
|
case '+':
|
|
case ' ':
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
case '.':
|
|
case 'h':
|
|
case 'l':
|
|
case 'L':
|
|
continue;
|
|
|
|
case '*':
|
|
break;
|
|
|
|
case 'j':
|
|
align = VA_STACK_ALIGN(intmax_t);
|
|
size = sizeof(intmax_t);
|
|
continue;
|
|
|
|
case 'z':
|
|
align = VA_STACK_ALIGN(size_t);
|
|
size = sizeof(size_t);
|
|
continue;
|
|
|
|
case 't':
|
|
align = VA_STACK_ALIGN(ptrdiff_t);
|
|
size = sizeof(ptrdiff_t);
|
|
continue;
|
|
|
|
case 'c':
|
|
case 'd':
|
|
case 'i':
|
|
case 'o':
|
|
case 'u':
|
|
case 'x':
|
|
case 'X':
|
|
if (fmt[-1] == 'l') {
|
|
if (fmt[-2] == 'l') {
|
|
align = VA_STACK_ALIGN(long long);
|
|
size = sizeof(long long);
|
|
} else {
|
|
align = VA_STACK_ALIGN(long);
|
|
size = sizeof(long);
|
|
}
|
|
}
|
|
parsing = false;
|
|
break;
|
|
|
|
case 's':
|
|
case 'p':
|
|
case 'n':
|
|
align = VA_STACK_ALIGN(void *);
|
|
size = sizeof(void *);
|
|
parsing = false;
|
|
break;
|
|
|
|
case 'a':
|
|
case 'A':
|
|
case 'e':
|
|
case 'E':
|
|
case 'f':
|
|
case 'F':
|
|
case 'g':
|
|
case 'G': {
|
|
/*
|
|
* Handle floats separately as they may be
|
|
* held in a different register set.
|
|
*/
|
|
union { double d; long double ld; } v;
|
|
|
|
if (fmt[-1] == 'L') {
|
|
v.ld = va_arg(ap, long double);
|
|
align = VA_STACK_ALIGN(long double);
|
|
size = sizeof(long double);
|
|
} else {
|
|
v.d = va_arg(ap, double);
|
|
align = VA_STACK_ALIGN(double);
|
|
size = sizeof(double);
|
|
}
|
|
/* align destination buffer location */
|
|
buf = (void *) ROUND_UP(buf, align);
|
|
if (buf0) {
|
|
/* make sure it fits */
|
|
if (buf - buf0 + size > len) {
|
|
return -ENOSPC;
|
|
}
|
|
if (Z_CBPRINTF_VA_STACK_LL_DBL_MEMCPY) {
|
|
memcpy(buf, &v, size);
|
|
} else if (fmt[-1] == 'L') {
|
|
*(long double *)buf = v.ld;
|
|
} else {
|
|
*(double *)buf = v.d;
|
|
}
|
|
}
|
|
buf += size;
|
|
parsing = false;
|
|
continue;
|
|
}
|
|
|
|
default:
|
|
parsing = false;
|
|
continue;
|
|
}
|
|
|
|
/* align destination buffer location */
|
|
buf = (void *) ROUND_UP(buf, align);
|
|
|
|
/* make sure the data fits */
|
|
if (buf0 && buf - buf0 + size > len) {
|
|
return -ENOSPC;
|
|
}
|
|
|
|
/* copy va_list data over to our buffer */
|
|
if (*fmt == 's') {
|
|
s = va_arg(ap, char *);
|
|
process_string:
|
|
if (buf0) {
|
|
*(const char **)buf = s;
|
|
}
|
|
if (ptr_in_rodata(s)) {
|
|
/* do nothing special */
|
|
} else if (buf0) {
|
|
/*
|
|
* Remember string pointer location.
|
|
* We will append it later.
|
|
*/
|
|
if (s_idx >= ARRAY_SIZE(str_ptr_pos)) {
|
|
__ASSERT(false, "str_ptr_pos[] too small");
|
|
return -EINVAL;
|
|
}
|
|
/* Use same multiple as the arg list size. */
|
|
str_ptr_pos[s_idx++] = (buf - buf0) / sizeof(int);
|
|
} else {
|
|
/*
|
|
* Add the string length, the final '\0'
|
|
* and size of the pointer position prefix.
|
|
*/
|
|
len += strlen(s) + 1 + 1;
|
|
}
|
|
buf += sizeof(char *);
|
|
} else if (size == sizeof(int)) {
|
|
int v = va_arg(ap, int);
|
|
|
|
if (buf0) {
|
|
*(int *)buf = v;
|
|
}
|
|
buf += sizeof(int);
|
|
} else if (size == sizeof(long)) {
|
|
long v = va_arg(ap, long);
|
|
|
|
if (buf0) {
|
|
*(long *)buf = v;
|
|
}
|
|
buf += sizeof(long);
|
|
} else if (size == sizeof(long long)) {
|
|
long long v = va_arg(ap, long long);
|
|
|
|
if (buf0) {
|
|
if (Z_CBPRINTF_VA_STACK_LL_DBL_MEMCPY) {
|
|
memcpy(buf, &v, sizeof(long long));
|
|
} else {
|
|
*(long long *)buf = v;
|
|
}
|
|
}
|
|
buf += sizeof(long long);
|
|
} else {
|
|
__ASSERT(false, "unexpected size %u", size);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We remember the size of the argument list as a multiple of
|
|
* sizeof(int) and limit it to a 8-bit field. That means 1020 bytes
|
|
* worth of va_list, or about 127 arguments on a 64-bit system
|
|
* (twice that on 32-bit systems). That ought to be good enough.
|
|
*/
|
|
if ((buf - buf0) / sizeof(int) > 255) {
|
|
__ASSERT(false, "too many format args");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* If all we wanted was to count required buffer size
|
|
* then we have it now.
|
|
*/
|
|
if (!buf0) {
|
|
return len + buf - buf0;
|
|
}
|
|
|
|
/* Clear our buffer header. We made room for it initially. */
|
|
*(char **)buf0 = 0;
|
|
|
|
/* Record end of argument list and number of appended strings. */
|
|
buf0[0] = (buf - buf0) / sizeof(int);
|
|
buf0[1] = s_idx;
|
|
|
|
/* Store strings prefixed by their pointer location. */
|
|
for (i = 0; i < s_idx; i++) {
|
|
/* retrieve the string pointer */
|
|
s = *(char **)(buf0 + str_ptr_pos[i] * sizeof(int));
|
|
/* clear the in-buffer pointer (less entropy if compressed) */
|
|
*(char **)(buf0 + str_ptr_pos[i] * sizeof(int)) = NULL;
|
|
/* find the string length including terminating '\0' */
|
|
size = strlen(s) + 1;
|
|
/* make sure it fits */
|
|
if (buf - buf0 + 1 + size > len) {
|
|
return -ENOSPC;
|
|
}
|
|
/* store the pointer position prefix */
|
|
*buf++ = str_ptr_pos[i];
|
|
/* copy the string with its terminating '\0' */
|
|
memcpy(buf, s, size);
|
|
buf += size;
|
|
}
|
|
|
|
/*
|
|
* TODO: remove pointers for appended strings since they're useless.
|
|
* TODO: explore leveraging same mechanism to remove alignment padding
|
|
*/
|
|
|
|
return buf - buf0;
|
|
}
|
|
|
|
int cbprintf_package(void *packaged, size_t len, const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
int ret;
|
|
|
|
va_start(ap, format);
|
|
ret = cbvprintf_package(packaged, len, format, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
int cbpprintf(cbprintf_cb out, void *ctx, void *packaged)
|
|
{
|
|
char *buf = packaged, *fmt, *s, **ps;
|
|
unsigned int i, args_size, s_nbr, s_idx;
|
|
|
|
if (!buf) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Retrieve the size of the arg list and number of strings. */
|
|
args_size = ((uint8_t *)buf)[0] * sizeof(int);
|
|
s_nbr = ((uint8_t *)buf)[1];
|
|
|
|
/* Locate the string table */
|
|
s = buf + args_size;
|
|
|
|
/*
|
|
* Patch in string pointers.
|
|
*/
|
|
for (i = 0; i < s_nbr; i++) {
|
|
/* Locate pointer location for this string */
|
|
s_idx = *(uint8_t *)s++;
|
|
ps = (char **)(buf + s_idx * sizeof(int));
|
|
/* update the pointer with current string location */
|
|
*ps = s;
|
|
/* move to next string */
|
|
s += strlen(s) + 1;
|
|
}
|
|
|
|
/* Retrieve format string */
|
|
fmt = ((char **)buf)[1];
|
|
|
|
/* skip past format string pointer */
|
|
buf += sizeof(char *) * 2;
|
|
|
|
/* Turn this into a va_list and print it */
|
|
return cbprintf_via_va_list(out, ctx, fmt, buf);
|
|
}
|