This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 52b77dcb4d325c04bb1855789b83e237f25b4d10 Author: Marco Casaroli <[email protected]> AuthorDate: Tue Oct 20 21:06:21 2026 +0200 libs/libc/stdlib: Grow the exit function list on the heap. atexit(), on_exit(), at_quick_exit() and __cxa_atexit() share one table of CONFIG_LIBC_MAX_EXITFUNS entries in the task information. When it was full, the registration failed, and a C++ program with more static objects than entries lost their destructors without notice. With CONFIG_LIBC_MAX_EXITFUNS=0 (the default) atexit() returned 0 and never called the function. Keep CONFIG_LIBC_MAX_EXITFUNS entries in the task information, and allocate more entries from the heap, 8 at a time. The number of exit functions is now limited only by memory: ATEXIT_MAX and sysconf(_SC_ATEXIT_MAX) are INT_MAX. A registration fails only when the heap is full, and then returns non-zero. The heap part is freed after the exit functions ran, or when the process ends without exit(). With CONFIG_LIBC_MAX_EXITFUNS=0 there are no exit functions, as before, but atexit(), on_exit() and at_quick_exit() are not built: a program that calls them does not link, instead of losing its functions without notice. __cxa_atexit() still registers nothing, so that C++ programs link; the destructors of static objects do not run, as before. struct atexit_list_s in struct task_info_s gets a count and a pointer for the heap part. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <[email protected]> --- include/nuttx/atexit.h | 21 ++++++-- libs/libc/stdlib/Kconfig | 16 ++++-- libs/libc/stdlib/lib_atexit.c | 118 +++++++++++++++++++++++++++++------------- sched/tls/task_uninitinfo.c | 12 +++++ 4 files changed, 124 insertions(+), 43 deletions(-) diff --git a/include/nuttx/atexit.h b/include/nuttx/atexit.h index c7b32e1b7ff..882d859f2c4 100644 --- a/include/nuttx/atexit.h +++ b/include/nuttx/atexit.h @@ -28,6 +28,7 @@ ****************************************************************************/ #include <nuttx/config.h> +#include <limits.h> #include <stdbool.h> #include <stdlib.h> @@ -35,9 +36,17 @@ * Pre-processor Definitions ****************************************************************************/ -/* Amount of exit functions */ +/* The number of exit functions is limited only by memory: the first + * CONFIG_LIBC_MAX_EXITFUNS entries are in the task information, the next + * ones come from the heap. With CONFIG_LIBC_MAX_EXITFUNS 0 there are no + * exit functions: atexit(), at_quick_exit() and on_exit() are not built. + */ -#define ATEXIT_MAX (CONFIG_LIBC_MAX_EXITFUNS) +#if CONFIG_LIBC_MAX_EXITFUNS > 0 +# define ATEXIT_MAX INT_MAX +#else +# define ATEXIT_MAX 0 +#endif /**************************************************************************** * Public Types @@ -59,11 +68,15 @@ struct atexit_s FAR void *arg; }; +#if CONFIG_LIBC_MAX_EXITFUNS > 0 struct atexit_list_s { - int nfuncs; - struct atexit_s funcs[ATEXIT_MAX]; + int nfuncs; /* Number of registered functions */ + int nextra; /* Number of entries in extra */ + FAR struct atexit_s *extra; /* Entries after funcs[] (heap) */ + struct atexit_s funcs[CONFIG_LIBC_MAX_EXITFUNS]; }; +#endif /**************************************************************************** * Public Function Prototypes diff --git a/libs/libc/stdlib/Kconfig b/libs/libc/stdlib/Kconfig index 272258344e3..a3433fd55a7 100644 --- a/libs/libc/stdlib/Kconfig +++ b/libs/libc/stdlib/Kconfig @@ -31,11 +31,21 @@ config LIBC_MAX_TMPFILE file path. config LIBC_MAX_EXITFUNS - int "Maximum amount of exit functions" + int "Number of preallocated exit functions" default 0 ---help--- - Configure the amount of exit functions for atexit/on_exit. The ANSI - default is 32, but most likely we don't need as many. + The number of exit functions (atexit(), on_exit(), at_quick_exit() + and the C++ __cxa_atexit()) that the task information of each + process holds. More functions come from the heap, 8 at a time, + so the number is limited only by memory. The entries in the task + information need no heap, also not at start-up. + + With 0, there are no exit functions: atexit(), on_exit() and + at_quick_exit() are not built, so a program that calls them does + not link, and __cxa_atexit() registers nothing (the destructors + of static C++ objects do not run). A C++ ELF program (built with + -fno-use-cxa-atexit) with a static object in a function calls + atexit(), so it needs a value larger than 0. config LIBC_DISABLE_HEXSTR_TO_FLOAT bool "Disable hex-string to float/double conversions" diff --git a/libs/libc/stdlib/lib_atexit.c b/libs/libc/stdlib/lib_atexit.c index 132ab302ff4..4ad98ea0346 100644 --- a/libs/libc/stdlib/lib_atexit.c +++ b/libs/libc/stdlib/lib_atexit.c @@ -29,11 +29,20 @@ #include <stdlib.h> #include <nuttx/atexit.h> +#include <nuttx/lib/lib.h> #include <nuttx/mutex.h> #include <nuttx/tls.h> #if CONFIG_LIBC_MAX_EXITFUNS > 0 +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Number of entries that each heap allocation adds */ + +#define ATEXIT_GROW 8 + /**************************************************************************** * Private Functions ****************************************************************************/ @@ -60,6 +69,25 @@ static FAR struct atexit_list_s *get_exitfuncs(void) return &info->ta_exit; } +/**************************************************************************** + * Name: get_entry + * + * Description: + * Return entry 'idx' of the list: in funcs[] or in the heap part. + * + ****************************************************************************/ + +static inline FAR struct atexit_s * +get_entry(FAR struct atexit_list_s *aehead, int idx) +{ + if (idx < CONFIG_LIBC_MAX_EXITFUNS) + { + return &aehead->funcs[idx]; + } + + return &aehead->extra[idx - CONFIG_LIBC_MAX_EXITFUNS]; +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -69,51 +97,59 @@ int atexit_register(int type, CODE void (*func)(void), FAR void *arg, { FAR struct task_info_s *info = task_get_info(); FAR struct atexit_list_s *aehead; - int idx; - int ret = ERROR; + FAR struct atexit_s *entry; + int ret; /* REVISIT: Missing logic */ UNUSED(dso); - /* The following must be atomic */ + if (func == NULL) + { + return ERROR; + } aehead = get_exitfuncs(); - if (func) + ret = nxrmutex_lock(&info->ta_lock); + if (ret < 0) { - ret = nxrmutex_lock(&info->ta_lock); - if (ret < 0) - { - return -ret; - } + return ERROR; + } - if ((idx = aehead->nfuncs) < ATEXIT_MAX) - { - aehead->funcs[idx].type = type; - aehead->funcs[idx].func = func; - aehead->funcs[idx].arg = arg; - aehead->nfuncs++; - ret = OK; - } - else + /* Grow the heap part when the list is full */ + + if (aehead->nfuncs >= CONFIG_LIBC_MAX_EXITFUNS + aehead->nextra) + { + entry = lib_realloc(aehead->extra, (aehead->nextra + ATEXIT_GROW) * + sizeof(struct atexit_s)); + if (entry == NULL) { - ret = ERROR; + nxrmutex_unlock(&info->ta_lock); + return ERROR; } - nxrmutex_unlock(&info->ta_lock); + aehead->extra = entry; + aehead->nextra += ATEXIT_GROW; } - return ret; + entry = get_entry(aehead, aehead->nfuncs); + entry->type = type; + entry->func = func; + entry->arg = arg; + aehead->nfuncs++; + + nxrmutex_unlock(&info->ta_lock); + return OK; } void atexit_call_exitfuncs(int status, bool quick) { FAR struct atexit_list_s *aehead; FAR struct task_info_s *info = task_get_info(); + FAR struct atexit_s *entry; CODE void (*func)(void); FAR void *arg; - int idx; int type; /* Call exit functions in reverse order */ @@ -133,16 +169,16 @@ void atexit_call_exitfuncs(int status, bool quick) break; } - idx = aehead->nfuncs - 1; + entry = get_entry(aehead, aehead->nfuncs - 1); - type = aehead->funcs[idx].type; - func = aehead->funcs[idx].func; - arg = aehead->funcs[idx].arg; + type = entry->type; + func = entry->func; + arg = entry->arg; /* Remove the function to prevent recursive call to it */ - aehead->funcs[idx].func = NULL; - aehead->funcs[idx].arg = NULL; + entry->func = NULL; + entry->arg = NULL; aehead->nfuncs--; nxrmutex_unlock(&info->ta_lock); @@ -172,9 +208,21 @@ void atexit_call_exitfuncs(int status, bool quick) (*((CODE void (*)(FAR void *))func))(arg); } } -} -#endif + /* Release the heap part */ + + if (nxrmutex_lock(&info->ta_lock) >= 0) + { + if (aehead->nfuncs == 0) + { + lib_free(aehead->extra); + aehead->extra = NULL; + aehead->nextra = 0; + } + + nxrmutex_unlock(&info->ta_lock); + } +} /**************************************************************************** * Name: atexit @@ -187,9 +235,7 @@ void atexit_call_exitfuncs(int status, bool quick) * * Limitations in the current implementation: * - * 1. Only a single atexit function can be registered unless - * CONFIG_LIBC_MAX_EXITFUNS defines a larger number. - * 2. atexit functions are not inherited when a new task is + * 1. atexit functions are not inherited when a new task is * created. * * Input Parameters: @@ -241,9 +287,7 @@ int at_quick_exit(CODE void (*func)(void)) * * Limitations in the current implementation: * - * 1. Only a single on_exit function can be registered unless - * CONFIG_LIBC_MAX_EXITFUNS defines a larger number. - * 2. on_exit functions are not inherited when a new task is + * 1. on_exit functions are not inherited when a new task is * created. * * Input Parameters: @@ -262,6 +306,8 @@ int on_exit(CODE void (*func)(int, FAR void *), FAR void *arg) NULL); } +#endif /* CONFIG_LIBC_MAX_EXITFUNS > 0 */ + /**************************************************************************** * Name: __cxa_atexit * diff --git a/sched/tls/task_uninitinfo.c b/sched/tls/task_uninitinfo.c index c8cb24eae98..bd104741109 100644 --- a/sched/tls/task_uninitinfo.c +++ b/sched/tls/task_uninitinfo.c @@ -193,6 +193,18 @@ void task_uninit_info(FAR struct task_group_s *group) } #endif +#if CONFIG_LIBC_MAX_EXITFUNS > 0 + /* Free the heap part of the exit function list (the task did not call + * exit()) + */ + + if (info->ta_exit.extra != NULL) + { + group_free(group, info->ta_exit.extra); + info->ta_exit.extra = NULL; + } +#endif + /* Release all environment variables */ env_release(info);
