Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

llext: add new inspection API #85498

Merged
merged 3 commits into from
Feb 13, 2025
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
2 changes: 2 additions & 0 deletions doc/services/llext/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ API Reference
.. doxygengroup:: llext_symbols

.. doxygengroup:: llext_loader_apis

.. doxygengroup:: llext_inspect_apis
4 changes: 4 additions & 0 deletions doc/services/llext/load.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ The returned ``void *`` can then be cast to the appropriate type and used.
A wrapper for calling a function with no arguments is provided in
:c:func:`llext_call_fn`.

Advanced users that need direct access to areas of the newly loaded extension
may want to refer to :c:func:`llext_get_section_info` and other LLEXT
inspection APIs.

Cleaning up after use
=====================

Expand Down
141 changes: 141 additions & 0 deletions include/zephyr/llext/inspect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2025 Arduino SA
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_LLEXT_INSPECT_H
#define ZEPHYR_LLEXT_INSPECT_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stddef.h>
#include <zephyr/llext/llext.h>
#include <zephyr/llext/loader.h>
#include <zephyr/llext/llext_internal.h>

/**
* @file
* @brief LLEXT ELF inspection routines.
*
* This file contains routines to inspect the contents of an ELF file. It is
* intended to be used by applications that need advanced access to the ELF
* file structures of a loaded extension.
*
* @defgroup llext_inspect_apis ELF inspection APIs
* @ingroup llext_apis
* @{
*/

/**
* @brief Get information about a memory region for the specified extension.
*
* Retrieve information about a region (merged group of similar sections) in
* the extension. Any output parameter can be NULL if that information is not
* needed.
*
* @param[in] ldr Loader
* @param[in] ext Extension
* @param[in] region Region to get information about
* @param[out] hdr Variable storing the pointer to the region header
* @param[out] addr Variable storing the region load address
* @param[out] size Variable storing the region size
*
* @return 0 on success, -EINVAL if the region is invalid
*/
static inline int llext_get_region_info(const struct llext_loader *ldr,
const struct llext *ext,
enum llext_mem region,
const elf_shdr_t **hdr,
const void **addr, size_t *size)
{
if ((unsigned int)region >= LLEXT_MEM_COUNT) {
return -EINVAL;
}

if (hdr) {
*hdr = &ldr->sects[region];
}
if (addr) {
*addr = ext->mem[region];
}
if (size) {
*size = ext->mem_size[region];
}

return 0;
}

/**
* @brief Get the index of a section with the specified name.
*
* Requires the @ref llext_load_param.keep_section_info flag to be set at
* extension load time.
*
* @param[in] ldr Loader
* @param[in] ext Extension
* @param[in] section_name Name of the section to look for
*
* @return Section index on success, -ENOENT if the section was not found,
* -ENOTSUP if section data is not available.
*/
int llext_section_shndx(const struct llext_loader *ldr, const struct llext *ext,
const char *section_name);

/**
* @brief Get information about a section for the specified extension.
*
* Retrieve information about an ELF sections in the extension. Any output
* parameter can be @c NULL if that information is not needed.
*
* Requires the @ref llext_load_param.keep_section_info flag to be set at
* extension load time.
*
* @param[in] ldr Loader
* @param[in] ext Extension
* @param[in] shndx Section index
* @param[out] hdr Variable storing the pointer to the section header
* @param[out] region Variable storing the region the section belongs to
* @param[out] offset Variable storing the offset of the section in the region
*
* @return 0 on success, -EINVAL if the section index is invalid,
* -ENOTSUP if section data is not available.
*/
static inline int llext_get_section_info(const struct llext_loader *ldr,
const struct llext *ext,
unsigned int shndx,
const elf_shdr_t **hdr,
enum llext_mem *region,
size_t *offset)
{
if (shndx < 0 || shndx >= ext->sect_cnt) {
return -EINVAL;
}
if (!ldr->sect_map) {
return -ENOTSUP;
}

if (hdr) {
*hdr = &ext->sect_hdrs[shndx];
}
if (region) {
*region = ldr->sect_map[shndx].mem_idx;
}
if (offset) {
*offset = ldr->sect_map[shndx].offset;
}

return 0;
}

/**
* @}
*/

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_LLEXT_INSPECT_H */
24 changes: 24 additions & 0 deletions include/zephyr/llext/llext.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ static inline unsigned int llext_section_count(const struct llext *ext)
struct llext_load_param {
/** Perform local relocation */
bool relocate_local;

/**
* Use the virtual symbol addresses from the ELF, not addresses within
* the memory buffer, when calculating relocation targets. It also
Expand All @@ -154,12 +155,22 @@ struct llext_load_param {
* allocation and copying internally.
*/
bool pre_located;

/**
* Extensions can implement custom ELF sections to be loaded in specific
* memory regions, detached from other sections of compatible types.
* This optional callback checks whether a section should be detached.
*/
bool (*section_detached)(const elf_shdr_t *shdr);

/**
* Keep the ELF section data in memory after loading the extension. This
* is needed to use some of the functions in @ref llext_inspect_apis.
*
* @note Related memory must be freed by @ref llext_free_inspection_data
* before the extension can be unloaded via @ref llext_unload.
*/
bool keep_section_info;
};

/** Default initializer for @ref llext_load_param */
Expand Down Expand Up @@ -211,6 +222,19 @@ int llext_load(struct llext_loader *loader, const char *name, struct llext **ext
*/
int llext_unload(struct llext **ext);

/**
* @brief Free any inspection-related memory for the specified loader and extension.
*
* This is only required if inspection data was requested at load time by
* setting @ref llext_load_param.keep_section_info; otherwise, this call will
* be a no-op.
*
* @param[in] ldr Extension loader
* @param[in] ext Extension
* @returns 0 on success, or a negative error code.
*/
int llext_free_inspection_data(struct llext_loader *ldr, struct llext *ext);

/** @brief Entry point function signature for an extension. */
typedef void (*llext_entry_fn_t)(void *user_data);

Expand Down
5 changes: 5 additions & 0 deletions include/zephyr/llext/llext_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ extern "C" {
struct llext_loader;
struct llext;

struct llext_elf_sect_map {
enum llext_mem mem_idx;
size_t offset;
};

const void *llext_loaded_sect_ptr(struct llext_loader *ldr, struct llext *ext, unsigned int sh_ndx);

/** @endcond */
Expand Down
37 changes: 21 additions & 16 deletions subsys/llext/llext.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,37 @@ static sys_slist_t _llext_list = SYS_SLIST_STATIC_INIT(&_llext_list);

static struct k_mutex llext_lock = Z_MUTEX_INITIALIZER(llext_lock);

int llext_get_section_header(struct llext_loader *ldr, struct llext *ext, const char *search_name,
elf_shdr_t *shdr)
int llext_section_shndx(const struct llext_loader *ldr, const struct llext *ext,
const char *sect_name)
{
const elf_shdr_t *tmp;
unsigned int i;

for (i = 0, tmp = ext->sect_hdrs;
i < ext->sect_cnt;
i++, tmp++) {
const char *name = llext_peek(ldr,
ldr->sects[LLEXT_MEM_SHSTRTAB].sh_offset +
tmp->sh_name);

if (!name) {
return -ENOTSUP;
}
for (i = 1; i < ext->sect_cnt; i++) {
const char *name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB,
ext->sect_hdrs[i].sh_name);

if (!strcmp(name, search_name)) {
*shdr = *tmp;
return 0;
if (!strcmp(name, sect_name)) {
return i;
}
}

return -ENOENT;
}

int llext_get_section_header(struct llext_loader *ldr, struct llext *ext, const char *search_name,
elf_shdr_t *shdr)
{
int ret;

ret = llext_section_shndx(ldr, ext, search_name);
if (ret < 0) {
return ret;
}

*shdr = ext->sect_hdrs[ret];
return 0;
}

ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name)
{
elf_shdr_t *shdr;
Expand Down
21 changes: 17 additions & 4 deletions subsys/llext/llext_load.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ static int llext_load_elf_data(struct llext_loader *ldr, struct llext *ext)
LOG_ERR("Failed to allocate section map, size %zu", sect_map_sz);
return -ENOMEM;
}
ext->alloc_size += sect_map_sz;
for (int i = 0; i < ext->sect_cnt; i++) {
ldr->sect_map[i].mem_idx = LLEXT_MEM_COUNT;
ldr->sect_map[i].offset = 0;
Expand Down Expand Up @@ -740,12 +741,13 @@ int do_llext_load(struct llext_loader *ldr, struct llext *ext,

out:
/*
* Free resources only used during loading. Note that this exploits
* the fact that freeing a NULL pointer has no effect.
* Free resources only used during loading, unless explicitly requested.
* Note that this exploits the fact that freeing a NULL pointer has no effect.
*/

llext_free(ldr->sect_map);
ldr->sect_map = NULL;
if (ret != 0 || !ldr_parm || !ldr_parm->keep_section_info) {
llext_free_inspection_data(ldr, ext);
}

/* Until proper inter-llext linking is implemented, the symbol table is
* not useful outside of the loading process; keep it only if debugging
Expand Down Expand Up @@ -777,3 +779,14 @@ int do_llext_load(struct llext_loader *ldr, struct llext *ext,

return ret;
}

int llext_free_inspection_data(struct llext_loader *ldr, struct llext *ext)
{
if (ldr->sect_map) {
ext->alloc_size -= ext->sect_cnt * sizeof(ldr->sect_map[0]);
llext_free(ldr->sect_map);
ldr->sect_map = NULL;
}

return 0;
}
8 changes: 2 additions & 6 deletions subsys/llext/llext_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@

#include <zephyr/kernel.h>
#include <zephyr/llext/llext.h>

struct llext_elf_sect_map {
enum llext_mem mem_idx;
size_t offset;
};
#include <zephyr/llext/llext_internal.h>

/*
* Memory management (llext_mem.c)
Expand Down Expand Up @@ -53,7 +49,7 @@ static inline void llext_free(void *ptr)
int do_llext_load(struct llext_loader *ldr, struct llext *ext,
const struct llext_load_param *ldr_parm);

static inline const char *llext_string(struct llext_loader *ldr, struct llext *ext,
static inline const char *llext_string(const struct llext_loader *ldr, const struct llext *ext,
enum llext_mem mem_idx, unsigned int idx)
{
return (char *)ext->mem[mem_idx] + idx;
Expand Down
1 change: 1 addition & 0 deletions tests/subsys/llext/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ set(ext_names
relative_jump
object
syscalls
inspect
threads_kernel_objects
export_dependent
export_dependency
Expand Down
28 changes: 28 additions & 0 deletions tests/subsys/llext/src/inspect_ext.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2025 Arduino SA
*
* SPDX-License-Identifier: Apache-2.0
*/

/* This extension exports a number of symbols to be used by the LLEXT
* inspection APIs. Each symbol is exported within a different section
* and only the addresses are checked in this test.
*/

#include <zephyr/llext/symbol.h>

int number_in_bss;
int number_in_data = 1;
const int number_in_rodata = 2;
const int number_in_my_rodata Z_GENERIC_SECTION(.my_rodata) = 3;

void function_in_text(void)
{
/* only used for address check */
}

EXPORT_SYMBOL(number_in_bss);
EXPORT_SYMBOL(number_in_data);
EXPORT_SYMBOL(number_in_rodata);
EXPORT_SYMBOL(number_in_my_rodata);
EXPORT_SYMBOL(function_in_text);
Loading
Loading