Merge branch 'feature/libc_init_array-from-toolchain' into 'master'

fix(esp_system): fix .init_array.*/.ctors.* ordering

Closes IDF-1176 and DOC-11527

See merge request espressif/esp-idf!39811
This commit is contained in:
Alexey Lapshin
2025-08-05 16:09:00 +04:00
26 changed files with 279 additions and 200 deletions

View File

@@ -359,8 +359,12 @@ SECTIONS
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*/ */
ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(.preinit_array))
__preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors SORT(.ctors.*))) KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);
/* Addresses of memory regions reserved via SOC_RESERVE_MEMORY_REGION() */ /* Addresses of memory regions reserved via SOC_RESERVE_MEMORY_REGION() */

View File

@@ -197,20 +197,12 @@ SECTIONS
* C++ constructor tables. * C++ constructor tables.
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*
* RISC-V gcc is configured with --enable-initfini-array so it emits
* .init_array section instead. But the init_priority sections will be
* sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending
* order during startup, however. Hence a different section is generated for
* the init_priority functions which is iterated in ascending order during
* startup. The corresponding code can be found in startup.c.
*/ */
ALIGNED_SYMBOL(4, __init_priority_array_start) ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) KEEP (*(.preinit_array))
__init_priority_array_end = ABSOLUTE(.); __preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);

View File

@@ -319,20 +319,12 @@ SECTIONS
* C++ constructor tables. * C++ constructor tables.
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*
* RISC-V gcc is configured with --enable-initfini-array so it emits
* .init_array section instead. But the init_priority sections will be
* sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending
* order during startup, however. Hence a different section is generated for
* the init_priority functions which is iterated in ascending order during
* startup. The corresponding code can be found in startup.c.
*/ */
ALIGNED_SYMBOL(4, __init_priority_array_start) ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) KEEP (*(.preinit_array))
__init_priority_array_end = ABSOLUTE(.); __preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);

View File

@@ -355,20 +355,12 @@ SECTIONS
* C++ constructor tables. * C++ constructor tables.
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*
* RISC-V gcc is configured with --enable-initfini-array so it emits
* .init_array section instead. But the init_priority sections will be
* sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending
* order during startup, however. Hence a different section is generated for
* the init_priority functions which is iterated in ascending order during
* startup. The corresponding code can be found in startup.c.
*/ */
ALIGNED_SYMBOL(4, __init_priority_array_start) ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) KEEP (*(.preinit_array))
__init_priority_array_end = ABSOLUTE(.); __preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);

View File

@@ -364,20 +364,12 @@ SECTIONS
* C++ constructor tables. * C++ constructor tables.
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*
* RISC-V gcc is configured with --enable-initfini-array so it emits
* .init_array section instead. But the init_priority sections will be
* sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending
* order during startup, however. Hence a different section is generated for
* the init_priority functions which is iterated in ascending order during
* startup. The corresponding code can be found in startup.c.
*/ */
ALIGNED_SYMBOL(4, __init_priority_array_start) ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) KEEP (*(.preinit_array))
__init_priority_array_end = ABSOLUTE(.); __preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);

View File

@@ -213,20 +213,13 @@ SECTIONS
* C++ constructor tables. * C++ constructor tables.
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*
* RISC-V gcc is configured with --enable-initfini-array so it emits
* .init_array section instead. But the init_priority sections will be
* sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending
* order during startup, however. Hence a different section is generated for
* the init_priority functions which is iterated in ascending order during
* startup. The corresponding code can be found in startup.c.
*/ */
ALIGNED_SYMBOL(4, __init_priority_array_start) ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) KEEP (*(.preinit_array))
__init_priority_array_end = ABSOLUTE(.); __preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);

View File

@@ -366,20 +366,12 @@ SECTIONS
* C++ constructor tables. * C++ constructor tables.
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*
* RISC-V gcc is configured with --enable-initfini-array so it emits
* .init_array section instead. But the init_priority sections will be
* sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending
* order during startup, however. Hence a different section is generated for
* the init_priority functions which is iterated in ascending order during
* startup. The corresponding code can be found in startup.c.
*/ */
ALIGNED_SYMBOL(4, __init_priority_array_start) ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) KEEP (*(.preinit_array))
__init_priority_array_end = ABSOLUTE(.); __preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);

View File

@@ -355,20 +355,12 @@ SECTIONS
* C++ constructor tables. * C++ constructor tables.
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*
* RISC-V gcc is configured with --enable-initfini-array so it emits
* .init_array section instead. But the init_priority sections will be
* sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending
* order during startup, however. Hence a different section is generated for
* the init_priority functions which is iterated in ascending order during
* startup. The corresponding code can be found in startup.c.
*/ */
ALIGNED_SYMBOL(4, __init_priority_array_start) ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) KEEP (*(.preinit_array))
__init_priority_array_end = ABSOLUTE(.); __preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);

View File

@@ -200,20 +200,12 @@ SECTIONS
* C++ constructor tables. * C++ constructor tables.
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*
* RISC-V gcc is configured with --enable-initfini-array so it emits
* .init_array section instead. But the init_priority sections will be
* sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending
* order during startup, however. Hence a different section is generated for
* the init_priority functions which is iterated in ascending order during
* startup. The corresponding code can be found in startup.c.
*/ */
ALIGNED_SYMBOL(4, __init_priority_array_start) ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) KEEP (*(.preinit_array))
__init_priority_array_end = ABSOLUTE(.); __preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);

View File

@@ -377,20 +377,12 @@ SECTIONS
* C++ constructor tables. * C++ constructor tables.
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*
* RISC-V gcc is configured with --enable-initfini-array so it emits
* .init_array section instead. But the init_priority sections will be
* sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending
* order during startup, however. Hence a different section is generated for
* the init_priority functions which is iterated in ascending order during
* startup. The corresponding code can be found in startup.c.
*/ */
ALIGNED_SYMBOL(4, __init_priority_array_start) ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) KEEP (*(.preinit_array))
__init_priority_array_end = ABSOLUTE(.); __preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);

View File

@@ -362,8 +362,12 @@ SECTIONS
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*/ */
ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(.preinit_array))
__preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors SORT(.ctors.*))) KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);
/* Addresses of memory regions reserved via SOC_RESERVE_MEMORY_REGION() */ /* Addresses of memory regions reserved via SOC_RESERVE_MEMORY_REGION() */

View File

@@ -373,8 +373,12 @@ SECTIONS
* *
* Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt. * Excluding crtbegin.o/crtend.o since IDF doesn't use the toolchain crt.
*/ */
ALIGNED_SYMBOL(4, __preinit_array_start)
KEEP (*(.preinit_array))
__preinit_array_end = ABSOLUTE(.);
ALIGNED_SYMBOL(4, __init_array_start) ALIGNED_SYMBOL(4, __init_array_start)
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors SORT(.ctors.*))) KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors.*)))
KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors))
__init_array_end = ABSOLUTE(.); __init_array_end = ABSOLUTE(.);
/* Addresses of memory regions reserved via SOC_RESERVE_MEMORY_REGION() */ /* Addresses of memory regions reserved via SOC_RESERVE_MEMORY_REGION() */

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -58,27 +58,16 @@ const sys_startup_fn_t g_startup_fn[1] = { start_cpu0 };
static const char* TAG = "cpu_start"; static const char* TAG = "cpu_start";
/**
* Xtensa gcc is configured to emit a .ctors section, RISC-V gcc is configured with --enable-initfini-array
* so it emits an .init_array section instead.
* But the init_priority sections will be sorted for iteration in ascending order during startup.
* The rest of the init_array sections is sorted for iteration in descending order during startup, however.
* Hence a different section is generated for the init_priority functions which is looped
* over in ascending direction instead of descending direction.
* The RISC-V-specific behavior is dependent on the linker script ld/esp32c3/sections.ld.in.
*/
__attribute__((no_sanitize_undefined)) /* TODO: IDF-8133 */
static void do_global_ctors(void)
{
#if __riscv
extern void (*__init_priority_array_start)(void);
extern void (*__init_priority_array_end)(void);
#endif
extern void (*__init_array_start)(void);
extern void (*__init_array_end)(void);
#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS #ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
/**
* @brief A helper function for __do_global_ctors, which is in crtend.o.
* It has been adapted from GCC source code. In GCC, it resides under
* the USE_EH_FRAME_REGISTRY macro, which is not enabled in Espressif
* toolchains to save small memory amount. Nevertheless, when C++ exceptions
* are enabled this initialization becomes necessary.
*/
static void __do_global_ctors_1(void)
{
struct object { struct object {
long placeholder[10]; long placeholder[10];
}; };
@@ -87,26 +76,9 @@ static void do_global_ctors(void)
static struct object ob; static struct object ob;
__register_frame_info(__eh_frame, &ob); __register_frame_info(__eh_frame, &ob);
}
#endif // CONFIG_COMPILER_CXX_EXCEPTIONS #endif // CONFIG_COMPILER_CXX_EXCEPTIONS
void (**p)(void);
#if __riscv
for (p = &__init_priority_array_start; p < &__init_priority_array_end; ++p) {
ESP_LOGD(TAG, "calling init function: %p", *p);
(*p)();
}
#endif
ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-out-of-bounds")
for (p = &__init_array_end - 1; p >= &__init_array_start; --p) {
ESP_LOGD(TAG, "calling init function: %p", *p);
(*p)();
}
ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-out-of-bounds")
}
/** /**
* @brief Call component init functions defined using ESP_SYSTEM_INIT_Fn macros. * @brief Call component init functions defined using ESP_SYSTEM_INIT_Fn macros.
* The esp_system_init_fn_t structures describing these functions are collected into * The esp_system_init_fn_t structures describing these functions are collected into
@@ -199,12 +171,16 @@ static void do_secondary_init(void)
static void start_cpu0_default(void) static void start_cpu0_default(void)
{ {
extern void __libc_init_array(void);
// Initialize core components and services. // Initialize core components and services.
// Operations that needs the cache to be disabled have to be done here. // Operations that needs the cache to be disabled have to be done here.
do_core_init(); do_core_init();
// Execute constructors. // Execute constructors.
do_global_ctors(); #ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
__do_global_ctors_1();
#endif
__libc_init_array();
/* ----------------------------------Separator----------------------------- /* ----------------------------------Separator-----------------------------
* After this stage, other CPU start running with the cache, however the scheduler (and ipc service) is not available. * After this stage, other CPU start running with the cache, however the scheduler (and ipc service) is not available.

View File

@@ -58,18 +58,4 @@ menu "Unity unit testing library"
jumping back to the test menu. The jumping is usually occurs in assert jumping back to the test menu. The jumping is usually occurs in assert
functions such as TEST_ASSERT, TEST_FAIL etc. functions such as TEST_ASSERT, TEST_FAIL etc.
config UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE
bool "Order unit tests by file path and line number"
default n
help
If enabled, the Unity test framework will automatically insert test cases
in a sorted order at registration time (during constructor execution),
based on their source file path and line number.
This ensures consistent execution order across platforms (e.g., Linux vs. on-chip),
preserving the logical order in which tests are written in the source files.
Note, the file path used for sorting follows the full absolute path format.
/IDF/examples/system/unit_test/components/testable/test/test_mean.c
endmenu # "Unity unit testing library" endmenu # "Unity unit testing library"

View File

@@ -1,2 +1 @@
CONFIG_ESP_TASK_WDT_EN=n CONFIG_ESP_TASK_WDT_EN=n
CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE=y

View File

@@ -27,35 +27,9 @@ void unity_testcase_register(test_desc_t *desc)
s_unity_tests_last = desc; s_unity_tests_last = desc;
return; return;
} }
#if CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE
test_desc_t *prev = NULL;
test_desc_t *current = s_unity_tests_first;
while (current) {
int file_cmp = strcmp(desc->file, current->file);
if (file_cmp < 0 || (file_cmp == 0 && desc->line < current->line)) {
// Insert before current
if (prev) {
prev->next = desc;
} else {
// Inserting at the head
s_unity_tests_first = desc;
}
desc->next = current;
return;
}
prev = current;
current = current->next;
}
// Insert at the end // Insert at the end
prev->next = desc; s_unity_tests_last->next = desc;
s_unity_tests_last = desc; s_unity_tests_last = desc;
#else
// Insert at head (original behavior)
desc->next = s_unity_tests_first;
s_unity_tests_first = desc;
#endif
} }
/* print the multiple function case name and its sub-menu /* print the multiple function case name and its sub-menu

View File

@@ -24,3 +24,53 @@ If you encounter an orphan section error during linking, you can resolve it usin
.. warning:: .. warning::
The option 3 is **not recommended**, as orphan sections may indicate misconfigured memory mapping or unintentional behavior in your application. The option 3 is **not recommended**, as orphan sections may indicate misconfigured memory mapping or unintentional behavior in your application.
Change in Global Constructor Order
----------------------------------
Initially, global constructors were executed using the internal ``do_global_ctors()``` function. This approach was used to support Xtensa targets, which emit ``.ctors.*`` sections ordered in **descending** order.
On RISC-V targets, the toolchain emits ``.init_array.*`` sections, which follow a standard **ascending** order. While priority constructors in ``.init_array.*`` were correctly processed, the non-priority ``.init_array`` section was previously handled in **descending** order and matched the Xtensa ``.ctors`` behavior.
Starting from ESP-IDF v6.0, the startup code uses ``__libc_init_array()``, consistent with standard toolchain behavior. This function processes both priority and non-priority constructors in **ascending** order.
To support this behavior, the following breaking changes were introduced:
- On Xtensa targets ``.ctors.*`` entries are now converted to ascending order to ensure compatibility with ``__libc_init_array()``.
- The processing order of non-priority ``.init_array`` and legacy ``.ctors`` sections was changed from **descending** to **ascending**.
These changes align ESP-IDF with toolchain expectations and improve consistency across supported architectures.
If the application relies on the previous (descending) constructor order and is affected by this change, consider the following approaches.
Update Constructor Registration Logic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In some cases, data structures were built assuming that constructors run in reverse order. To preserve the original behavior, update the registration logic to insert new items at the tail instead of the head.
Example (from ``components/unity/unity_runner.c``):
.. code-block:: diff
- // Insert at the head
- desc->next = s_unity_tests_first;
- s_unity_tests_first = desc;
+ // Insert at the end
+ _unity_tests_last->next = desc;
+ s_unity_tests_last = desc;
.. note::
This approach is suitable only for very specific cases.
Use Constructor Priorities
~~~~~~~~~~~~~~~~~~~~~~~~~~
To explicitly control constructor order, use the ``constructor()`` function attribute with a numeric priority:
.. code-block:: c
__attribute__((constructor(PRIO)))
void foo(void);
Replace ``PRIO`` with an integer value. Lower values are executed earlier. This is the preferred method when specific ordering is required.

View File

@@ -24,3 +24,53 @@
.. warning:: .. warning::
方案 3 **不推荐使用**,因为孤立段可能意味着内存映射配置存在问题,或应用程序中存在非预期行为。 方案 3 **不推荐使用**,因为孤立段可能意味着内存映射配置存在问题,或应用程序中存在非预期行为。
全局构造函数顺序变更
---------------------
最初ESP-IDF 使用内部函数 ``do_global_ctors()`` 执行全局构造函数,以兼容 Xtensa 架构,其编译器会生成按 **降序** 排列的 ``.ctors.*`` 段。
在 RISC-V 架构中,工具链会生成 ``.init_array.*`` 段,并采用标准的 **升序** 排列。虽然优先级构造函数(位于 ``.init_array.*``)能正确执行,但此前未指定优先级的 ``.init_array`` 段却是按 **降序** 方式处理,从而与 Xtensa 的 ``.ctors`` 行为保持一致。
从 ESP-IDF v6.0 起,启动代码改用标准工具链函数 ``__libc_init_array()``。该函数会按 **升序** 执行带优先级和不带优先级的构造函数。
因此,我们引入了以下重大变更:
- 对于 Xtensa 架构,``.ctors.*`` 项现在会被转换为升序,以确保与 ``__libc_init_array()`` 的行为兼容。
- 非优先级的 ``.init_array`` 和旧版 ``.ctors`` 段的执行顺序从 ``降序`` 改为 ``升序``
这些变更使 ESP-IDF 的行为与工具链的预期保持一致,并提升了跨架构的兼容性。
若应用程序依赖原有构造函数的执行顺序(降序),并因此受到影响,可参考以下解决方案。
更新构造函数注册逻辑
~~~~~~~~~~~~~~~~~~~~
有些数据结构是基于“构建函数按相反顺序运行”的假设来构建的。现在为了不改变原有行为,可将注册新项的逻辑从“插入头部”改为“插入尾部”。
示例(来自 ``components/unity/unity_runner.c``
.. code-block:: diff
- // 插入到链表头部
- desc->next = s_unity_tests_first;
- s_unity_tests_first = desc;
+ // 插入到链表尾部
+ _unity_tests_last->next = desc;
+ s_unity_tests_last = desc;
.. note::
此方案仅适用于特定场景。
使用构造函数优先级
~~~~~~~~~~~~~~~~~~~~~~~
若需显式控制构造函数执行顺序,可使用带数值优先级的 ``constructor()`` 函数属性:
.. code-block:: c
__attribute__((constructor(PRIO)))
void foo(void);
``PRIO`` 替换为整数值。值越小,执行越早。当对执行顺序有特定要求时,这种方式是首选。

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@@ -34,12 +34,13 @@ void example_plugin_register(const example_plugin_desc_t* plugin_desc)
} }
memcpy(&record->plugin_desc, plugin_desc, sizeof(*plugin_desc)); memcpy(&record->plugin_desc, plugin_desc, sizeof(*plugin_desc));
struct plugin_record *head = LIST_FIRST(&s_plugins_list); static struct plugin_record *tail = NULL;
if (head == NULL) { if (tail == NULL) {
LIST_INSERT_HEAD(&s_plugins_list, record, list_entry); LIST_INSERT_HEAD(&s_plugins_list, record, list_entry);
} else { } else {
LIST_INSERT_BEFORE(head, record, list_entry); LIST_INSERT_AFTER(tail, record, list_entry);
} }
tail = record;
printf("Successfully registered plugin '%s'\n", plugin_desc->name); printf("Successfully registered plugin '%s'\n", plugin_desc->name);
} }

View File

@@ -11,10 +11,10 @@ from pytest_embedded_idf.utils import idf_parametrize
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
def test_plugins(dut: Dut) -> None: def test_plugins(dut: Dut) -> None:
log_text = textwrap.dedent(r""" log_text = textwrap.dedent(r"""
Nihao plugin performing self-registration...
Successfully registered plugin 'Nihao'
Hello plugin performing self-registration... Hello plugin performing self-registration...
Successfully registered plugin 'Hello' Successfully registered plugin 'Hello'
Nihao plugin performing self-registration...
Successfully registered plugin 'Nihao'
main_task: Calling app_main() main_task: Calling app_main()
List of plugins: List of plugins:
- Plugin 'Hello' - Plugin 'Hello'

View File

@@ -61,6 +61,14 @@ tools/test_apps/system/gdb_loadable_elf:
temporary: true temporary: true
reason: target esp32c6, esp32h2 is not supported yet reason: target esp32c6, esp32h2 is not supported yet
tools/test_apps/system/init_array:
enable:
- if: INCLUDE_DEFAULT == 1 or IDF_TARGET == "linux"
depends_components:
- esp_system
depends_filepatterns:
- tools/tools.json
tools/test_apps/system/log: tools/test_apps/system/log:
disable_test: disable_test:
- if: IDF_TARGET not in ["esp32", "esp32c3"] - if: IDF_TARGET not in ["esp32", "esp32c3"]

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.16)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_init_array)

View File

@@ -0,0 +1,20 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | Linux |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | ----- |
# Historical note
Initially, ESP-IDF used the `do_global_ctors()` function to run global constructors.
This was done to accommodate Xtensa targets that emit `.ctors.*` sections, which are ordered in descending order.
For RISC-V, compilation used `.init_array.*` sections, which are designed to have ascending order.
Priority constructors in `.init_array.*` sections were correctly processed in ascending order.
However, non-priority `.init_array` section was processed in descending order (as it was done for Xtensa `.ctors`).
Starting with ESP-IDF v6.0, the implementation switched to the standard LibC behavior (`__libc_init_array()`),
which processes both priority and non-priority constructors in ascending order.
To achieve this, a breaking changes were introduced:
- Xtensa `.ctors.*` entries converted to `.init_array.*` format (ascending), to be passed to `__libc_init_array()`.
- Processing order of non-priority `.init_array` and `.ctors` sections was changed from descending to ascending.
This test ensures that the initialization order is correct and consistent between ESP-IDF and Linux targets.

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "test_app_main.c"
REQUIRES esp_system)

View File

@@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
__attribute__((constructor))
void foo(void)
{
printf("%s\n", __FUNCTION__);
}
__attribute__((constructor(103)))
void init_prio_103(void)
{
printf("%s\n", __FUNCTION__);
}
__attribute__((constructor(101)))
void init_prio_101(void)
{
printf("%s\n", __FUNCTION__);
}
__attribute__((constructor(102)))
void init_prio_102(void)
{
printf("%s\n", __FUNCTION__);
}
__attribute__((constructor))
void bar(void)
{
printf("%s\n", __FUNCTION__);
}
void preinit_func(void)
{
printf("%s\n", __FUNCTION__);
}
__attribute__((section(".preinit_array"), used))
uintptr_t test_preinit = (uintptr_t) preinit_func;
void app_main(void)
{
printf("app_main running\n");
}

View File

@@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@idf_parametrize('target', ['supported_targets', 'preview_targets', 'linux'], indirect=['target'])
def test_init_array(dut: Dut) -> None:
dut.expect_exact('preinit_func')
dut.expect_exact('init_prio_101')
dut.expect_exact('init_prio_102')
dut.expect_exact('init_prio_103')
dut.expect_exact('foo')
dut.expect_exact('bar')
dut.expect_exact('app_main running')