tests/llext: add init_fini test

Add a test to check the proper support of ELF init arrays, using
the new llext_bootstrap function in place of llext_entry in the
LLEXT test suite.

Signed-off-by: Luca Burelli <l.burelli@arduino.cc>
This commit is contained in:
Luca Burelli 2024-08-22 15:55:42 +02:00 committed by Fabio Baltieri
commit dfef264bed
3 changed files with 115 additions and 12 deletions

View file

@ -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)

View file

@ -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 <stdint.h>
#include <zephyr/llext/symbol.h>
#include <zephyr/toolchain.h>
#include <zephyr/sys/printk.h>
#include <zephyr/ztest_assert.h>
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);

View file

@ -67,16 +67,6 @@ struct llext_test {
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 */
@ -169,7 +159,8 @@ void load_call_unload(const 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);
@ -192,7 +183,8 @@ void load_call_unload(const struct llext_test *test_case)
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);
@ -210,12 +202,24 @@ void load_call_unload(const struct llext_test *test_case)
}
#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);
@ -257,6 +261,29 @@ 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"
};