diff --git a/components/esp_system/ld/esp32/sections.ld.in b/components/esp_system/ld/esp32/sections.ld.in index 930e1b4cda..22b76e32b2 100644 --- a/components/esp_system/ld/esp32/sections.ld.in +++ b/components/esp_system/ld/esp32/sections.ld.in @@ -359,8 +359,12 @@ SECTIONS * * 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) - 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(.); /* Addresses of memory regions reserved via SOC_RESERVE_MEMORY_REGION() */ diff --git a/components/esp_system/ld/esp32c2/sections.ld.in b/components/esp_system/ld/esp32c2/sections.ld.in index 8986e49940..a905dde289 100644 --- a/components/esp_system/ld/esp32c2/sections.ld.in +++ b/components/esp_system/ld/esp32c2/sections.ld.in @@ -197,20 +197,12 @@ SECTIONS * C++ constructor tables. * * 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) - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) - __init_priority_array_end = ABSOLUTE(.); - + ALIGNED_SYMBOL(4, __preinit_array_start) + KEEP (*(.preinit_array)) + __preinit_array_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, __init_array_start) + KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*))) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) __init_array_end = ABSOLUTE(.); diff --git a/components/esp_system/ld/esp32c3/sections.ld.in b/components/esp_system/ld/esp32c3/sections.ld.in index 5c84d3cd5a..e18a528dd5 100644 --- a/components/esp_system/ld/esp32c3/sections.ld.in +++ b/components/esp_system/ld/esp32c3/sections.ld.in @@ -319,20 +319,12 @@ SECTIONS * C++ constructor tables. * * 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) - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) - __init_priority_array_end = ABSOLUTE(.); - + ALIGNED_SYMBOL(4, __preinit_array_start) + KEEP (*(.preinit_array)) + __preinit_array_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, __init_array_start) + KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*))) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) __init_array_end = ABSOLUTE(.); diff --git a/components/esp_system/ld/esp32c5/sections.ld.in b/components/esp_system/ld/esp32c5/sections.ld.in index 9275091051..74bf089be3 100644 --- a/components/esp_system/ld/esp32c5/sections.ld.in +++ b/components/esp_system/ld/esp32c5/sections.ld.in @@ -355,20 +355,12 @@ SECTIONS * C++ constructor tables. * * 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) - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) - __init_priority_array_end = ABSOLUTE(.); - + ALIGNED_SYMBOL(4, __preinit_array_start) + KEEP (*(.preinit_array)) + __preinit_array_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, __init_array_start) + KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*))) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) __init_array_end = ABSOLUTE(.); diff --git a/components/esp_system/ld/esp32c6/sections.ld.in b/components/esp_system/ld/esp32c6/sections.ld.in index f2b2df7926..01edda27ee 100644 --- a/components/esp_system/ld/esp32c6/sections.ld.in +++ b/components/esp_system/ld/esp32c6/sections.ld.in @@ -364,20 +364,12 @@ SECTIONS * C++ constructor tables. * * 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) - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) - __init_priority_array_end = ABSOLUTE(.); - + ALIGNED_SYMBOL(4, __preinit_array_start) + KEEP (*(.preinit_array)) + __preinit_array_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, __init_array_start) + KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*))) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) __init_array_end = ABSOLUTE(.); diff --git a/components/esp_system/ld/esp32c61/sections.ld.in b/components/esp_system/ld/esp32c61/sections.ld.in index e6c48b5452..daac666f97 100644 --- a/components/esp_system/ld/esp32c61/sections.ld.in +++ b/components/esp_system/ld/esp32c61/sections.ld.in @@ -213,20 +213,13 @@ SECTIONS * C++ constructor tables. * * 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) - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) - __init_priority_array_end = ABSOLUTE(.); + ALIGNED_SYMBOL(4, __preinit_array_start) + KEEP (*(.preinit_array)) + __preinit_array_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, __init_array_start) + KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*))) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) __init_array_end = ABSOLUTE(.); diff --git a/components/esp_system/ld/esp32h2/sections.ld.in b/components/esp_system/ld/esp32h2/sections.ld.in index be973ed63c..0fd235f04d 100644 --- a/components/esp_system/ld/esp32h2/sections.ld.in +++ b/components/esp_system/ld/esp32h2/sections.ld.in @@ -366,20 +366,12 @@ SECTIONS * C++ constructor tables. * * 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) - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) - __init_priority_array_end = ABSOLUTE(.); - + ALIGNED_SYMBOL(4, __preinit_array_start) + KEEP (*(.preinit_array)) + __preinit_array_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, __init_array_start) + KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*))) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) __init_array_end = ABSOLUTE(.); diff --git a/components/esp_system/ld/esp32h21/sections.ld.in b/components/esp_system/ld/esp32h21/sections.ld.in index dc628feeff..d2227359f6 100644 --- a/components/esp_system/ld/esp32h21/sections.ld.in +++ b/components/esp_system/ld/esp32h21/sections.ld.in @@ -355,20 +355,12 @@ SECTIONS * C++ constructor tables. * * 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) - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) - __init_priority_array_end = ABSOLUTE(.); - + ALIGNED_SYMBOL(4, __preinit_array_start) + KEEP (*(.preinit_array)) + __preinit_array_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, __init_array_start) + KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*))) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) __init_array_end = ABSOLUTE(.); diff --git a/components/esp_system/ld/esp32h4/sections.ld.in b/components/esp_system/ld/esp32h4/sections.ld.in index 95eeef9be4..fa715e3a23 100644 --- a/components/esp_system/ld/esp32h4/sections.ld.in +++ b/components/esp_system/ld/esp32h4/sections.ld.in @@ -200,20 +200,12 @@ SECTIONS * C++ constructor tables. * * 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) - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) - __init_priority_array_end = ABSOLUTE(.); - + ALIGNED_SYMBOL(4, __preinit_array_start) + KEEP (*(.preinit_array)) + __preinit_array_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, __init_array_start) + KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*))) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) __init_array_end = ABSOLUTE(.); diff --git a/components/esp_system/ld/esp32p4/sections.ld.in b/components/esp_system/ld/esp32p4/sections.ld.in index b062587f7c..be51ae5318 100644 --- a/components/esp_system/ld/esp32p4/sections.ld.in +++ b/components/esp_system/ld/esp32p4/sections.ld.in @@ -377,20 +377,12 @@ SECTIONS * C++ constructor tables. * * 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) - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*)) - __init_priority_array_end = ABSOLUTE(.); - + ALIGNED_SYMBOL(4, __preinit_array_start) + KEEP (*(.preinit_array)) + __preinit_array_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, __init_array_start) + KEEP (*(SORT_BY_INIT_PRIORITY(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array.*))) KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .init_array)) __init_array_end = ABSOLUTE(.); diff --git a/components/esp_system/ld/esp32s2/sections.ld.in b/components/esp_system/ld/esp32s2/sections.ld.in index f0df70ac03..1832712fd4 100644 --- a/components/esp_system/ld/esp32s2/sections.ld.in +++ b/components/esp_system/ld/esp32s2/sections.ld.in @@ -362,8 +362,12 @@ SECTIONS * * 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) - 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(.); /* Addresses of memory regions reserved via SOC_RESERVE_MEMORY_REGION() */ diff --git a/components/esp_system/ld/esp32s3/sections.ld.in b/components/esp_system/ld/esp32s3/sections.ld.in index c6649ba3a6..26abf1fa66 100644 --- a/components/esp_system/ld/esp32s3/sections.ld.in +++ b/components/esp_system/ld/esp32s3/sections.ld.in @@ -373,8 +373,12 @@ SECTIONS * * 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) - 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(.); /* Addresses of memory regions reserved via SOC_RESERVE_MEMORY_REGION() */ diff --git a/components/esp_system/startup.c b/components/esp_system/startup.c index 98daf5498c..0d957a207b 100644 --- a/components/esp_system/startup.c +++ b/components/esp_system/startup.c @@ -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 */ @@ -58,54 +58,26 @@ const sys_startup_fn_t g_startup_fn[1] = { start_cpu0 }; 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 +/** + * @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 { - long placeholder[ 10 ]; + long placeholder[10]; }; void __register_frame_info(const void *begin, struct object * ob); extern char __eh_frame[]; static struct object ob; __register_frame_info(__eh_frame, &ob); -#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") - } +#endif // CONFIG_COMPILER_CXX_EXCEPTIONS /** * @brief Call component init functions defined using ESP_SYSTEM_INIT_Fn macros. @@ -199,12 +171,16 @@ static void do_secondary_init(void) static void start_cpu0_default(void) { + extern void __libc_init_array(void); // Initialize core components and services. // Operations that needs the cache to be disabled have to be done here. do_core_init(); // Execute constructors. - do_global_ctors(); +#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS + __do_global_ctors_1(); +#endif + __libc_init_array(); /* ----------------------------------Separator----------------------------- * After this stage, other CPU start running with the cache, however the scheduler (and ipc service) is not available. diff --git a/components/unity/Kconfig b/components/unity/Kconfig index 19d11a3e96..72952c1e53 100644 --- a/components/unity/Kconfig +++ b/components/unity/Kconfig @@ -58,18 +58,4 @@ menu "Unity unit testing library" jumping back to the test menu. The jumping is usually occurs in assert 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" diff --git a/components/unity/test_apps/sdkconfig.defaults b/components/unity/test_apps/sdkconfig.defaults index 43fdc0f587..e4bfc208a5 100644 --- a/components/unity/test_apps/sdkconfig.defaults +++ b/components/unity/test_apps/sdkconfig.defaults @@ -1,2 +1 @@ CONFIG_ESP_TASK_WDT_EN=n -CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE=y diff --git a/components/unity/unity_runner.c b/components/unity/unity_runner.c index eaea5710e5..a809f274ea 100644 --- a/components/unity/unity_runner.c +++ b/components/unity/unity_runner.c @@ -27,35 +27,9 @@ void unity_testcase_register(test_desc_t *desc) s_unity_tests_last = desc; 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 - prev->next = desc; + s_unity_tests_last->next = 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 diff --git a/docs/en/migration-guides/release-6.x/6.0/build-system.rst b/docs/en/migration-guides/release-6.x/6.0/build-system.rst index 1b6f2dd699..0ace3c6d95 100644 --- a/docs/en/migration-guides/release-6.x/6.0/build-system.rst +++ b/docs/en/migration-guides/release-6.x/6.0/build-system.rst @@ -24,3 +24,53 @@ If you encounter an orphan section error during linking, you can resolve it usin .. warning:: 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. diff --git a/examples/build_system/cmake/plugins/components/plugins/plugins.c b/examples/build_system/cmake/plugins/components/plugins/plugins.c index 2bcead441a..f84a7eca77 100644 --- a/examples/build_system/cmake/plugins/components/plugins/plugins.c +++ b/examples/build_system/cmake/plugins/components/plugins/plugins.c @@ -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 */ @@ -34,12 +34,13 @@ void example_plugin_register(const example_plugin_desc_t* plugin_desc) } memcpy(&record->plugin_desc, plugin_desc, sizeof(*plugin_desc)); - struct plugin_record *head = LIST_FIRST(&s_plugins_list); - if (head == NULL) { + static struct plugin_record *tail = NULL; + if (tail == NULL) { LIST_INSERT_HEAD(&s_plugins_list, record, list_entry); } 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); } diff --git a/examples/build_system/cmake/plugins/pytest_plugins.py b/examples/build_system/cmake/plugins/pytest_plugins.py index 61fec11187..965c28a30a 100644 --- a/examples/build_system/cmake/plugins/pytest_plugins.py +++ b/examples/build_system/cmake/plugins/pytest_plugins.py @@ -11,10 +11,10 @@ from pytest_embedded_idf.utils import idf_parametrize @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_plugins(dut: Dut) -> None: log_text = textwrap.dedent(r""" - Nihao plugin performing self-registration... - Successfully registered plugin 'Nihao' Hello plugin performing self-registration... Successfully registered plugin 'Hello' + Nihao plugin performing self-registration... + Successfully registered plugin 'Nihao' main_task: Calling app_main() List of plugins: - Plugin 'Hello' diff --git a/tools/test_apps/system/.build-test-rules.yml b/tools/test_apps/system/.build-test-rules.yml index 9fc3fac138..01740f17f1 100644 --- a/tools/test_apps/system/.build-test-rules.yml +++ b/tools/test_apps/system/.build-test-rules.yml @@ -61,6 +61,14 @@ tools/test_apps/system/gdb_loadable_elf: temporary: true 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: disable_test: - if: IDF_TARGET not in ["esp32", "esp32c3"] diff --git a/tools/test_apps/system/init_array/CMakeLists.txt b/tools/test_apps/system/init_array/CMakeLists.txt new file mode 100644 index 0000000000..168278a6e2 --- /dev/null +++ b/tools/test_apps/system/init_array/CMakeLists.txt @@ -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) diff --git a/tools/test_apps/system/init_array/README.md b/tools/test_apps/system/init_array/README.md new file mode 100644 index 0000000000..18be58dda5 --- /dev/null +++ b/tools/test_apps/system/init_array/README.md @@ -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. diff --git a/tools/test_apps/system/init_array/main/CMakeLists.txt b/tools/test_apps/system/init_array/main/CMakeLists.txt new file mode 100644 index 0000000000..8dcbb3a7c3 --- /dev/null +++ b/tools/test_apps/system/init_array/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "test_app_main.c" + REQUIRES esp_system) diff --git a/tools/test_apps/system/init_array/main/test_app_main.c b/tools/test_apps/system/init_array/main/test_app_main.c new file mode 100644 index 0000000000..545e9800c1 --- /dev/null +++ b/tools/test_apps/system/init_array/main/test_app_main.c @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include + +__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"); +} diff --git a/tools/test_apps/system/init_array/pytest_init_array.py b/tools/test_apps/system/init_array/pytest_init_array.py new file mode 100644 index 0000000000..fe367c7998 --- /dev/null +++ b/tools/test_apps/system/init_array/pytest_init_array.py @@ -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')