Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/utilities/ulog/backend/console_be.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Change Logs:
* Date Author Notes
* 2018-09-04 armink the first version
* 2025-10-30 wdfk-prog add emergency log flush mechanism
*/

#include <rthw.h>
Expand All @@ -32,6 +33,8 @@ int ulog_console_backend_init(void)
console.output = ulog_console_backend_output;

ulog_backend_register(&console, "console", RT_TRUE);
console.output = ulog_console_backend_output;
console.is_emergency_backend = RT_TRUE;

return 0;
}
Expand Down
117 changes: 108 additions & 9 deletions components/utilities/ulog/ulog.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Change Logs:
* Date Author Notes
* 2018-08-25 armink the first version
* 2025-10-30 wdfk-prog add emergency log flush mechanism
*/

#include <stdarg.h>
Expand Down Expand Up @@ -501,7 +502,23 @@ rt_weak rt_size_t ulog_hex_formater(char *log_buf, const char *tag, const rt_uin
return ulog_tail_formater(log_buf, log_len, RT_TRUE, LOG_LVL_DBG);
}

static void ulog_output_to_all_backend(rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log, rt_size_t len)
/**
* @brief Internal helper to broadcast a log to backends.
*
* @details This is the core broadcasting function. It iterates through all
* registered backends and outputs the log message. It can operate
* in two modes.
*
* @param emergency_only If RT_TRUE, it will only output to backends marked as
* `is_emergency_backend`. If RT_FALSE, it will output to
* all backends that match the filter criteria.
* @param level The log level.
* @param tag The log tag.
* @param is_raw Whether the log is raw data.
* @param log The log message buffer.
* @param len The length of the log message.
*/
static void _ulog_output_to_backends(rt_bool_t emergency_only, rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log, rt_size_t len)
{
rt_slist_t *node;
ulog_backend_t backend;
Expand All @@ -520,6 +537,13 @@ static void ulog_output_to_all_backend(rt_uint32_t level, const char *tag, rt_bo
for (node = rt_slist_first(&ulog.backend_list); node; node = rt_slist_next(node))
{
backend = rt_slist_entry(node, struct ulog_backend, list);

if (emergency_only && !backend->is_emergency_backend)
{
/* In emergency mode, skip any backend not marked as emergency-safe. */
continue;
}

if (backend->out_level < level)
{
continue;
Expand Down Expand Up @@ -558,6 +582,11 @@ static void ulog_output_to_all_backend(rt_uint32_t level, const char *tag, rt_bo
}
}

static void ulog_output_to_all_backend(rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log, rt_size_t len)
{
_ulog_output_to_backends(RT_FALSE, level, tag, is_raw, log, len);
}

static void do_output(rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log_buf, rt_size_t log_len)
{
#ifdef ULOG_USING_ASYNC_OUTPUT
Expand Down Expand Up @@ -1297,6 +1326,7 @@ rt_err_t ulog_backend_register(ulog_backend_t backend, const char *name, rt_bool
backend->support_color = support_color;
backend->out_level = LOG_FILTER_LVL_ALL;
rt_strncpy(backend->name, name, RT_NAME_MAX - 1);
backend->is_emergency_backend = RT_FALSE;

level = rt_spin_lock_irqsave(&_spinlock);
rt_slist_append(&ulog.backend_list, &backend->list);
Expand Down Expand Up @@ -1366,13 +1396,54 @@ ulog_backend_t ulog_backend_find(const char *name)
return RT_NULL;
}

/**
* @brief Sets the emergency-safe status for a specific backend at runtime.
*
* @details This function allows an application to dynamically mark a backend
* as safe or unsafe for use in emergency contexts (e.g., fault handlers).
* Only backends marked as safe will be used by `ulog_emergency_flush`.
*
* @param[in] name The name of the target backend.
* @param[in] is_emergency RT_TRUE to mark the backend as safe for emergency calls,
* RT_FALSE otherwise.
*
* @return rt_err_t RT_EOK on success, -RT_ERROR if the backend with the given name
* is not found, or -RT_EINVAL if the name is invalid.
*/
rt_err_t ulog_backend_set_emergency(const char *name, rt_bool_t is_emergency)
{
ulog_backend_t backend;
rt_err_t result = -RT_ERROR;

if (name == RT_NULL)
{
return -RT_EINVAL;
}

rt_mutex_take(&ulog.output_locker, RT_WAITING_FOREVER);

backend = ulog_backend_find(name);
if (backend != RT_NULL)
{
backend->is_emergency_backend = is_emergency;
result = RT_EOK;
}

rt_mutex_release(&ulog.output_locker);

return result;
}

#ifdef ULOG_USING_ASYNC_OUTPUT
/**
* asynchronous output logs to all backends
*
* @param[in] emergency_mode A flag to select the operational mode. RT_FALSE for
* normal operation, RT_TRUE for emergency fault context.
*
* @note you must call this function when ULOG_ASYNC_OUTPUT_BY_THREAD is disable
*/
void ulog_async_output(void)
void ulog_async_output(rt_bool_t emergency_mode)
{
rt_rbb_blk_t log_blk;
ulog_frame_t log_frame;
Expand All @@ -1387,14 +1458,12 @@ void ulog_async_output(void)
log_frame = (ulog_frame_t) log_blk->buf;
if (log_frame->magic == ULOG_FRAME_MAGIC)
{
/* output to all backends */
ulog_output_to_all_backend(log_frame->level, log_frame->tag, log_frame->is_raw, log_frame->log,
log_frame->log_len);
_ulog_output_to_backends(emergency_mode, log_frame->level, log_frame->tag, log_frame->is_raw, log_frame->log, log_frame->log_len);
}
rt_rbb_blk_free(ulog.async_rbb, log_blk);
}
/* output the log_raw format log */
if (ulog.async_rb)
if (ulog.async_rb && !emergency_mode)
{
rt_size_t log_len = rt_ringbuffer_data_len(ulog.async_rb);
char *log = rt_malloc(log_len + 1);
Expand Down Expand Up @@ -1434,14 +1503,14 @@ rt_err_t ulog_async_waiting_log(rt_int32_t time)

static void async_output_thread_entry(void *param)
{
ulog_async_output();
ulog_async_output(RT_FALSE);

while (1)
{
ulog_async_waiting_log(RT_WAITING_FOREVER);
while (1)
{
ulog_async_output();
ulog_async_output(RT_FALSE);
/* If there is no log output for a certain period of time,
* refresh the log buffer
*/
Expand Down Expand Up @@ -1471,7 +1540,7 @@ void ulog_flush(void)
return;

#ifdef ULOG_USING_ASYNC_OUTPUT
ulog_async_output();
ulog_async_output(RT_FALSE);
#endif

/* flush all backends */
Expand All @@ -1485,6 +1554,36 @@ void ulog_flush(void)
}
}

/**
* @brief Flushes pending logs and outputs to emergency-safe backends.
*/
void ulog_emergency_flush(void)
{
rt_slist_t *node;
ulog_backend_t backend;
rt_base_t irq_flag;

if (!ulog.init_ok)
return;

irq_flag = rt_hw_interrupt_disable();

#ifdef ULOG_USING_ASYNC_OUTPUT
ulog_async_output(RT_TRUE);
#endif

rt_slist_for_each(node, &ulog.backend_list)
{
backend = rt_slist_entry(node, struct ulog_backend, list);
if (backend->is_emergency_backend && backend->flush)
{
backend->flush(backend);
}
}

rt_hw_interrupt_enable(irq_flag);
}

/**
* @brief ulog initialization
*
Expand Down
43 changes: 42 additions & 1 deletion components/utilities/ulog/ulog.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Change Logs:
* Date Author Notes
* 2018-08-25 armink the first version
* 2025-10-30 wdfk-prog add emergency log flush mechanism
*/

#ifndef _ULOG_H_
Expand Down Expand Up @@ -47,13 +48,52 @@ void ulog_deinit(void);
#define LOG_RAW(...) ulog_raw(__VA_ARGS__)
#define LOG_HEX(name, width, buf, size) ulog_hex(name, width, buf, size)

/**
* @brief Output an emergency log and attempt an immediate, safe flush.
*
* @details This macro is specifically designed for use in critical system states
* where standard logging mechanisms might be unreliable, such as in
* assertion handlers, HardFault handlers, or stack overflow hooks.
*
* It operates in two steps:
* 1. It calls ulog_e() to place the log message into the ulog buffer.
* 2. It immediately calls ulog_emergency_flush(). This special flush
* function is designed to bypass potentially unsafe OS services (like
* mutexes or complex file systems). It will only flush the log to
* backends that have been explicitly marked as "emergency safe"
* (where is_emergency_backend is set to RT_TRUE), such as a simple
* console or polled UART backend.
*
* This provides the highest possible chance of outputting critical debug
* information just before a system crash or lock-up.
*
* @note The LOG_TAG macro must be defined before using this API.
*
* @example
* // In a stack overflow hook
* #define LOG_TAG "StackCheck"
* #include <ulog.h>
* void rt_err_hook(rt_err_t err, const char* msg, rt_uint32_t* sp)
* {
* LOG_EMERGENCY("Stack overflow detected! Msg: %s, SP: 0x%08X", msg, sp);
* // The system will likely halt after this.
* }
*/
#define LOG_EMERGENCY(...) \
do \
{ \
ulog_e(LOG_TAG, __VA_ARGS__); \
ulog_emergency_flush(); \
} while (0)

/*
* backend register and unregister
*/
rt_err_t ulog_backend_register(ulog_backend_t backend, const char *name, rt_bool_t support_color);
rt_err_t ulog_backend_unregister(ulog_backend_t backend);
rt_err_t ulog_backend_set_filter(ulog_backend_t backend, ulog_backend_filter_t filter);
ulog_backend_t ulog_backend_find(const char *name);
rt_err_t ulog_backend_set_emergency(const char *name, rt_bool_t is_emergency);

#ifdef ULOG_USING_FILTER
/*
Expand All @@ -74,12 +114,13 @@ const char *ulog_global_filter_kw_get(void);
* flush all backends's log
*/
void ulog_flush(void);
void ulog_emergency_flush(void);

#ifdef ULOG_USING_ASYNC_OUTPUT
/*
* asynchronous output API
*/
void ulog_async_output(void);
void ulog_async_output(rt_bool_t emergency_mode);
void ulog_async_output_enabled(rt_bool_t enabled);
rt_err_t ulog_async_waiting_log(rt_int32_t time);
#endif
Expand Down
2 changes: 2 additions & 0 deletions components/utilities/ulog/ulog_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Change Logs:
* Date Author Notes
* 2018-08-25 armink the first version
* 2025-10-30 wdfk-prog add emergency log flush mechanism
*/

#ifndef _ULOG_DEF_H_
Expand Down Expand Up @@ -198,6 +199,7 @@ struct ulog_backend
char name[RT_NAME_MAX];
rt_bool_t support_color;
rt_uint32_t out_level;
rt_bool_t is_emergency_backend; /**< Can this backend be called safely in a fault/emergency context? */
void (*init) (struct ulog_backend *backend);
void (*output)(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log, rt_size_t len);
void (*flush) (struct ulog_backend *backend);
Expand Down
3 changes: 2 additions & 1 deletion src/scheduler_comm.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Date Author Notes
* 2024-01-18 Shell Separate scheduling related codes from thread.c, scheduler_.*
* 2025-09-01 Rbb666 Add thread stack overflow hook.
* 2025-10-30 wdfk-prog add emergency log flush mechanism
*/

#define DBG_TAG "kernel.sched"
Expand Down Expand Up @@ -484,7 +485,7 @@ void rt_scheduler_stack_check(struct rt_thread *thread)
rt_base_t dummy = 1;
rt_err_t hook_result = -RT_ERROR;

LOG_E("thread:%s stack overflow\n", thread->parent.name);
LOG_EMERGENCY("thread:%s stack overflow\n", thread->parent.name);

#if defined(RT_USING_HOOK) && defined(RT_HOOK_USING_FUNC_PTR)
if (rt_stack_overflow_hook != RT_NULL)
Expand Down
Loading