zephyr/include/logging/log_msg2.h
Krzysztof Chruscinski a662e69cad logging: Prevent multiple arguments evaluation
Logging v2 is utilizing complex preprocessing operations to
prepare message at compile time. Multiple operations are peformed
on log message arguments. However, it is expected that argument
will be evaluated only once (e.g. it can be a call to a function
with side effects). Adding additional layer which creates copies
of user arguments on stack and passes them to further processing.

Updated test for log_msg2 which is using internal macro which
got renamed.

Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
2022-02-22 14:44:11 +01:00

614 lines
18 KiB
C

/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_LOGGING_LOG_MSG2_H_
#define ZEPHYR_INCLUDE_LOGGING_LOG_MSG2_H_
#include <logging/log_instance.h>
#include <sys/mpsc_packet.h>
#include <sys/cbprintf.h>
#include <sys/atomic.h>
#include <sys/util.h>
#include <string.h>
#ifdef __GNUC__
#ifndef alloca
#define alloca __builtin_alloca
#endif
#else
#include <alloca.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define LOG_MSG2_DEBUG 0
#define LOG_MSG2_DBG(...) IF_ENABLED(LOG_MSG2_DEBUG, (printk(__VA_ARGS__)))
#ifdef CONFIG_LOG_TIMESTAMP_64BIT
typedef uint64_t log_timestamp_t;
#else
typedef uint32_t log_timestamp_t;
#endif
/**
* @brief Log message API
* @defgroup log_msg2 Log message v2 API
* @ingroup logger
* @{
*/
#define Z_LOG_MSG2_LOG 0
#define LOG_MSG2_GENERIC_HDR \
MPSC_PBUF_HDR;\
uint32_t type:1
struct log_msg2_desc {
LOG_MSG2_GENERIC_HDR;
uint32_t domain:3;
uint32_t level:3;
uint32_t package_len:10;
uint32_t data_len:12;
uint32_t reserved:1;
};
union log_msg2_source {
const struct log_source_const_data *fixed;
struct log_source_dynamic_data *dynamic;
void *raw;
};
struct log_msg2_hdr {
struct log_msg2_desc desc;
/* Attempting to keep best alignment. When address is 64 bit and timestamp 32
* swap the order to have 16 byte header instead of 24 byte.
*/
#if (INTPTR_MAX > INT32_MAX) && !CONFIG_LOG_TIMESTAMP_64BIT
log_timestamp_t timestamp;
const void *source;
#else
const void *source;
log_timestamp_t timestamp;
#endif
#if defined(__xtensa__) && !defined(CONFIG_LOG_TIMESTAMP_64BIT)
/* xtensa requires that cbprintf package that follows the header is
* aligned to 16 bytes. Adding padding when necessary.
*/
uint32_t padding;
#endif
};
struct log_msg2 {
struct log_msg2_hdr hdr;
uint8_t data[];
};
struct log_msg2_generic_hdr {
LOG_MSG2_GENERIC_HDR;
};
union log_msg2_generic {
union mpsc_pbuf_generic buf;
struct log_msg2_generic_hdr generic;
struct log_msg2 log;
};
/** @brief Method used for creating a log message.
*
* It is used for testing purposes to validate that expected mode was used.
*/
enum z_log_msg2_mode {
/* Runtime mode is least efficient but supports all cases thus it is
* treated as a fallback method when others cannot be used.
*/
Z_LOG_MSG2_MODE_RUNTIME,
/* Mode creates statically a string package on stack and calls a
* function for creating a message. It takes code size than
* Z_LOG_MSG2_MODE_ZERO_COPY but is a bit slower.
*/
Z_LOG_MSG2_MODE_FROM_STACK,
/* Mode calculates size of the message and allocates it and writes
* directly to the message space. It is the fastest method but requires
* more code size.
*/
Z_LOG_MSG2_MODE_ZERO_COPY,
};
#define Z_LOG_MSG_DESC_INITIALIZER(_domain_id, _level, _plen, _dlen) \
{ \
.valid = 0, \
.busy = 0, \
.type = Z_LOG_MSG2_LOG, \
.domain = _domain_id, \
.level = _level, \
.package_len = _plen, \
.data_len = _dlen, \
.reserved = 0, \
}
/* Messages are aligned to alignment required by cbprintf package. */
#define Z_LOG_MSG2_ALIGNMENT CBPRINTF_PACKAGE_ALIGNMENT
#ifdef CONFIG_LOG2_USE_VLA
#define Z_LOG_MSG2_ON_STACK_ALLOC(ptr, len) \
long long _ll_buf[ceiling_fraction(len, sizeof(long long))]; \
long double _ld_buf[ceiling_fraction(len, sizeof(long double))]; \
ptr = (sizeof(long double) == Z_LOG_MSG2_ALIGNMENT) ? \
(struct log_msg2 *)_ld_buf : (struct log_msg2 *)_ll_buf; \
if (IS_ENABLED(CONFIG_LOG_TEST_CLEAR_MESSAGE_SPACE)) { \
/* During test fill with 0's to simplify message comparison */ \
memset(ptr, 0, len); \
}
#else /* Z_LOG_MSG2_USE_VLA */
/* When VLA cannot be used we need to trick compiler a bit and create multiple
* fixed size arrays and take the smallest one that will fit the message.
* Compiler will remove unused arrays and stack usage will be kept similar
* to vla case, rounded to the size of the used buffer.
*/
#define Z_LOG_MSG2_ON_STACK_ALLOC(ptr, len) \
long long _ll_buf32[32 / sizeof(long long)]; \
long long _ll_buf48[48 / sizeof(long long)]; \
long long _ll_buf64[64 / sizeof(long long)]; \
long long _ll_buf128[128 / sizeof(long long)]; \
long long _ll_buf256[256 / sizeof(long long)]; \
long double _ld_buf32[32 / sizeof(long double)]; \
long double _ld_buf48[48 / sizeof(long double)]; \
long double _ld_buf64[64 / sizeof(long double)]; \
long double _ld_buf128[128 / sizeof(long double)]; \
long double _ld_buf256[256 / sizeof(long double)]; \
if (sizeof(long double) == Z_LOG_MSG2_ALIGNMENT) { \
ptr = (len > 128) ? (struct log_msg2 *)_ld_buf256 : \
((len > 64) ? (struct log_msg2 *)_ld_buf128 : \
((len > 48) ? (struct log_msg2 *)_ld_buf64 : \
((len > 32) ? (struct log_msg2 *)_ld_buf48 : \
(struct log_msg2 *)_ld_buf32)));\
} else { \
ptr = (len > 128) ? (struct log_msg2 *)_ll_buf256 : \
((len > 64) ? (struct log_msg2 *)_ll_buf128 : \
((len > 48) ? (struct log_msg2 *)_ll_buf64 : \
((len > 32) ? (struct log_msg2 *)_ll_buf48 : \
(struct log_msg2 *)_ll_buf32)));\
} \
if (IS_ENABLED(CONFIG_LOG_TEST_CLEAR_MESSAGE_SPACE)) { \
/* During test fill with 0's to simplify message comparison */ \
memset(ptr, 0, len); \
}
#endif /* Z_LOG_MSG2_USE_VLA */
#define Z_LOG_MSG2_ALIGN_OFFSET \
sizeof(struct log_msg2_hdr)
#define Z_LOG_MSG2_LEN(pkg_len, data_len) \
(sizeof(struct log_msg2_hdr) + pkg_len + (data_len))
#define Z_LOG_MSG2_ALIGNED_WLEN(pkg_len, data_len) \
ceiling_fraction(ROUND_UP(Z_LOG_MSG2_LEN(pkg_len, data_len), \
Z_LOG_MSG2_ALIGNMENT), \
sizeof(uint32_t))
#define Z_LOG_MSG2_STACK_CREATE(_domain_id, _source, _level, _data, _dlen, ...)\
do { \
int _plen; \
if (GET_ARG_N(1, __VA_ARGS__) == NULL) { \
_plen = 0; \
} else { \
CBPRINTF_STATIC_PACKAGE(NULL, 0, _plen, Z_LOG_MSG2_ALIGN_OFFSET, \
0, __VA_ARGS__); \
} \
struct log_msg2 *_msg; \
Z_LOG_MSG2_ON_STACK_ALLOC(_msg, Z_LOG_MSG2_LEN(_plen, 0)); \
if (_plen) { \
CBPRINTF_STATIC_PACKAGE(_msg->data, _plen, \
_plen, Z_LOG_MSG2_ALIGN_OFFSET, \
0, __VA_ARGS__);\
} \
struct log_msg2_desc _desc = \
Z_LOG_MSG_DESC_INITIALIZER(_domain_id, _level, \
(uint32_t)_plen, _dlen); \
LOG_MSG2_DBG("creating message on stack: package len: %d, data len: %d\n", \
_plen, (int)(_dlen)); \
z_log_msg2_static_create((void *)_source, _desc, _msg->data, _data); \
} while (0)
#ifdef CONFIG_LOG_SPEED
#define Z_LOG_MSG2_SIMPLE_CREATE(_domain_id, _source, _level, ...) do { \
int _plen; \
CBPRINTF_STATIC_PACKAGE(NULL, 0, _plen, Z_LOG_MSG2_ALIGN_OFFSET, \
0, __VA_ARGS__); \
size_t _msg_wlen = Z_LOG_MSG2_ALIGNED_WLEN(_plen, 0); \
struct log_msg2 *_msg = z_log_msg2_alloc(_msg_wlen); \
struct log_msg2_desc _desc = \
Z_LOG_MSG_DESC_INITIALIZER(_domain_id, _level, (uint32_t)_plen, 0); \
LOG_MSG2_DBG("creating message zero copy: package len: %d, msg: %p\n", \
_plen, _msg); \
if (_msg) { \
CBPRINTF_STATIC_PACKAGE(_msg->data, _plen, _plen, \
Z_LOG_MSG2_ALIGN_OFFSET, \
0, __VA_ARGS__); \
} \
z_log_msg2_finalize(_msg, (void *)_source, _desc, NULL); \
} while (0)
#else
/* Alternative empty macro created to speed up compilation when LOG_SPEED is
* disabled (default).
*/
#define Z_LOG_MSG2_SIMPLE_CREATE(...)
#endif
/* Macro handles case when local variable with log message string is created.It
* replaces origing string literal with that variable.
*/
#define Z_LOG_FMT_ARGS_2(_name, ...) \
COND_CODE_1(CONFIG_LOG2_FMT_SECTION, \
(COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), \
(_name), (_name, GET_ARGS_LESS_N(1, __VA_ARGS__)))), \
(__VA_ARGS__))
/** @brief Wrapper for log message string with arguments.
*
* Wrapper is replacing first argument with a variable from a dedicated memory
* section if option is enabled. Macro handles the case when there is no
* log message provided.
*
* @param _name Name of the variable with log message string. It is optionally used.
* @param ... Optional log message with arguments (may be empty).
*/
#define Z_LOG_FMT_ARGS(_name, ...) \
COND_CODE_0(NUM_VA_ARGS_LESS_1(_, ##__VA_ARGS__), \
(NULL), \
(Z_LOG_FMT_ARGS_2(_name, ##__VA_ARGS__)))
/* Macro handles case when there is no string provided, in that case variable
* is not created.
*/
#define Z_LOG_MSG2_STR_VAR_IN_SECTION(_name, ...) \
COND_CODE_0(NUM_VA_ARGS_LESS_1(_, ##__VA_ARGS__), \
(/* No args provided, no variable */), \
(static const char _name[] \
__attribute__((__section__(".log_strings"))) = \
GET_ARG_N(1, __VA_ARGS__);))
/** @brief Create variable in the dedicated memory section (if enabled).
*
* Variable is initialized with a format string from the log message.
*
* @param _name Variable name.
* @param ... Optional log message with arguments (may be empty).
*/
#define Z_LOG_MSG2_STR_VAR(_name, ...) \
IF_ENABLED(CONFIG_LOG2_FMT_SECTION, \
(Z_LOG_MSG2_STR_VAR_IN_SECTION(_name, ##__VA_ARGS__)))
/** @brief Create log message and write it into the logger buffer.
*
* Macro handles creation of log message which includes storing log message
* description, timestamp, arguments, copying string arguments into message and
* copying user data into the message space. The are 3 modes of message
* creation:
* - at compile time message size is determined, message is allocated and
* content is written directly to the message. It is the fastest but cannot
* be used in user mode. Message size cannot be determined at compile time if
* it contains data or string arguments which are string pointers.
* - at compile time message size is determined, string package is created on
* stack, message is created in function call. String package can only be
* created on stack if it does not contain unexpected pointers to strings.
* - string package is created at runtime. This mode has no limitations but
* it is significantly slower.
*
* @param _try_0cpy If positive then, if possible, message content is written
* directly to message. If 0 then, if possible, string package is created on
* the stack and message is created in the function call.
*
* @param _mode Used for testing. It is set according to message creation mode
* used.
*
* @param _cstr_cnt Number of constant strings present in the string. It is
* used to help detect messages which must be runtime processed, compared to
* message which can be prebuilt at compile time.
*
* @param _domain_id Domain ID.
*
* @param _source Pointer to the constant descriptor of the log message source.
*
* @param _level Log message level.
*
* @param _data Pointer to the data. Can be null.
*
* @param _dlen Number of data bytes. 0 if data is not provided.
*
* @param ... Optional string with arguments (fmt, ...). It may be empty.
*/
#ifdef CONFIG_LOG2_ALWAYS_RUNTIME
#define Z_LOG_MSG2_CREATE2(_try_0cpy, _mode, _cstr_cnt, _domain_id, _source,\
_level, _data, _dlen, ...) \
do {\
Z_LOG_MSG2_STR_VAR(_fmt, ##__VA_ARGS__) \
z_log_msg2_runtime_create(_domain_id, (void *)_source, \
_level, (uint8_t *)_data, _dlen,\
Z_LOG_FMT_ARGS(_fmt, ##__VA_ARGS__));\
_mode = Z_LOG_MSG2_MODE_RUNTIME; \
} while (0)
#else /* CONFIG_LOG2_ALWAYS_RUNTIME */
#define Z_LOG_MSG2_CREATE3(_try_0cpy, _mode, _cstr_cnt, _domain_id, _source,\
_level, _data, _dlen, ...) \
do { \
Z_LOG_MSG2_STR_VAR(_fmt, ##__VA_ARGS__); \
if (CBPRINTF_MUST_RUNTIME_PACKAGE(_cstr_cnt, 0, __VA_ARGS__)) { \
LOG_MSG2_DBG("create runtime message\n");\
z_log_msg2_runtime_create(_domain_id, (void *)_source, \
_level, (uint8_t *)_data, _dlen,\
Z_LOG_FMT_ARGS(_fmt, ##__VA_ARGS__));\
_mode = Z_LOG_MSG2_MODE_RUNTIME; \
} else if (IS_ENABLED(CONFIG_LOG_SPEED) && _try_0cpy && ((_dlen) == 0)) {\
LOG_MSG2_DBG("create zero-copy message\n");\
Z_LOG_MSG2_SIMPLE_CREATE(_domain_id, _source, \
_level, Z_LOG_FMT_ARGS(_fmt, ##__VA_ARGS__)); \
_mode = Z_LOG_MSG2_MODE_ZERO_COPY; \
} else { \
LOG_MSG2_DBG("create on stack message\n");\
Z_LOG_MSG2_STACK_CREATE(_domain_id, _source, _level, _data, \
_dlen, Z_LOG_FMT_ARGS(_fmt, ##__VA_ARGS__)); \
_mode = Z_LOG_MSG2_MODE_FROM_STACK; \
} \
(void)_mode; \
} while (0)
#if defined(__cplusplus)
#define Z_AUTO_TYPE auto
#else
#define Z_AUTO_TYPE __auto_type
#endif
/* Macro for getting name of a local variable with the exception of the first argument
* which is a formatted string in log message.
*/
#define Z_LOG_LOCAL_ARG_NAME(idx, arg) COND_CODE_0(idx, (arg), (_v##idx))
/* Create local variable from input variable (expect for the first (fmt) argument). */
#define Z_LOG_LOCAL_ARG_CREATE(idx, arg) \
COND_CODE_0(idx, (), (Z_AUTO_TYPE Z_LOG_LOCAL_ARG_NAME(idx, arg) = (arg) + 0))
/* First level of processing creates stack variables to be passed for further processing.
* This is done to prevent multiple evaluations of input arguments (in case argument
* evaluation has side effects, e.g. it is a non-pure function call).
*/
#define Z_LOG_MSG2_CREATE2(_try_0cpy, _mode, _cstr_cnt, _domain_id, _source, \
_level, _data, _dlen, ...) \
do { \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wpointer-arith\"") \
FOR_EACH_IDX(Z_LOG_LOCAL_ARG_CREATE, (;), __VA_ARGS__); \
_Pragma("GCC diagnostic pop") \
Z_LOG_MSG2_CREATE3(_try_0cpy, _mode, _cstr_cnt, _domain_id, _source,\
_level, _data, _dlen, \
FOR_EACH_IDX(Z_LOG_LOCAL_ARG_NAME, (,), __VA_ARGS__)); \
} while (0)
#endif /* CONFIG_LOG2_ALWAYS_RUNTIME */
#define Z_LOG_MSG2_CREATE(_try_0cpy, _mode, _domain_id, _source,\
_level, _data, _dlen, ...) \
Z_LOG_MSG2_CREATE2(_try_0cpy, _mode, UTIL_CAT(Z_LOG_FUNC_PREFIX_, _level), \
_domain_id, _source, _level, _data, _dlen, \
Z_LOG_STR(_level, __VA_ARGS__))
/** @brief Allocate log message.
*
* @param wlen Length in 32 bit words.
*
* @return allocated space or null if cannot be allocated.
*/
struct log_msg2 *z_log_msg2_alloc(uint32_t wlen);
/** @brief Finalize message.
*
* Finalization includes setting source, copying data and timestamp in the
* message followed by committing the message.
*
* @param msg Message.
*
* @param source Address of the source descriptor.
*
* @param desc Message descriptor.
*
* @param data Data.
*/
void z_log_msg2_finalize(struct log_msg2 *msg, const void *source,
const struct log_msg2_desc desc, const void *data);
/** @brief Create simple message from message details and string package.
*
* @param source Source.
*
* @param desc Message descriptor.
*
* @param package Package.
*
* @oaram data Data.
*/
__syscall void z_log_msg2_static_create(const void *source,
const struct log_msg2_desc desc,
uint8_t *package, const void *data);
/** @brief Create message at runtime.
*
* Function allows to build any log message based on input data. Processing
* time is significantly higher than statically message creating.
*
* @param domain_id Domain ID.
*
* @param source Source.
*
* @param level Log level.
*
* @param data Data.
*
* @param dlen Data length.
*
* @param fmt String.
*
* @param ap Variable list of string arguments.
*/
__syscall void z_log_msg2_runtime_vcreate(uint8_t domain_id, const void *source,
uint8_t level, const void *data,
size_t dlen, const char *fmt,
va_list ap);
/** @brief Create message at runtime.
*
* Function allows to build any log message based on input data. Processing
* time is significantly higher than statically message creating.
*
* @param domain_id Domain ID.
*
* @param source Source.
*
* @param level Log level.
*
* @param data Data.
*
* @param dlen Data length.
*
* @param fmt String.
*
* @param ... String arguments.
*/
static inline void z_log_msg2_runtime_create(uint8_t domain_id,
const void *source,
uint8_t level, const void *data,
size_t dlen, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
z_log_msg2_runtime_vcreate(domain_id, source, level,
data, dlen, fmt, ap);
va_end(ap);
}
static inline bool z_log_item_is_msg(const union log_msg2_generic *msg)
{
return msg->generic.type == Z_LOG_MSG2_LOG;
}
/** @brief Get total length (in 32 bit words) of a log message.
*
* @param desc Log message descriptor.
*
* @return Length.
*/
static inline uint32_t log_msg2_get_total_wlen(const struct log_msg2_desc desc)
{
return Z_LOG_MSG2_ALIGNED_WLEN(desc.package_len, desc.data_len);
}
/** @brief Get length of the log item.
*
* @param item Item.
*
* @return Length in 32 bit words.
*/
static inline uint32_t log_msg2_generic_get_wlen(const union mpsc_pbuf_generic *item)
{
const union log_msg2_generic *generic_msg = (const union log_msg2_generic *)item;
if (z_log_item_is_msg(generic_msg)) {
const struct log_msg2 *msg = (const struct log_msg2 *)generic_msg;
return log_msg2_get_total_wlen(msg->hdr.desc);
}
return 0;
}
/** @brief Get log message domain ID.
*
* @param msg Log message.
*
* @return Domain ID
*/
static inline uint8_t log_msg2_get_domain(struct log_msg2 *msg)
{
return msg->hdr.desc.domain;
}
/** @brief Get log message level.
*
* @param msg Log message.
*
* @return Log level.
*/
static inline uint8_t log_msg2_get_level(struct log_msg2 *msg)
{
return msg->hdr.desc.level;
}
/** @brief Get message source data.
*
* @param msg Log message.
*
* @return Pointer to the source data.
*/
static inline const void *log_msg2_get_source(struct log_msg2 *msg)
{
return msg->hdr.source;
}
/** @brief Get timestamp.
*
* @param msg Log message.
*
* @return Timestamp.
*/
static inline log_timestamp_t log_msg2_get_timestamp(struct log_msg2 *msg)
{
return msg->hdr.timestamp;
}
/** @brief Get data buffer.
*
* @param msg log message.
*
* @param len location where data length is written.
*
* @return pointer to the data buffer.
*/
static inline uint8_t *log_msg2_get_data(struct log_msg2 *msg, size_t *len)
{
*len = msg->hdr.desc.data_len;
return msg->data + msg->hdr.desc.package_len;
}
/** @brief Get string package.
*
* @param msg log message.
*
* @param len location where string package length is written.
*
* @return pointer to the package.
*/
static inline uint8_t *log_msg2_get_package(struct log_msg2 *msg, size_t *len)
{
*len = msg->hdr.desc.package_len;
return msg->data;
}
/**
* @}
*/
#include <syscalls/log_msg2.h>
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_LOGGING_LOG_MSG2_H_ */