Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions doc/services/llext/load.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <usermode_api>`, 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
=======================

Expand Down
3 changes: 3 additions & 0 deletions include/zephyr/llext/elf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
77 changes: 75 additions & 2 deletions include/zephyr/llext/llext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
};
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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);
Expand All @@ -278,4 +349,6 @@ void arch_elf_relocate_local(struct llext_loader *loader, struct llext *ext,
}
#endif

#include <zephyr/syscalls/llext.h>

#endif /* ZEPHYR_LLEXT_H */
3 changes: 3 additions & 0 deletions subsys/llext/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
if(CONFIG_LLEXT)
zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/llext/llext.h)

zephyr_library()

# For strnlen()
Expand All @@ -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)
Expand Down
63 changes: 63 additions & 0 deletions subsys/llext/llext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you wrote "if desired" in the commit description. Should you check for entry_fn == NULL then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, commit message is a leftover from the previous iteration, when llext_bootstrap was the only way to call init functions. I have since changed code, comments and documentation to make it clear it's not optional anymore; if you only want to init an extension, you now have llext_bringup.
Will fix if I have to do any code change 👍


/* Call de-initialization functions */
ret = llext_teardown(ext);
if (ret < 0) {
LOG_ERR("Failed to call de-init functions: %d", ret);
return;
}
}
95 changes: 95 additions & 0 deletions subsys/llext/llext_handlers.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2024 Arduino SA
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <string.h>
#include <zephyr/llext/llext.h>
#include <zephyr/llext/loader.h>
#include <zephyr/internal/syscall_handler.h>
#include <zephyr/kernel.h>

#include <zephyr/logging/log.h>
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 <zephyr/syscalls/llext_get_fn_table_mrsh.c>

#endif /* CONFIG_USERSPACE */
Loading