diff --git a/doc/services/llext/load.rst b/doc/services/llext/load.rst index 0898cb3a40e77..78fc897b87d43 100644 --- a/doc/services/llext/load.rst +++ b/doc/services/llext/load.rst @@ -24,6 +24,32 @@ successfully, the extension is loaded into memory and is ready to be used. included in any user memory domain. To allow access from user mode, the :c:func:`llext_add_domain` function must be called. +Initializing and cleaning up the extension +========================================== + +The extension may define a number of initialization functions that must be +called after loading but before any function in it can be used; this is typical +in languages such as C++ that provide the concept of object constructors. The +same is true for cleanup functions that must be called before unloading the +extension. + +LLEXT supports calling the functions listed in the ``.preinit_array`` and +``.init_array`` sections of the ELF file with the :c:func:`llext_bringup` +function, and the functions listed in the ``.fini_array`` section with the +:c:func:`llext_teardown` function. These APIs are compatible with +:ref:`User Mode `, and thus can be called from either kernel or +user context. + +.. important:: + The code run by these functions is fully determined by the contents of the + ELF file. This may have security implications if its origin is untrusted. + +If the extension requires a dedicated thread, the :c:func:`llext_bootstrap` +function can be used to minimize boilerplate code. This function has a +signature that is compatible with the :c:func:`k_thread_create` API, and will +call :c:func:`llext_bringup`, then a user-specified function in the same +context, and finally :c:func:`llext_teardown` before returning. + Accessing code and data ======================= diff --git a/include/zephyr/llext/elf.h b/include/zephyr/llext/elf.h index ca9f498a49ac0..7230d13181b72 100644 --- a/include/zephyr/llext/elf.h +++ b/include/zephyr/llext/elf.h @@ -206,6 +206,9 @@ struct elf64_shdr { #define SHT_NOBITS 0x8 /**< Program data with no file image */ #define SHT_REL 0x9 /**< Relocation entries without addends */ #define SHT_DYNSYM 0xB /**< Dynamic linking symbol table */ +#define SHT_INIT_ARRAY 0xe /**< Array of pointers to init functions */ +#define SHT_FINI_ARRAY 0xf /**< Array of pointers to termination functions */ +#define SHT_PREINIT_ARRAY 0x10 /**< Array of pointers to early init functions */ /** ELF section flags */ #define SHF_WRITE 0x1 /**< Section is writable */ diff --git a/include/zephyr/llext/llext.h b/include/zephyr/llext/llext.h index 9256f1db8a65d..2aa588f8243c6 100644 --- a/include/zephyr/llext/llext.h +++ b/include/zephyr/llext/llext.h @@ -50,6 +50,9 @@ enum llext_mem { LLEXT_MEM_SYMTAB, /**< Symbol table */ LLEXT_MEM_STRTAB, /**< Symbol name strings */ LLEXT_MEM_SHSTRTAB, /**< Section name strings */ + LLEXT_MEM_PREINIT, /**< Array of early setup functions */ + LLEXT_MEM_INIT, /**< Array of setup functions */ + LLEXT_MEM_FINI, /**< Array of cleanup functions */ LLEXT_MEM_COUNT, /**< Number of regions managed by LLEXT */ }; @@ -183,6 +186,74 @@ int llext_load(struct llext_loader *loader, const char *name, struct llext **ext */ int llext_unload(struct llext **ext); +/** @brief Entry point function signature for an extension. */ +typedef void (*llext_entry_fn_t)(void *user_data); + +/** + * @brief Calls bringup functions for an extension. + * + * Must be called before accessing any symbol in the extension. Will execute + * the extension's own setup functions in the caller context. + * @see llext_bootstrap + * + * @param[in] ext Extension to initialize. + * @returns 0 on success, or a negative error code. + * @retval -EFAULT A relocation issue was detected + */ +int llext_bringup(struct llext *ext); + +/** + * @brief Calls teardown functions for an extension. + * + * Will execute the extension's own cleanup functions in the caller context. + * After this function completes, the extension is no longer usable and must be + * fully unloaded with @ref llext_unload. + * @see llext_bootstrap + * + * @param[in] ext Extension to de-initialize. + * @returns 0 on success, or a negative error code. + * @retval -EFAULT A relocation issue was detected + */ +int llext_teardown(struct llext *ext); + +/** + * @brief Bring up, execute, and teardown an extension. + * + * Calls the extension's own setup functions, an additional entry point and + * the extension's cleanup functions in the current thread context. + * + * This is a convenient wrapper around @ref llext_bringup and @ref + * llext_teardown that matches the @ref k_thread_entry_t signature, so it can + * be directly started as a new user or kernel thread via @ref k_thread_create. + * + * @param[in] ext Extension to execute. Passed as `p1` in @ref k_thread_create. + * @param[in] entry_fn Main entry point of the thread after performing + * extension setup. Passed as `p2` in @ref k_thread_create. + * @param[in] user_data Argument passed to @a entry_fn. Passed as `p3` in + * @ref k_thread_create. + */ +void llext_bootstrap(struct llext *ext, llext_entry_fn_t entry_fn, void *user_data); + +/** + * @brief Get pointers to setup or cleanup functions for an extension. + * + * This syscall can be used to get the addresses of all the functions that + * have to be called for full extension setup or cleanup. + * + * @see llext_bootstrap + * + * @param[in] ext Extension to initialize. + * @param[in] is_init `true` to get functions to be called at setup time, + * `false` to get the cleanup ones. + * @param[inout] buf Buffer to store the function pointers in. Can be `NULL` + * to only get the minimum required size. + * @param[in] size Allocated size of the buffer in bytes. + * @returns the size used by the array in bytes, or a negative error code. + * @retval -EFAULT A relocation issue was detected + * @retval -ENOMEM Array does not fit in the allocated buffer + */ +__syscall ssize_t llext_get_fn_table(struct llext *ext, bool is_init, void *buf, size_t size); + /** * @brief Find the address for an arbitrary symbol. * @@ -252,8 +323,8 @@ int arch_elf_relocate(elf_rela_t *rel, uintptr_t loc, * * Searches for a section by name in the ELF file and returns its offset. * - * @param loader Extension loader data and context - * @param search_name Section name to search for + * @param[in] loader Extension loader data and context + * @param[in] search_name Section name to search for * @returns the section offset or a negative error code */ ssize_t llext_find_section(struct llext_loader *loader, const char *search_name); @@ -278,4 +349,6 @@ void arch_elf_relocate_local(struct llext_loader *loader, struct llext *ext, } #endif +#include + #endif /* ZEPHYR_LLEXT_H */ diff --git a/subsys/llext/CMakeLists.txt b/subsys/llext/CMakeLists.txt index 415e012a3d2f7..870216401ef8d 100644 --- a/subsys/llext/CMakeLists.txt +++ b/subsys/llext/CMakeLists.txt @@ -1,4 +1,6 @@ if(CONFIG_LLEXT) + zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/llext/llext.h) + zephyr_library() # For strnlen() @@ -10,6 +12,7 @@ if(CONFIG_LLEXT) llext_load.c llext_link.c llext_export.c + llext_handlers.c buf_loader.c ) zephyr_library_sources_ifdef(CONFIG_LLEXT_SHELL shell.c) diff --git a/subsys/llext/llext.c b/subsys/llext/llext.c index 9e8880d5bc7db..8d25198726ae4 100644 --- a/subsys/llext/llext.c +++ b/subsys/llext/llext.c @@ -273,3 +273,66 @@ int llext_call_fn(struct llext *ext, const char *sym_name) return 0; } + +static int call_fn_table(struct llext *ext, bool is_init) +{ + ssize_t ret; + + ret = llext_get_fn_table(ext, is_init, NULL, 0); + if (ret < 0) { + LOG_ERR("Failed to get table size: %d", (int)ret); + return ret; + } + + typedef void (*elf_void_fn_t)(void); + + int fn_count = ret / sizeof(elf_void_fn_t); + elf_void_fn_t fn_table[fn_count]; + + ret = llext_get_fn_table(ext, is_init, &fn_table, sizeof(fn_table)); + if (ret < 0) { + LOG_ERR("Failed to get function table: %d", (int)ret); + return ret; + } + + for (int i = 0; i < fn_count; i++) { + LOG_DBG("calling %s function %p()", + is_init ? "bringup" : "teardown", fn_table[i]); + fn_table[i](); + } + + return 0; +} + +inline int llext_bringup(struct llext *ext) +{ + return call_fn_table(ext, true); +} + +inline int llext_teardown(struct llext *ext) +{ + return call_fn_table(ext, false); +} + +void llext_bootstrap(struct llext *ext, llext_entry_fn_t entry_fn, void *user_data) +{ + int ret; + + /* Call initialization functions */ + ret = llext_bringup(ext); + if (ret < 0) { + LOG_ERR("Failed to call init functions: %d", ret); + return; + } + + /* Start extension main function */ + LOG_DBG("calling entry function %p(%p)", entry_fn, user_data); + entry_fn(user_data); + + /* Call de-initialization functions */ + ret = llext_teardown(ext); + if (ret < 0) { + LOG_ERR("Failed to call de-init functions: %d", ret); + return; + } +} diff --git a/subsys/llext/llext_handlers.c b/subsys/llext/llext_handlers.c new file mode 100644 index 0000000000000..c79af8383e2c2 --- /dev/null +++ b/subsys/llext/llext_handlers.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL); + +#include "llext_priv.h" + +ssize_t z_impl_llext_get_fn_table(struct llext *ext, bool is_init, void *buf, size_t buf_size) +{ + size_t table_size; + + if (!ext) { + return -EINVAL; + } + + if (is_init) { + table_size = ext->mem_size[LLEXT_MEM_PREINIT] + + ext->mem_size[LLEXT_MEM_INIT]; + } else { + table_size = ext->mem_size[LLEXT_MEM_FINI]; + } + + if (buf) { + char *byte_ptr = buf; + + if (buf_size < table_size) { + return -ENOMEM; + } + + if (is_init) { + /* setup functions from preinit_array and init_array */ + memcpy(byte_ptr, + ext->mem[LLEXT_MEM_PREINIT], ext->mem_size[LLEXT_MEM_PREINIT]); + memcpy(byte_ptr + ext->mem_size[LLEXT_MEM_PREINIT], + ext->mem[LLEXT_MEM_INIT], ext->mem_size[LLEXT_MEM_INIT]); + } else { + /* cleanup functions from fini_array */ + memcpy(byte_ptr, + ext->mem[LLEXT_MEM_FINI], ext->mem_size[LLEXT_MEM_FINI]); + } + + /* Sanity check: pointers in this table must map inside the + * text region of the extension. If this fails, something went + * wrong during the relocation process. + * Using "char *" for these simplifies pointer arithmetic. + */ + const char *text_start = ext->mem[LLEXT_MEM_TEXT]; + const char *text_end = text_start + ext->mem_size[LLEXT_MEM_TEXT]; + const char **fn_ptrs = buf; + + for (int i = 0; i < table_size / sizeof(void *); i++) { + if (fn_ptrs[i] < text_start || fn_ptrs[i] >= text_end) { + LOG_ERR("init function %i (%p) outside text region", + i, fn_ptrs[i]); + return -EFAULT; + } + } + } + + return table_size; +} + +#ifdef CONFIG_USERSPACE + +static int ext_is_valid(struct llext *ext, void *arg) +{ + return ext == arg; +} + +static inline ssize_t z_vrfy_llext_get_fn_table(struct llext *ext, bool is_init, + void *buf, size_t size) +{ + /* Test that ext matches a loaded extension */ + K_OOPS(llext_iterate(ext_is_valid, ext) == 0); + + if (buf) { + /* Test that buf is a valid user-accessible pointer */ + K_OOPS(K_SYSCALL_MEMORY_WRITE(buf, size)); + } + + return z_impl_llext_get_fn_table(ext, is_init, buf, size); +} +#include + +#endif /* CONFIG_USERSPACE */ diff --git a/subsys/llext/llext_load.c b/subsys/llext/llext_load.c index fcc26210ebcfc..28574806a9579 100644 --- a/subsys/llext/llext_load.c +++ b/subsys/llext/llext_load.c @@ -208,6 +208,12 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext) name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr->sh_name); + if (ldr->sect_map[i].mem_idx != LLEXT_MEM_COUNT) { + LOG_DBG("section %d name %s already mapped to region %d", + i, name, ldr->sect_map[i].mem_idx); + continue; + } + /* Identify the section type by its flags */ enum llext_mem mem_idx; @@ -224,6 +230,15 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext) mem_idx = LLEXT_MEM_RODATA; } break; + case SHT_PREINIT_ARRAY: + mem_idx = LLEXT_MEM_PREINIT; + break; + case SHT_INIT_ARRAY: + mem_idx = LLEXT_MEM_INIT; + break; + case SHT_FINI_ARRAY: + mem_idx = LLEXT_MEM_FINI; + break; default: mem_idx = LLEXT_MEM_COUNT; break; @@ -241,6 +256,19 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext) continue; } + switch (mem_idx) { + case LLEXT_MEM_PREINIT: + case LLEXT_MEM_INIT: + case LLEXT_MEM_FINI: + if (shdr->sh_entsize != sizeof(void *) || + shdr->sh_size % shdr->sh_entsize != 0) { + LOG_ERR("Invalid %s array in section %d", name, i); + return -ENOEXEC; + } + default: + break; + } + LOG_DBG("section %d name %s maps to region %d", i, name, mem_idx); ldr->sect_map[i].mem_idx = mem_idx; @@ -261,7 +289,9 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext) return -ENOEXEC; } - if (mem_idx == LLEXT_MEM_BSS) { + /* Check if this region type is extendable */ + switch (mem_idx) { + case LLEXT_MEM_BSS: /* SHT_NOBITS sections cannot be merged properly: * as they use no space in the file, the logic * below does not work; they must be treated as @@ -269,6 +299,16 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext) */ LOG_ERR("Multiple SHT_NOBITS sections are not supported"); return -ENOTSUP; + case LLEXT_MEM_PREINIT: + case LLEXT_MEM_INIT: + case LLEXT_MEM_FINI: + /* These regions are not extendable and must be + * referenced at most once in the ELF file. + */ + LOG_ERR("Region %d redefined", mem_idx); + return -ENOEXEC; + default: + break; } if (ldr->hdr.e_type == ET_DYN) { diff --git a/tests/subsys/llext/simple/CMakeLists.txt b/tests/subsys/llext/simple/CMakeLists.txt index 8db40dfb10e40..b38f66c9a3a33 100644 --- a/tests/subsys/llext/simple/CMakeLists.txt +++ b/tests/subsys/llext/simple/CMakeLists.txt @@ -31,6 +31,11 @@ if (CONFIG_LLEXT_STORAGE_WRITABLE) list(APPEND ext_names find_section) endif() +if (NOT CONFIG_LLEXT_TYPE_ELF_SHAREDLIB) + # ELF shared libraries do not support init sections + list(APPEND ext_names init_fini) +endif() + # generate extension targets foreach extension given by 'ext_names' foreach(ext_name ${ext_names}) set(ext_src ${PROJECT_SOURCE_DIR}/src/${ext_name}_ext.c) diff --git a/tests/subsys/llext/simple/src/init_fini_ext.c b/tests/subsys/llext/simple/src/init_fini_ext.c new file mode 100644 index 0000000000000..13cd4e741d7db --- /dev/null +++ b/tests/subsys/llext/simple/src/init_fini_ext.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This test checks for proper support of ELF init arrays. This processing is + * performed by llext_bootstrap(), which gets the array of function pointers + * from LLEXT via the llext_get_fn_table() syscall. + * + * Each function in this test shifts the number left by 4 bits and sets the + * lower 4 bits to a specific value. The proper init sequence (preinit_fn_1, + * preinit_fn_2, init_fn) would leave number set to 0x123; the termination + * function will further shift the number to 0x1234. If a different result is + * detected, then either not all routines were executed, or their order was not + * correct. + */ + +#include +#include +#include +#include +#include + +static int number; +EXPORT_SYMBOL(number); + +static void preinit_fn_1(void) +{ + number = 1; +} + +static void preinit_fn_2(void) +{ + number <<= 4; + number |= 2; +} + +static void init_fn(void) +{ + number <<= 4; + number |= 3; +} + +static void fini_fn(void) +{ + number <<= 4; + number |= 4; +} + +static const void *const preinit_fn_ptrs[] __used Z_GENERIC_SECTION(".preinit_array") = { + preinit_fn_1, + preinit_fn_2 +}; +static const void *const init_fn_ptrs[] __used Z_GENERIC_SECTION(".init_array") = { + init_fn +}; +static const void *const fini_fn_ptrs[] __used Z_GENERIC_SECTION(".fini_array") = { + fini_fn +}; + +void test_entry(void) +{ + /* fini_fn() is not called yet, so we expect 0x123 here */ + const int expected = (((1 << 4) | 2) << 4) | 3; + + zassert_equal(number, expected, "got 0x%x instead of 0x%x during test", + number, expected); +} +EXPORT_SYMBOL(test_entry); diff --git a/tests/subsys/llext/simple/src/test_llext_simple.c b/tests/subsys/llext/simple/src/test_llext_simple.c index 4646c9b5519ad..728a0b7889dea 100644 --- a/tests/subsys/llext/simple/src/test_llext_simple.c +++ b/tests/subsys/llext/simple/src/test_llext_simple.c @@ -40,28 +40,27 @@ LOG_MODULE_REGISTER(test_llext_simple); struct llext_test { const char *name; - bool try_userspace; - size_t buf_len; LLEXT_CONST uint8_t *buf; + size_t buf_len; + + bool kernel_only; - void (*perm_setup)(struct k_thread *llext_thread); + /* + * Optional callbacks + */ + + /* Called in kernel context before each test starts */ + void (*test_setup)(struct llext *ext, struct k_thread *llext_thread); + + /* Called in kernel context after each test completes */ + void (*test_cleanup)(struct llext *ext); }; K_THREAD_STACK_DEFINE(llext_stack, 1024); struct k_thread llext_thread; -#ifdef CONFIG_USERSPACE -void llext_entry(void *arg0, void *arg1, void *arg2) -{ - void (*fn)(void) = arg0; - - LOG_INF("calling fn %p from thread %p", fn, k_current_get()); - fn(); -} -#endif /* CONFIG_USERSPACE */ - /* syscalls test */ @@ -93,8 +92,12 @@ K_THREAD_STACK_DEFINE(my_thread_stack, MY_THREAD_STACK_SIZE); EXPORT_SYMBOL(my_thread_stack); #ifdef CONFIG_USERSPACE -/* Allow the user space test thread to access global objects */ -static void threads_objects_perm_setup(struct k_thread *llext_thread) +/* Allow the test threads to access global objects. + * Note: Permissions on objects used in the test by this thread are initialized + * even in supervisor mode, so that user mode descendant threads can inherit + * these permissions. + */ +static void threads_objects_test_setup(struct llext *, struct k_thread *llext_thread) { k_object_access_grant(&my_sem, llext_thread); k_object_access_grant(&my_thread, llext_thread); @@ -102,10 +105,10 @@ static void threads_objects_perm_setup(struct k_thread *llext_thread) } #else /* No need to set up permissions for supervisor mode */ -#define threads_objects_perm_setup NULL +#define threads_objects_test_setup NULL #endif /* CONFIG_USERSPACE */ -void load_call_unload(struct llext_test *test_case) +void load_call_unload(const struct llext_test *test_case) { struct llext_buf_loader buf_loader = LLEXT_BUF_LOADER(test_case->buf, test_case->buf_len); @@ -150,45 +153,71 @@ void load_call_unload(struct llext_test *test_case) /* Should be runnable from newly created thread */ k_thread_create(&llext_thread, llext_stack, K_THREAD_STACK_SIZEOF(llext_stack), - &llext_entry, test_entry_fn, NULL, NULL, + (k_thread_entry_t) &llext_bootstrap, + ext, test_entry_fn, NULL, 1, 0, K_FOREVER); k_mem_domain_add_thread(&domain, &llext_thread); - /* Even in supervisor mode, initialize permissions on objects used in - * the test by this thread, so that user mode descendant threads can - * inherit these permissions. - */ - if (test_case->perm_setup) { - test_case->perm_setup(&llext_thread); + if (test_case->test_setup) { + test_case->test_setup(ext, &llext_thread); } k_thread_start(&llext_thread); k_thread_join(&llext_thread, K_FOREVER); + if (test_case->test_cleanup) { + test_case->test_cleanup(ext); + } + /* Some extensions may wish to be tried from the context * of a userspace thread along with the usual supervisor context * tried above. */ - if (test_case->try_userspace) { + if (!test_case->kernel_only) { k_thread_create(&llext_thread, llext_stack, K_THREAD_STACK_SIZEOF(llext_stack), - &llext_entry, test_entry_fn, NULL, NULL, + (k_thread_entry_t) &llext_bootstrap, + ext, test_entry_fn, NULL, 1, K_USER, K_FOREVER); k_mem_domain_add_thread(&domain, &llext_thread); - if (test_case->perm_setup) { - test_case->perm_setup(&llext_thread); + if (test_case->test_setup) { + test_case->test_setup(ext, &llext_thread); } k_thread_start(&llext_thread); k_thread_join(&llext_thread, K_FOREVER); + + if (test_case->test_cleanup) { + test_case->test_cleanup(ext); + } } #else /* CONFIG_USERSPACE */ + /* No userspace support: run the test only in supervisor mode, without + * creating a new thread. + */ + if (test_case->test_setup) { + test_case->test_setup(ext, NULL); + } + +#ifdef CONFIG_LLEXT_TYPE_ELF_SHAREDLIB + /* The ELF specification forbids shared libraries from defining init + * entries, so calling llext_bootstrap here would be redundant. Use + * this opportunity to test llext_call_fn, even though llext_bootstrap + * would have behaved simlarly. + */ zassert_ok(llext_call_fn(ext, "test_entry"), "test_entry call should succeed"); +#else /* !USERSPACE && !SHAREDLIB */ + llext_bootstrap(ext, test_entry_fn, NULL); +#endif + + if (test_case->test_cleanup) { + test_case->test_cleanup(ext); + } #endif /* CONFIG_USERSPACE */ llext_unload(&ext); @@ -200,17 +229,16 @@ void load_call_unload(struct llext_test *test_case) * unloading each extension which may itself excercise various APIs provided by * Zephyr. */ -#define LLEXT_LOAD_UNLOAD(_name, _userspace, _perm_setup) \ - ZTEST(llext, test_load_unload_##_name) \ - { \ - struct llext_test test_case = { \ - .name = STRINGIFY(_name), \ - .try_userspace = _userspace, \ - .buf_len = ARRAY_SIZE(_name ## _ext), \ - .buf = _name ## _ext, \ - .perm_setup = _perm_setup, \ - }; \ - load_call_unload(&test_case); \ +#define LLEXT_LOAD_UNLOAD(_name, extra_args...) \ + ZTEST(llext, test_load_unload_##_name) \ + { \ + const struct llext_test test_case = { \ + .name = STRINGIFY(_name), \ + .buf = _name ## _ext, \ + .buf_len = ARRAY_SIZE(_name ## _ext), \ + extra_args \ + }; \ + load_call_unload(&test_case); \ } /* @@ -223,40 +251,67 @@ void load_call_unload(struct llext_test *test_case) static LLEXT_CONST uint8_t hello_world_ext[] ELF_ALIGN = { #include "hello_world.inc" }; -LLEXT_LOAD_UNLOAD(hello_world, false, NULL) +LLEXT_LOAD_UNLOAD(hello_world, + .kernel_only = true +) + +#ifndef CONFIG_LLEXT_TYPE_ELF_SHAREDLIB +static LLEXT_CONST uint8_t init_fini_ext[] ELF_ALIGN = { + #include "init_fini.inc" +}; + +static void init_fini_test_cleanup(struct llext *ext) +{ + /* Make sure fini_fn() was called during teardown. + * (see init_fini_ext.c for more details). + */ + const int *number = llext_find_sym(&ext->exp_tab, "number"); + const int expected = (((((1 << 4) | 2) << 4) | 3) << 4) | 4; /* 0x1234 */ + + zassert_not_null(number, "number should be an exported symbol"); + zassert_equal(*number, expected, "got 0x%x instead of 0x%x during cleanup", + *number, expected); +} + +LLEXT_LOAD_UNLOAD(init_fini, + .test_cleanup = init_fini_test_cleanup +) +#endif static LLEXT_CONST uint8_t logging_ext[] ELF_ALIGN = { #include "logging.inc" }; -LLEXT_LOAD_UNLOAD(logging, true, NULL) +LLEXT_LOAD_UNLOAD(logging) static LLEXT_CONST uint8_t relative_jump_ext[] ELF_ALIGN = { #include "relative_jump.inc" }; -LLEXT_LOAD_UNLOAD(relative_jump, true, NULL) +LLEXT_LOAD_UNLOAD(relative_jump) static LLEXT_CONST uint8_t object_ext[] ELF_ALIGN = { #include "object.inc" }; -LLEXT_LOAD_UNLOAD(object, true, NULL) +LLEXT_LOAD_UNLOAD(object) #ifndef CONFIG_LLEXT_TYPE_ELF_RELOCATABLE static LLEXT_CONST uint8_t syscalls_ext[] ELF_ALIGN = { #include "syscalls.inc" }; -LLEXT_LOAD_UNLOAD(syscalls, true, NULL) +LLEXT_LOAD_UNLOAD(syscalls) static LLEXT_CONST uint8_t threads_kernel_objects_ext[] ELF_ALIGN = { #include "threads_kernel_objects.inc" }; -LLEXT_LOAD_UNLOAD(threads_kernel_objects, true, threads_objects_perm_setup) +LLEXT_LOAD_UNLOAD(threads_kernel_objects, + .test_setup = threads_objects_test_setup, +) #endif #ifndef CONFIG_LLEXT_TYPE_ELF_OBJECT static LLEXT_CONST uint8_t multi_file_ext[] ELF_ALIGN = { #include "multi_file.inc" }; -LLEXT_LOAD_UNLOAD(multi_file, true, NULL) +LLEXT_LOAD_UNLOAD(multi_file) #endif #if defined(CONFIG_LLEXT_TYPE_ELF_RELOCATABLE) && defined(CONFIG_XTENSA)