llext: Linkable loadable extensions

Adds the linkable loadable extensions (llext) subsystem which provides
functionality for reading, parsing, and linking ELF encoded executable
code into a managed extension to the running elf base image.

A loader interface, and default buffer loader implementation,
make available to the llext subsystem the elf data. A simple management
API provide the ability to load and unload extensions as needed. A shell
interface for extension loading and unloading makes it easy to try.

Adds initial support for armv7 thumb built elfs with very specific
compiler flags.

Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
Co-authored-by: Chen Peng1 <peng1.chen@intel.com>
Co-authored-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
This commit is contained in:
Tom Burdick 2023-09-27 08:10:10 -05:00 committed by Anas Nashif
parent 34e16225eb
commit 41e0a4a371
16 changed files with 1927 additions and 0 deletions

View file

@ -13,6 +13,7 @@ zephyr_library_sources_ifdef(CONFIG_IRQ_OFFLOAD irq_offload.c)
zephyr_library_sources_ifdef(CONFIG_THREAD_LOCAL_STORAGE tls.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE userspace.S)
zephyr_library_sources_ifdef(CONFIG_ARM_ZIMAGE_HEADER header.S)
zephyr_library_sources_ifdef(CONFIG_LLEXT elf.c)
add_subdirectory_ifdef(CONFIG_CPU_CORTEX_M cortex_m)
add_subdirectory_ifdef(CONFIG_CPU_CORTEX_M_HAS_CMSE cortex_m/cmse)

37
arch/arm/core/elf.c Normal file
View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/llext/elf.h>
#include <zephyr/llext/llext.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(elf);
/**
* @brief Architecture specific function for relocating partially linked (static) elf
*
* Elf files contain a series of relocations described in a section. These relocation
* instructions are architecture specific and each architecture supporting modules
* must implement this.
*
* The relocation codes for arm are well documented
* https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst#relocation
*/
void arch_elf_relocate(elf_rel_t *rel, uintptr_t opaddr, uintptr_t opval)
{
elf_word reloc_type = ELF32_R_TYPE(rel->r_info);
switch (reloc_type) {
case R_ARM_ABS32:
/* Update the absolute address of a load/store instruction */
*((uint32_t *)opaddr) = (uint32_t)opval;
break;
default:
LOG_DBG("Unsupported ARM elf relocation type %d at address %lx",
reloc_type, opaddr);
break;
}
}

View file

@ -39,6 +39,10 @@
ITERABLE_SECTION_ROM(zbus_channel_observation, 4)
#endif /* CONFIG_ZBUS */
#ifdef CONFIG_LLEXT
ITERABLE_SECTION_ROM(llext_const_symbol, 4)
#endif /* CONFIG_LLEXT */
SECTION_DATA_PROLOGUE(symbol_to_keep,,)
{
__symbol_to_keep_start = .;

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_LLEXT_BUF_LOADER_H
#define ZEPHYR_LLEXT_BUF_LOADER_H
#include <zephyr/llext/loader.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LLEXT buffer loader
* @defgroup llext_buf_loader Linkable loadable extensions buffer loader
* @ingroup llext
* @{
*/
/**
* @brief An extension loader from a provided buffer containing an ELF
*/
struct llext_buf_loader {
/** Extension loader */
struct llext_loader loader;
/** @cond ignore */
const uint8_t *buf;
size_t len;
size_t pos;
/** @endcond */
};
/** @cond ignore */
int llext_buf_read(struct llext_loader *ldr, void *buf, size_t len);
int llext_buf_seek(struct llext_loader *ldr, size_t pos);
/** @endcond */
/**
* @brief Initialize an extension buf loader
*
* @param _buf Buffer containing an ELF binary
* @param _buf_len Buffer length in bytes
*/
#define LLEXT_BUF_LOADER(_buf, _buf_len) \
{ \
.loader = { \
.read = llext_buf_read, \
.seek = llext_buf_seek \
}, \
.buf = (_buf), \
.len = (_buf_len), \
.pos = 0 \
}
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_LLEXT_BUF_LOADER_H */

484
include/zephyr/llext/elf.h Normal file
View file

@ -0,0 +1,484 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#ifndef ZEPHYR_LLEXT_ELF_H
#define ZEPHYR_LLEXT_ELF_H
#include <stdint.h>
/**
* @brief ELF types and parsing
*
* Reference documents can be found here https://refspecs.linuxfoundation.org/elf/
*
* @defgroup elf ELF data types and defines
* @ingroup llext
* @{
*/
#ifdef __cplusplus
extern "C" {
#endif
/** Unsigned program address */
typedef uint32_t elf32_addr;
/** Unsigned medium integer */
typedef uint16_t elf32_half;
/** Unsigned file offset */
typedef uint32_t elf32_off;
/** Signed integer */
typedef int32_t elf32_sword;
/** Unsigned integer */
typedef uint32_t elf32_word;
/** Unsigned program address */
typedef uint64_t elf64_addr;
/** Unsigned medium integer */
typedef uint16_t elf64_half;
/** Unsigned file offset */
typedef uint64_t elf64_off;
/** Signed integer */
typedef int32_t elf64_sword;
/** Unsigned integer */
typedef uint32_t elf64_word;
/** Signed long integer */
typedef int64_t elf64_sxword;
/** Unsigned long integer */
typedef uint64_t elf64_xword;
/**
* @brief ELF identifier block
*
* 4 byte magic (.ELF)
* 1 byte class (Invalid, 32 bit, 64 bit)
* 1 byte endianness (Invalid, LSB, MSB)
* 1 byte version (1)
* 1 byte OS ABI (0 None, 1 HP-UX, 2 NetBSD, 3 Linux)
* 1 byte ABI (0)
* 7 bytes padding
*/
#define EI_NIDENT 16
/**
* @brief ELF Header(32-bit)
*/
struct elf32_ehdr {
/** Magic string identifying ELF binary */
unsigned char e_ident[EI_NIDENT];
/** Type of ELF */
elf32_half e_type;
/** Machine type */
elf32_half e_machine;
/** Object file version */
elf32_word e_version;
/** Virtual address of entry */
elf32_addr e_entry;
/** Program header table offset */
elf32_off e_phoff;
/** Section header table offset */
elf32_off e_shoff;
/** Processor specific flags */
elf32_word e_flags;
/** ELF header size */
elf32_half e_ehsize;
/** Program header count */
elf32_half e_phentsize;
/** Program header count */
elf32_half e_phnum;
/** Section header size */
elf32_half e_shentsize;
/** Section header count */
elf32_half e_shnum;
/** Section header containing section header string table */
elf32_half e_shstrndx;
};
/**
* @brief ELF Header(64-bit)
*/
struct elf64_ehdr {
/** Magic string identifying ELF binary */
unsigned char e_ident[EI_NIDENT];
/** Type of ELF */
elf64_half e_type;
/** Machine type */
elf64_half e_machine;
/** Object file version */
elf64_word e_version;
/** Virtual address of entry */
elf64_addr e_entry;
/** Program header table offset */
elf64_off e_phoff;
/** Section header table offset */
elf64_off e_shoff;
/** Processor specific flags */
elf64_word e_flags;
/** ELF header size */
elf64_half e_ehsize;
/** Program header size */
elf64_half e_phentsize;
/** Program header count */
elf64_half e_phnum;
/** Section header size */
elf64_half e_shentsize;
/** Section header count */
elf64_half e_shnum;
/** Section header containing section header string table */
elf64_half e_shstrndx;
};
/** Relocatable (unlinked) ELF */
#define ET_REL 1
/** Executable (without PIC/PIE) ELF */
#define ET_EXEC 2
/** Dynamic (executable with PIC/PIE or shared lib) ELF */
#define ET_DYN 3
/** Core Dump */
#define ET_CORE 4
/**
* @brief Section Header(32-bit)
*/
struct elf32_shdr {
/** Section header name index in section header string table */
elf32_word sh_name;
/** Section type */
elf32_word sh_type;
/** Section header attributes */
elf32_word sh_flags;
/** Address of section in the image */
elf32_addr sh_addr;
/** Location of section in the ELF binary in bytes */
elf32_off sh_offset;
/** Section size in bytes */
elf32_word sh_size;
/** Section header table link index, depends on section type */
elf32_word sh_link;
/** Section info, depends on section type */
elf32_word sh_info;
/** Section address alignment */
elf32_word sh_addralign;
/** Section contains table of fixed size entries sh_entsize bytes large */
elf32_word sh_entsize;
};
/**
* @brief Section Header(64-bit)
*/
struct elf64_shdr {
/** Section header name index in section header string table */
elf64_word sh_name;
/** Section type */
elf64_word sh_type;
/** Section header attributes */
elf64_xword sh_flags;
/** Address of section in the image */
elf64_addr sh_addr;
/** Location of section in the ELF binary in bytes */
elf64_off sh_offset;
/** Section size in bytes */
elf64_xword sh_size;
/** Section header table link index, depends on section type */
elf64_word sh_link;
/** Section info, depends on section type */
elf64_word sh_info;
/** Section address alignment */
elf64_xword sh_addralign;
/** Section contains table of fixed size entries sh_entsize bytes large */
elf64_xword sh_entsize;
};
#define SHT_PROGBITS 0x1
#define SHT_SYMTAB 0x2
#define SHT_STRTAB 0x3
#define SHT_RELA 0x4
#define SHT_NOBITS 0x8
#define SHT_REL 0x9
#define SHT_DYNSYM 0xB
#define SHF_WRITE 0x1
#define SHF_ALLOC 0x2
#define SHF_EXECINSTR 0x4
/**
* @brief Symbol table entry(32-bit)
*/
struct elf32_sym {
/** Name of the symbol as an index into the symbol string table */
elf32_word st_name;
/** Value or location of the symbol */
elf32_addr st_value;
/** Size of the symbol */
elf32_word st_size;
/** Symbol binding and type information */
unsigned char st_info;
/** Symbol visibility */
unsigned char st_other;
/** Symbols related section given by section header index */
elf32_half st_shndx;
};
/**
* @brief Symbol table entry(64-bit)
*/
struct elf64_sym {
/** Name of the symbol as an index into the symbol string table */
elf64_word st_name;
/** Value or location of the symbol */
elf64_addr st_value;
/** Size of the symbol */
elf64_xword st_size;
/** Symbol binding and type information */
unsigned char st_info;
/** Symbol visibility */
unsigned char st_other;
/** Symbols related section given by section header index */
elf64_half st_shndx;
};
#define SHN_UNDEF 0
#define SHN_ABS 0xfff1
#define SHN_COMMON 0xfff2
#define STT_NOTYPE 0
#define STT_OBJECT 1
#define STT_FUNC 2
#define STT_SECTION 3
#define STT_FILE 4
#define STT_COMMON 5
#define STT_LOOS 10
#define STT_HIOS 12
#define STT_LOPROC 13
#define STT_HIPROC 15
#define STB_LOCAL 0
#define STB_GLOBAL 1
#define STB_WEAK 2
#define STB_LOOS 10
#define STB_HIOS 12
#define STB_LOPROC 13
#define STB_HIPROC 15
/**
* @brief Symbol binding from 32bit st_info
*
* @param i Value of st_info
*/
#define ELF32_ST_BIND(i) ((i) >> 4)
/**
* @brief Symbol type from 32bit st_info
*
* @param i Value of st_info
*/
#define ELF32_ST_TYPE(i) ((i) & 0xf)
/**
* @brief Symbol binding from 32bit st_info
*
* @param i Value of st_info
*/
#define ELF64_ST_BIND(i) ((i) >> 4)
/**
* @brief Symbol type from 32bit st_info
*
* @param i Value of st_info
*/
#define ELF64_ST_TYPE(i) ((i) & 0xf)
/**
* @brief Relocation entry for 32-bit ELFs
*/
struct elf32_rel {
/** Offset in the section to perform a relocation */
elf32_addr r_offset;
/** Information about the relocation, related symbol and type */
elf32_word r_info;
};
/**
* @brief Relocation symbol index from r_info
*
* @param i Value of r_info
*/
#define ELF32_R_SYM(i) ((i) >> 8)
/**
* @brief Relocation type from r_info
*
* @param i Value of r_info
*/
#define ELF32_R_TYPE(i) ((i) & 0xff)
/**
* @brief Relocation entry for 64-bit ELFs
*/
struct elf64_rel {
/** Offset in section to perform a relocation */
elf64_addr r_offset;
/** Information about relocation, related symbol and type */
elf64_xword r_info;
};
/** @brief Relocation symbol from r_info
*
* @param i Value of r_info
*/
#define ELF64_R_SYM(i) ((i) >> 32)
/**
* @brief Relocation type from r_info
*
* @param i Value of r_info
*/
#define ELF64_R_TYPE(i) ((i) & 0xffffffff)
#define R_386_NONE 0
#define R_386_32 1
#define R_386_PC32 2
#define R_386_GOT32 3
#define R_386_PLT32 4
#define R_386_COPY 5
#define R_386_GLOB_DAT 6
#define R_386_JMP_SLOT 7
#define R_386_RELATIVE 8
#define R_386_GOTOFF 9
#define R_ARM_NONE 0
#define R_ARM_PC24 1
#define R_ARM_ABS32 2
#define R_ARM_REL32 3
#define R_ARM_COPY 4
#define R_ARM_CALL 28
#define R_ARM_V4BX 40
#define R_XTENSA_NONE 0
#define R_XTENSA_32 1
#define R_XTENSA_SLOT0_OP 20
/**
* @brief Program header(32-bit)
*/
struct elf32_phdr {
elf32_word p_type;
elf32_off p_offset;
elf32_addr p_vaddr;
elf32_addr p_paddr;
elf32_word p_filesz;
elf32_word p_memsz;
elf32_word p_flags;
elf32_word p_align;
};
/**
* @brief Program header(64-bit)
*/
struct elf64_phdr {
elf64_word p_type;
elf64_off p_offset;
elf64_addr p_vaddr;
elf64_addr p_paddr;
elf64_xword p_filesz;
elf64_xword p_memsz;
elf64_word p_flags;
elf64_xword p_align;
};
/**
* @brief Program segment type
*/
#define PT_LOAD 1
/**
* @brief Dynamic section entry(32-bit)
*/
struct elf32_dyn {
elf32_sword d_tag;
union {
elf32_word d_val;
elf32_addr d_ptr;
} d_un;
};
/**
* @brief Dynamic section entry(64-bit)
*/
struct elf64_dyn {
elf64_sxword d_tag;
union {
elf64_xword d_val;
elf64_addr d_ptr;
} d_un;
};
#if defined(CONFIG_64BIT) || defined(__DOXYGEN__)
/** Machine sized elf header structure */
typedef struct elf64_ehdr elf_ehdr_t;
/** Machine sized section header structure */
typedef struct elf64_shdr elf_shdr_t;
/** Machine sized program header structure */
typedef struct elf64_phdr elf_phdr_t;
/** Machine sized program address */
typedef elf64_addr elf_addr;
/** Machine sized small integer */
typedef elf64_half elf_half;
/** Machine sized integer */
typedef elf64_xword elf_word;
/** Machine sized relocation struct */
typedef struct elf64_rela elf_rel_t;
/** Machine sized symbol struct */
typedef struct elf64_sym elf_sym_t;
/** Machine sized macro alias for obtaining a relocation symbol */
#define ELF_R_SYM ELF64_R_SYM
/** Machine sized macro alias for obtaining a relocation type */
#define ELF_R_TYPE ELF64_R_TYPE
/** Machine sized macro alias for obtaining a symbol bind */
#define ELF_ST_BIND ELF64_ST_BIND
/** Machine sized macro alias for obtaining a symbol type */
#define ELF_ST_TYPE ELF64_ST_TYPE
#else
/** Machine sized elf header structure */
typedef struct elf32_ehdr elf_ehdr_t;
/** Machine sized section header structure */
typedef struct elf32_shdr elf_shdr_t;
/** Machine sized program header structure */
typedef struct elf32_phdr elf_phdr_t;
/** Machine sized program address */
typedef elf32_addr elf_addr;
/** Machine sized small integer */
typedef elf32_half elf_half;
/** Machine sized integer */
typedef elf32_word elf_word;
/** Machine sized relocation struct */
typedef struct elf32_rel elf_rel_t;
/** Machine sized symbol struct */
typedef struct elf32_sym elf_sym_t;
/** Machine sized macro alias for obtaining a relocation symbol */
#define ELF_R_SYM ELF32_R_SYM
/** Machine sized macro alias for obtaining a relocation type */
#define ELF_R_TYPE ELF32_R_TYPE
/** Machine sized macro alias for obtaining a symbol bind */
#define ELF_ST_BIND ELF32_ST_BIND
/** Machine sized macro alias for obtaining a symbol type */
#define ELF_ST_TYPE ELF32_ST_TYPE
#endif
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* ZEPHYR_LLEXT_ELF_H */

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_LLEXT_H
#define ZEPHYR_LLEXT_H
#include <zephyr/sys/slist.h>
#include <zephyr/llext/elf.h>
#include <zephyr/llext/symbol.h>
#include <zephyr/llext/loader.h>
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Linkable loadable extensions
* @defgroup llext Linkable loadable extensions
* @ingroup os_services
* @{
*/
/**
* @brief Enum of memory regions for lookup tables
*/
enum llext_mem {
LLEXT_MEM_TEXT,
LLEXT_MEM_DATA,
LLEXT_MEM_RODATA,
LLEXT_MEM_BSS,
LLEXT_MEM_COUNT,
};
/**
* @brief Linkable loadable extension
*/
struct llext {
/** @cond ignore */
sys_snode_t _llext_list;
/** @endcond */
/** Name of the llext */
char name[16];
/** Lookup table of llext memory regions */
void *mem[LLEXT_MEM_COUNT];
/** Total size of the llext memory usage */
size_t mem_size;
/** Exported symbols from the llext, may be linked against by other llext */
struct llext_symtable sym_tab;
};
/**
* @brief List head of loaded extensions
*/
sys_slist_t *llext_list(void);
/**
* @brief Find an llext by name
*
* @param[in] name String name of the llext
* @retval NULL if no llext not found
* @retval llext if llext found
*/
struct llext *llext_by_name(const char *name);
/**
* @brief Load and link an extension
*
* Loads relevant ELF data into memory and provides a structure to work with it.
*
* Only relocatable ELF files are currently supported (partially linked).
*
* @param[in] loader An extension loader that provides input data and context
* @param[in] name A string identifier for the module
* @param[out] ext A pointer to a statically allocated llext struct
*
* @retval 0 Success
* @retval -ENOMEM Not enough memory
* @retval -EINVAL Invalid ELF stream
*/
int llext_load(struct llext_loader *loader, const char *name, struct llext **ext);
/**
* @brief Unload an extension
*
* @param[in] ext Extension to unload
*/
void llext_unload(struct llext *ext);
/**
* @brief Find the address for an arbitrary symbol name.
*
* @param[in] sym_table Symbol table to lookup symbol in, if NULL uses base table
* @param[in] sym_name Symbol name to find
*
* @retval NULL if no symbol found
* @retval addr Address of symbol in memory if found
*/
const void * const llext_find_sym(const struct llext_symtable *sym_table, const char *sym_name);
/**
* @brief Call a function by name
*
* Expects a symbol representing a void fn(void) style function exists
* and may be called.
*
* @param[in] ext Extension to call function in
* @param[in] sym_name Function name (exported symbol) in the extension
*
* @retval 0 success
* @retval -EINVAL invalid symbol name
*/
int llext_call_fn(struct llext *ext, const char *sym_name);
/**
* @brief Architecture specific function for updating op codes given a relocation
*
* Elf files contain a series of relocations described in a section. These relocation
* instructions are architecture specific and each architecture supporting extensions
* must implement this. They are instructions on how to rewrite opcodes given
* the actual placement of some symbolic data such as a section, function,
* or object.
*
* @param[in] rel Relocation data provided by elf
* @param[in] opaddr Address of operation to rewrite with relocation
* @param[in] opval Value of looked up symbol to relocate
*/
void arch_elf_relocate(elf_rel_t *rel, uintptr_t opaddr, uintptr_t opval);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODULE_H */

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_LLEXT_LOADER_H
#define ZEPHYR_LLEXT_LOADER_H
#include <zephyr/llext/elf.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Loader context for llext
* @defgroup llext_loader Loader context for llext
* @ingroup llext
* @{
*/
/**
* @brief Enum of sections for lookup tables
*/
enum llext_section {
LLEXT_SECT_TEXT,
LLEXT_SECT_DATA,
LLEXT_SECT_RODATA,
LLEXT_SECT_BSS,
LLEXT_SECT_REL_TEXT,
LLEXT_SECT_REL_DATA,
LLEXT_SECT_REL_RODATA,
LLEXT_SECT_REL_BSS,
LLEXT_SECT_SYMTAB,
LLEXT_SECT_STRTAB,
LLEXT_SECT_SHSTRTAB,
LLEXT_SECT_COUNT,
};
/**
* @brief Linkable loadable extension loader context
*/
struct llext_loader {
/**
* @brief Read (copy) from the loader
*
* Copies len bytes into buf from the current position of the
* loader.
*
* @param[in] ldr Loader
* @param[in] out Output location
* @param[in] len Length to copy into the output location
*
* @retval 0 Success
* @retval -errno Error reading (any errno)
*/
int (*read)(struct llext_loader *ldr, void *out, size_t len);
/**
* @brief Seek to a new absolute location
*
* Changes the location of the loader position to a new absolute
* given position.
*
* @param[in] ldr Loader
* @param[in] pos Position in stream to move loader
*
* @retval 0 Success
* @retval -errno Error reading (any errno)
*/
int (*seek)(struct llext_loader *s, size_t pos);
/** @cond ignore */
elf_ehdr_t hdr;
elf_shdr_t sects[LLEXT_SECT_COUNT];
uint32_t *sect_map;
uint32_t sect_cnt;
uint32_t sym_cnt;
/** @endcond */
};
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_LLEXT_LOADER_H */

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_LLEXT_SYMBOL_H
#define ZEPHYR_LLEXT_SYMBOL_H
#include <zephyr/sys/iterable_sections.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Linkable loadable extension symbol
* @defgroup llext_symbols LLEXT symbols
* @ingroup llext
* @{
*/
/**
* @brief Constant symbols are unchangeable named memory addresses
*
* Symbols may be named function or global objects that have been exported
* for linking. These constant symbols are useful in the base image
* as they may be placed in ROM.
*/
struct llext_const_symbol {
/** Name of symbol */
const char *const name;
/** Address of symbol */
const void *const addr;
};
/**
* @brief Symbols are named memory addresses
*
* Symbols may be named function or global objects that have been exported
* for linking. These are mutable and should come from extensions where
* the location may need updating depending on where memory is placed.
*/
struct llext_symbol {
/** Name of symbol */
char *name;
/** Address of symbol */
void *addr;
};
/**
* @brief A symbol table
*
* An array of symbols
*/
struct llext_symtable {
/** Number of symbols in the table */
size_t sym_cnt;
/** Array of symbols */
struct llext_symbol *syms;
};
/**
* @brief Export a constant symbol to a table of symbols
*
* Takes a symbol (function or object) by symbolic name and adds the name
* and address of the symbol to a table of symbols that may be used for linking.
*
* @param x Symbol to export
*/
#define EXPORT_SYMBOL(x) \
static const STRUCT_SECTION_ITERABLE(llext_const_symbol, x ## _sym) = { \
.name = STRINGIFY(x), .addr = x, \
}
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_LLEXT_SYMBOL_H */

View file

@ -20,6 +20,7 @@
#include <zephyr/syscall_handler.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/cbprintf.h>
#include <zephyr/llext/symbol.h>
#include <sys/types.h>
/* Option present only when CONFIG_USERSPACE enabled. */
@ -210,6 +211,7 @@ void printk(const char *fmt, ...)
va_end(ap);
}
EXPORT_SYMBOL(printk);
#endif /* defined(CONFIG_PRINTK) */
#ifndef CONFIG_PICOLIBC

View file

@ -43,6 +43,7 @@ add_subdirectory_ifdef(CONFIG_IMG_MANAGER dfu)
add_subdirectory_ifdef(CONFIG_INPUT input)
add_subdirectory_ifdef(CONFIG_JWT jwt)
add_subdirectory_ifdef(CONFIG_MODEM_MODULES modem)
add_subdirectory_ifdef(CONFIG_LLEXT llext)
add_subdirectory_ifdef(CONFIG_NET_BUF net)
add_subdirectory_ifdef(CONFIG_RETENTION retention)
add_subdirectory_ifdef(CONFIG_SENSING sensing)

View file

@ -21,6 +21,7 @@ source "subsys/fs/Kconfig"
source "subsys/input/Kconfig"
source "subsys/ipc/Kconfig"
source "subsys/jwt/Kconfig"
source "subsys/llext/Kconfig"
source "subsys/logging/Kconfig"
source "subsys/lorawan/Kconfig"
source "subsys/mem_mgmt/Kconfig"

View file

@ -0,0 +1,6 @@
if(CONFIG_LLEXT)
zephyr_library()
zephyr_library_sources(llext.c)
zephyr_library_sources(buf_loader.c)
zephyr_library_sources_ifdef(CONFIG_LLEXT_SHELL shell.c)
endif()

27
subsys/llext/Kconfig Normal file
View file

@ -0,0 +1,27 @@
# Copyright (c) 2023 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
menuconfig LLEXT
bool "Linkable loadable extensions"
help
Enable the linkable loadable extension subsystem
if LLEXT
config LLEXT_HEAP_SIZE
int "llext heap memory size in kilobytes"
default 8
help
Heap size in kilobytes available to llext for dynamic allocation
config LLEXT_SHELL
bool "llext shell commands"
depends on SHELL
help
Manage llext with shell commands for loading, unloading, and introspection
module = LLEXT
module-str = llext
source "subsys/logging/Kconfig.template.log_config"
endif

31
subsys/llext/buf_loader.c Normal file
View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <zephyr/llext/buf_loader.h>
#include <zephyr/sys/util.h>
#include <string.h>
int llext_buf_read(struct llext_loader *l, void *buf, size_t len)
{
struct llext_buf_loader *buf_l = CONTAINER_OF(l, struct llext_buf_loader, loader);
size_t end = MIN(buf_l->pos + len, buf_l->len);
size_t read_len = end - buf_l->pos;
memcpy(buf, buf_l->buf + buf_l->pos, read_len);
buf_l->pos = end;
return 0;
}
int llext_buf_seek(struct llext_loader *l, size_t pos)
{
struct llext_buf_loader *buf_l = CONTAINER_OF(l, struct llext_buf_loader, loader);
buf_l->pos = MIN(pos, buf_l->len);
return 0;
}

757
subsys/llext/llext.c Normal file
View file

@ -0,0 +1,757 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include "zephyr/sys/__assert.h"
#include <zephyr/sys/util.h>
#include <zephyr/llext/elf.h>
#include <zephyr/llext/loader.h>
#include <zephyr/llext/llext.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL);
#include <string.h>
K_HEAP_DEFINE(llext_heap, CONFIG_LLEXT_HEAP_SIZE * 1024);
static const char ELF_MAGIC[] = {0x7f, 'E', 'L', 'F'};
static inline int llext_read(struct llext_loader *l, void *buf, size_t len)
{
return l->read(l, buf, len);
}
static inline int llext_seek(struct llext_loader *l, size_t pos)
{
return l->seek(l, pos);
}
static sys_slist_t _llext_list = SYS_SLIST_STATIC_INIT(&_llext_list);
sys_slist_t *llext_list(void)
{
return &_llext_list;
}
struct llext *llext_by_name(const char *name)
{
sys_slist_t *mlist = llext_list();
sys_snode_t *node = sys_slist_peek_head(mlist);
struct llext *ext = CONTAINER_OF(node, struct llext, _llext_list);
while (node != NULL) {
if (strncmp(ext->name, name, sizeof(ext->name)) == 0) {
return ext;
}
node = sys_slist_peek_next(node);
ext = CONTAINER_OF(node, struct llext, _llext_list);
}
return NULL;
}
const void * const llext_find_sym(const struct llext_symtable *sym_table, const char *sym_name)
{
if (sym_table == NULL) {
/* Buildin symbol table */
STRUCT_SECTION_FOREACH(llext_const_symbol, sym) {
if (strcmp(sym->name, sym_name) == 0) {
return sym->addr;
}
}
} else {
/* find symbols in module */
for (size_t i = 0; i < sym_table->sym_cnt; i++) {
if (strcmp(sym_table->syms[i].name, sym_name) == 0) {
return sym_table->syms[i].addr;
}
}
}
return NULL;
}
/*
* Find all relevant string and symbol tables
*/
static int llext_find_tables(struct llext_loader *ldr)
{
int ret = 0;
size_t pos = ldr->hdr.e_shoff;
elf_shdr_t shdr;
ldr->sects[LLEXT_SECT_SHSTRTAB] =
ldr->sects[LLEXT_SECT_STRTAB] =
ldr->sects[LLEXT_SECT_SYMTAB] = (elf_shdr_t){0};
/* Find symbol and string tables */
for (int i = 0, str_cnt = 0; i < ldr->hdr.e_shnum && str_cnt < 3; i++) {
ret = llext_seek(ldr, pos);
if (ret != 0) {
LOG_ERR("failed seeking to position %u\n", pos);
goto out;
}
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
if (ret != 0) {
LOG_ERR("failed reading section header at position %u\n", pos);
goto out;
}
pos += ldr->hdr.e_shentsize;
LOG_DBG("section %d at %x: name %d, type %d, flags %x, addr %x, size %d",
i,
ldr->hdr.e_shoff + i * ldr->hdr.e_shentsize,
shdr.sh_name,
shdr.sh_type,
shdr.sh_flags,
shdr.sh_addr,
shdr.sh_size);
switch (shdr.sh_type) {
case SHT_SYMTAB:
case SHT_DYNSYM:
LOG_DBG("symtab at %d", i);
ldr->sects[LLEXT_SECT_SYMTAB] = shdr;
ldr->sect_map[i] = LLEXT_SECT_SYMTAB;
str_cnt++;
break;
case SHT_STRTAB:
if (ldr->hdr.e_shstrndx == i) {
LOG_DBG("shstrtab at %d", i);
ldr->sects[LLEXT_SECT_SHSTRTAB] = shdr;
ldr->sect_map[i] = LLEXT_SECT_SHSTRTAB;
} else {
LOG_DBG("strtab at %d", i);
ldr->sects[LLEXT_SECT_STRTAB] = shdr;
ldr->sect_map[i] = LLEXT_SECT_STRTAB;
}
str_cnt++;
break;
default:
break;
}
}
if (!ldr->sects[LLEXT_SECT_SHSTRTAB].sh_type ||
!ldr->sects[LLEXT_SECT_STRTAB].sh_type ||
!ldr->sects[LLEXT_SECT_SYMTAB].sh_type) {
LOG_ERR("Some sections are missing or present multiple times!");
ret = -ENOENT;
}
out:
return ret;
}
/*
* Maps the section indexes and copies special section headers for easier use
*/
static int llext_map_sections(struct llext_loader *ldr)
{
int ret = 0;
size_t pos = ldr->hdr.e_shoff;
elf_shdr_t shdr;
char name[32];
for (int i = 0; i < ldr->hdr.e_shnum; i++) {
ret = llext_seek(ldr, pos);
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
if (ret != 0) {
goto out;
}
pos += ldr->hdr.e_shentsize;
elf_word str_idx = shdr.sh_name;
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_SHSTRTAB].sh_offset + str_idx);
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, name, sizeof(name));
if (ret != 0) {
goto out;
}
name[sizeof(name) - 1] = '\0';
LOG_DBG("section %d name %s", i, name);
enum llext_section sect_idx;
if (strncmp(name, ".text", sizeof(name)) == 0) {
sect_idx = LLEXT_SECT_TEXT;
} else if (strncmp(name, ".data", sizeof(name)) == 0) {
sect_idx = LLEXT_SECT_DATA;
} else if (strncmp(name, ".rodata", sizeof(name)) == 0) {
sect_idx = LLEXT_SECT_RODATA;
} else if (strncmp(name, ".bss", sizeof(name)) == 0) {
sect_idx = LLEXT_SECT_BSS;
} else {
LOG_DBG("Not copied section %s", name);
continue;
}
ldr->sects[sect_idx] = shdr;
ldr->sect_map[i] = sect_idx;
}
out:
return ret;
}
static inline enum llext_section llext_sect_from_mem(enum llext_mem m)
{
enum llext_section s;
switch (m) {
case LLEXT_MEM_BSS:
s = LLEXT_SECT_BSS;
break;
case LLEXT_MEM_DATA:
s = LLEXT_SECT_DATA;
break;
case LLEXT_MEM_RODATA:
s = LLEXT_SECT_RODATA;
break;
case LLEXT_MEM_TEXT:
s = LLEXT_SECT_TEXT;
break;
default:
CODE_UNREACHABLE;
}
return s;
}
static int llext_allocate_mem(struct llext_loader *ldr, struct llext *ext)
{
int ret = 0;
enum llext_section sect_idx;
for (enum llext_mem mem_idx = 0; mem_idx < LLEXT_MEM_COUNT; mem_idx++) {
sect_idx = llext_sect_from_mem(mem_idx);
if (ldr->sects[sect_idx].sh_size > 0) {
ext->mem[mem_idx] =
k_heap_aligned_alloc(&llext_heap, sizeof(uintptr_t),
ldr->sects[sect_idx].sh_size,
K_NO_WAIT);
if (ext->mem[mem_idx] == NULL) {
ret = -ENOMEM;
goto out;
}
}
}
out:
return ret;
}
static int llext_copy_sections(struct llext_loader *ldr, struct llext *ext)
{
int ret = 0;
enum llext_section sect_idx;
for (enum llext_mem mem_idx = 0; mem_idx < LLEXT_MEM_COUNT; mem_idx++) {
sect_idx = llext_sect_from_mem(mem_idx);
if (ldr->sects[sect_idx].sh_size > 0) {
ret = llext_seek(ldr, ldr->sects[sect_idx].sh_offset);
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, ext->mem[mem_idx], ldr->sects[sect_idx].sh_size);
if (ret != 0) {
goto out;
}
}
}
out:
return ret;
}
static int llext_count_export_syms(struct llext_loader *ldr)
{
int ret = 0;
elf_sym_t sym;
size_t ent_size = ldr->sects[LLEXT_SECT_SYMTAB].sh_entsize;
size_t syms_size = ldr->sects[LLEXT_SECT_SYMTAB].sh_size;
size_t pos = ldr->sects[LLEXT_SECT_SYMTAB].sh_offset;
size_t sym_cnt = syms_size / sizeof(elf_sym_t);
char name[32];
LOG_DBG("symbol count %u", sym_cnt);
for (int i = 0; i < sym_cnt; i++) {
ret = llext_seek(ldr, pos);
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, &sym, ent_size);
if (ret != 0) {
goto out;
}
pos += ent_size;
uint32_t stt = ELF_ST_TYPE(sym.st_info);
uint32_t stb = ELF_ST_BIND(sym.st_info);
uint32_t sect = sym.st_shndx;
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_STRTAB].sh_offset + sym.st_name);
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, name, sizeof(name));
if (ret != 0) {
goto out;
}
name[sizeof(name) - 1] = '\0';
if (stt == STT_FUNC && stb == STB_GLOBAL) {
LOG_DBG("function symbol %d, name %s, type tag %d, bind %d, sect %d",
i, name, stt, stb, sect);
ldr->sym_cnt++;
} else {
LOG_DBG("unhandled symbol %d, name %s, type tag %d, bind %d, sect %d",
i, name, stt, stb, sect);
}
}
out:
return ret;
}
static inline int llext_allocate_symtab(struct llext_loader *ldr, struct llext *ext)
{
int ret = 0;
ext->sym_tab.syms = k_heap_alloc(&llext_heap, ldr->sym_cnt * sizeof(struct llext_symbol),
K_NO_WAIT);
ext->sym_tab.sym_cnt = ldr->sym_cnt;
memset(ext->sym_tab.syms, 0, ldr->sym_cnt * sizeof(struct llext_symbol));
return ret;
}
static inline int llext_copy_symbols(struct llext_loader *ldr, struct llext *ext)
{
int ret = 0;
elf_sym_t sym;
size_t ent_size = ldr->sects[LLEXT_SECT_SYMTAB].sh_entsize;
size_t syms_size = ldr->sects[LLEXT_SECT_SYMTAB].sh_size;
size_t pos = ldr->sects[LLEXT_SECT_SYMTAB].sh_offset;
size_t sym_cnt = syms_size / sizeof(elf_sym_t);
char name[32];
int i, j = 0;
for (i = 0; i < sym_cnt; i++) {
ret = llext_seek(ldr, pos);
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, &sym, ent_size);
if (ret != 0) {
goto out;
}
pos += ent_size;
uint32_t stt = ELF_ST_TYPE(sym.st_info);
uint32_t stb = ELF_ST_BIND(sym.st_info);
uint32_t sect = sym.st_shndx;
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_STRTAB].sh_offset + sym.st_name);
if (ret != 0) {
goto out;
}
llext_read(ldr, name, sizeof(name));
if (ret != 0) {
goto out;
}
if (stt == STT_FUNC && stb == STB_GLOBAL && sect != SHN_UNDEF) {
ext->sym_tab.syms[j].name = k_heap_alloc(&llext_heap,
sizeof(name),
K_NO_WAIT);
strcpy(ext->sym_tab.syms[j].name, name);
ext->sym_tab.syms[j].addr =
(void *)((uintptr_t)ext->mem[ldr->sect_map[sym.st_shndx]]
+ sym.st_value);
LOG_DBG("function symbol %d name %s addr %p",
j, name, ext->sym_tab.syms[j].addr);
j++;
}
}
out:
return ret;
}
static int llext_link(struct llext_loader *ldr, struct llext *ext)
{
int ret = 0;
uintptr_t loc = 0;
elf_shdr_t shdr;
elf_rel_t rel;
elf_sym_t sym;
size_t pos = ldr->hdr.e_shoff;
elf_word rel_cnt = 0;
char name[32];
for (int i = 0; i < ldr->hdr.e_shnum - 1; i++) {
ret = llext_seek(ldr, pos);
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, &shdr, sizeof(elf_shdr_t));
if (ret != 0) {
goto out;
}
pos += ldr->hdr.e_shentsize;
/* find relocation sections */
if (shdr.sh_type != SHT_REL && shdr.sh_type != SHT_RELA) {
continue;
}
rel_cnt = shdr.sh_size / sizeof(elf_rel_t);
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_SHSTRTAB].sh_offset + shdr.sh_name);
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, name, sizeof(name));
if (ret != 0) {
goto out;
}
if (strncmp(name, ".rel.text", sizeof(name)) == 0 ||
strncmp(name, ".rela.text", sizeof(name)) == 0) {
loc = (uintptr_t)ext->mem[LLEXT_MEM_TEXT];
} else if (strncmp(name, ".rel.bss", sizeof(name)) == 0) {
loc = (uintptr_t)ext->mem[LLEXT_MEM_BSS];
} else if (strncmp(name, ".rel.rodata", sizeof(name)) == 0) {
loc = (uintptr_t)ext->mem[LLEXT_MEM_RODATA];
} else if (strncmp(name, ".rel.data", sizeof(name)) == 0) {
loc = (uintptr_t)ext->mem[LLEXT_MEM_DATA];
}
LOG_DBG("relocation section %s (%d) linked to section %d has %d relocations",
name, i, shdr.sh_link, rel_cnt);
for (int j = 0; j < rel_cnt; j++) {
/* get each relocation entry */
ret = llext_seek(ldr, shdr.sh_offset + j * sizeof(elf_rel_t));
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, &rel, sizeof(elf_rel_t));
if (ret != 0) {
goto out;
}
/* get corresponding symbol */
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_SYMTAB].sh_offset
+ ELF_R_SYM(rel.r_info) * sizeof(elf_sym_t));
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, &sym, sizeof(elf_sym_t));
if (ret != 0) {
goto out;
}
ret = llext_seek(ldr, ldr->sects[LLEXT_SECT_STRTAB].sh_offset +
sym.st_name);
if (ret != 0) {
goto out;
}
ret = llext_read(ldr, name, sizeof(name));
if (ret != 0) {
goto out;
}
LOG_DBG("relocation %d:%d info %x (type %d, sym %d) offset %d sym_name "
"%s sym_type %d sym_bind %d sym_ndx %d",
i, j, rel.r_info, ELF_R_TYPE(rel.r_info), ELF_R_SYM(rel.r_info),
rel.r_offset, name, ELF_ST_TYPE(sym.st_info),
ELF_ST_BIND(sym.st_info), sym.st_shndx);
uintptr_t link_addr, op_loc, op_code;
/* If symbol is undefined, then we need to look it up */
if (sym.st_shndx == SHN_UNDEF) {
link_addr = (uintptr_t)llext_find_sym(NULL, name);
if (link_addr == 0) {
LOG_ERR("Undefined symbol with no entry in "
"symbol table %s, offset %d, link section %d",
name, rel.r_offset, shdr.sh_link);
ret = -ENODATA;
goto out;
} else {
op_code = (uintptr_t)(loc + rel.r_offset);
LOG_INF("found symbol %s at 0x%lx, updating op code 0x%lx",
name, link_addr, op_code);
}
} else if (ELF_ST_TYPE(sym.st_info) == STT_SECTION) {
link_addr = (uintptr_t)ext->mem[ldr->sect_map[sym.st_shndx]];
LOG_INF("found section symbol %s addr 0x%lx", name, link_addr);
} else {
/* Nothing to relocate here */
continue;
}
op_loc = loc + rel.r_offset;
LOG_INF("relocating (linking) symbol %s type %d binding %d ndx %d offset "
"%d link section %d",
name, ELF_ST_TYPE(sym.st_info), ELF_ST_BIND(sym.st_info),
sym.st_shndx, rel.r_offset, shdr.sh_link);
LOG_INF("writing relocation symbol %s type %d sym %d at addr 0x%lx "
"addr 0x%lx",
name, ELF_R_TYPE(rel.r_info), ELF_R_SYM(rel.r_info),
op_loc, link_addr);
/* relocation */
arch_elf_relocate(&rel, op_loc, link_addr);
}
}
out:
return ret;
}
/*
* Load a valid ELF as an extension
*/
static int do_llext_load(struct llext_loader *ldr, struct llext *ext)
{
int ret = 0;
memset(ldr->sects, 0, sizeof(ldr->sects));
ldr->sect_cnt = 0;
ldr->sym_cnt = 0;
size_t sect_map_sz = ldr->hdr.e_shnum * sizeof(uint32_t);
ldr->sect_map = k_heap_alloc(&llext_heap, sect_map_sz, K_NO_WAIT);
if (!ldr->sect_map) {
LOG_ERR("Failed to allocate memory for section map, size %u", sect_map_sz);
ret = -ENOMEM;
goto out;
}
memset(ldr->sect_map, 0, ldr->hdr.e_shnum*sizeof(uint32_t));
ldr->sect_cnt = ldr->hdr.e_shnum;
LOG_DBG("Finding ELF tables...");
ret = llext_find_tables(ldr);
if (ret != 0) {
LOG_ERR("Failed to find important ELF tables, ret %d", ret);
goto out;
}
LOG_DBG("Mapping ELF sections...");
ret = llext_map_sections(ldr);
if (ret != 0) {
LOG_ERR("Failed to map ELF sections, ret %d", ret);
goto out;
}
LOG_DBG("Allocation memory for ELF sections...");
ret = llext_allocate_mem(ldr, ext);
if (ret != 0) {
LOG_ERR("Failed to map memory for ELF sections, ret %d", ret);
goto out;
}
LOG_DBG("Copying sections...");
ret = llext_copy_sections(ldr, ext);
if (ret != 0) {
LOG_ERR("Failed to copy ELF sections, ret %d", ret);
goto out;
}
LOG_DBG("Counting exported symbols...");
ret = llext_count_export_syms(ldr);
if (ret != 0) {
LOG_ERR("Failed to count exported ELF symbols, ret %d", ret);
goto out;
}
LOG_DBG("Allocating memory for symbol table...");
ret = llext_allocate_symtab(ldr, ext);
if (ret != 0) {
LOG_ERR("Failed to allocate extension symbol table, ret %d", ret);
goto out;
}
LOG_DBG("Copying symbols...");
ret = llext_copy_symbols(ldr, ext);
if (ret != 0) {
LOG_ERR("Failed to copy symbols, ret %d", ret);
goto out;
}
LOG_DBG("Linking ELF...");
ret = llext_link(ldr, ext);
if (ret != 0) {
LOG_ERR("Failed to link, ret %d", ret);
goto out;
}
out:
if (ldr->sect_map != NULL) {
k_heap_free(&llext_heap, ldr->sect_map);
}
if (ret != 0) {
LOG_DBG("Failed to load extension, freeing memory...");
for (enum llext_mem mem_idx = 0; mem_idx < LLEXT_MEM_COUNT; mem_idx++) {
if (ext->mem[mem_idx] != NULL) {
k_heap_free(&llext_heap, ext->mem[mem_idx]);
}
}
for (int i = 0; i < ext->sym_tab.sym_cnt; i++) {
if (ext->sym_tab.syms[i].name != NULL) {
k_heap_free(&llext_heap, ext->sym_tab.syms[i].name);
}
}
k_heap_free(&llext_heap, ext->sym_tab.syms);
} else {
LOG_DBG("loaded module, .text at %p, .rodata at %p", ext->mem[LLEXT_MEM_TEXT],
ext->mem[LLEXT_MEM_RODATA]);
}
return ret;
}
int llext_load(struct llext_loader *ldr, const char *name, struct llext **ext)
{
int ret = 0;
elf_ehdr_t ehdr;
ret = llext_seek(ldr, 0);
if (ret != 0) {
LOG_ERR("Failed to seek for ELF header");
goto out;
}
ret = llext_read(ldr, &ehdr, sizeof(ehdr));
if (ret != 0) {
LOG_ERR("Failed to read ELF header");
goto out;
}
/* check whether this is an valid elf file */
if (memcmp(ehdr.e_ident, ELF_MAGIC, sizeof(ELF_MAGIC)) != 0) {
LOG_HEXDUMP_ERR(ehdr.e_ident, 16, "Invalid ELF, magic does not match");
ret = -EINVAL;
goto out;
}
switch (ehdr.e_type) {
case ET_REL:
case ET_DYN:
LOG_DBG("Loading relocatable or shared elf");
*ext = k_heap_alloc(&llext_heap, sizeof(struct llext), K_NO_WAIT);
if (*ext == NULL) {
LOG_ERR("Not enough memory for extension metadata");
ret = -ENOMEM;
goto out;
}
memset(*ext, 0, sizeof(struct llext));
for (int i = 0; i < LLEXT_MEM_COUNT; i++) {
(*ext)->mem[i] = NULL;
}
ldr->hdr = ehdr;
ret = do_llext_load(ldr, *ext);
break;
default:
LOG_ERR("Unsupported elf file type %x", ehdr.e_type);
*ext = NULL;
ret = -EINVAL;
goto out;
}
if (ret == 0) {
strncpy((*ext)->name, name, sizeof((*ext)->name));
(*ext)->name[sizeof((*ext)->name) - 1] = '\0';
sys_slist_append(&_llext_list, &(*ext)->_llext_list);
LOG_INF("Loaded extension %s", (*ext)->name);
}
out:
return ret;
}
void llext_unload(struct llext *ext)
{
__ASSERT(ext, "Expected non-null extension");
sys_slist_find_and_remove(&_llext_list, &ext->_llext_list);
for (int i = 0; i < LLEXT_MEM_COUNT; i++) {
if (ext->mem[i] != NULL) {
LOG_DBG("freeing memory region %d", i);
k_heap_free(&llext_heap, ext->mem[i]);
ext->mem[i] = NULL;
}
}
if (ext->sym_tab.syms != NULL) {
for (int i = 0; i < ext->sym_tab.sym_cnt; i++) {
k_heap_free(&llext_heap, ext->sym_tab.syms[i].name);
}
k_heap_free(&llext_heap, ext->sym_tab.syms);
}
k_heap_free(&llext_heap, ext);
}
int llext_call_fn(struct llext *ext, const char *sym_name)
{
void (*fn)(void);
fn = llext_find_sym(&ext->sym_tab, sym_name);
if (fn == NULL) {
return -EINVAL;
}
fn();
return 0;
}

178
subsys/llext/shell.c Normal file
View file

@ -0,0 +1,178 @@
/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <zephyr/sys/slist.h>
#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>
#include <zephyr/llext/elf.h>
#include <zephyr/llext/llext.h>
#include <zephyr/llext/buf_loader.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(llext_shell, CONFIG_LLEXT_LOG_LEVEL);
#define LLEXT_LIST_HELP "List loaded extensions and their size in memory"
#define LLEXT_LOAD_HEX_HELP \
"Load an elf file encoded in hex directly from the shell input. Syntax:\n" \
"<ext_name> <ext_hex_string>"
#define LLEXT_UNLOAD_HELP \
"Unload an extension by name. Syntax:\n" \
"<ext_name>"
#define LLEXT_LIST_SYMBOLS_HELP \
"List extension symbols. Syntax:\n" \
"<ext_name>"
#define LLEXT_CALL_FN_HELP \
"Call extension function with prototype void fn(void). Syntax:\n" \
"<ext_name> <function_name>"
static int cmd_llext_list_symbols(const struct shell *sh, size_t argc, char *argv[])
{
struct llext *m = llext_by_name(argv[1]);
if (m == NULL) {
shell_print(sh, "No such llext %s", argv[1]);
return -EINVAL;
}
shell_print(sh, "Extension: %s symbols", m->name);
shell_print(sh, "| Symbol | Address |\n");
for (elf_word i = 0; i < m->sym_tab.sym_cnt; i++) {
shell_print(sh, "| %16s | %p |\n", m->sym_tab.syms[i].name,
m->sym_tab.syms[i].addr);
}
return 0;
}
static void llext_name_get(size_t idx, struct shell_static_entry *entry)
{
sys_slist_t *ext_list = llext_list();
sys_snode_t *node = sys_slist_peek_head(ext_list);
entry->syntax = NULL;
for (int i = 0; i < idx; i++) {
node = sys_slist_peek_next(node);
if (node == NULL) {
goto out;
}
}
struct llext *ext = CONTAINER_OF(node, struct llext, _llext_list);
entry->syntax = ext->name;
out:
entry->syntax = NULL;
entry->help = NULL;
entry->subcmd = NULL;
}
SHELL_DYNAMIC_CMD_CREATE(msub_llext_name, llext_name_get);
static int cmd_llext_list(const struct shell *sh, size_t argc, char *argv[])
{
sys_snode_t *node;
struct llext *ext;
shell_print(sh, "| Name | Size |\n");
SYS_SLIST_FOR_EACH_NODE(llext_list(), node) {
ext = CONTAINER_OF(node, struct llext, _llext_list);
shell_print(sh, "| %16s | %12d |\n", ext->name, ext->mem_size);
}
return 0;
}
#define LLEXT_MAX_SIZE 8192
static uint8_t llext_buf[LLEXT_MAX_SIZE];
static int cmd_llext_load_hex(const struct shell *sh, size_t argc, char *argv[])
{
char name[16];
size_t hex_len = strnlen(argv[2], LLEXT_MAX_SIZE*2+1);
size_t bin_len = hex_len/2;
if (bin_len > LLEXT_MAX_SIZE) {
shell_print(sh, "Extension %d bytes too large to load, max %d bytes\n", hex_len/2,
LLEXT_MAX_SIZE);
return -ENOMEM;
}
strncpy(name, argv[1], sizeof(name));
size_t llext_buf_len = hex2bin(argv[2], hex_len, llext_buf, LLEXT_MAX_SIZE);
struct llext_buf_loader buf_loader = LLEXT_BUF_LOADER(llext_buf, llext_buf_len);
struct llext_loader *ldr = &buf_loader.loader;
LOG_DBG("hex2bin hex len %d, llext buf sz %d, read %d",
hex_len, LLEXT_MAX_SIZE, llext_buf_len);
LOG_HEXDUMP_DBG(llext_buf, 4, "4 byte MAGIC");
struct llext *ext;
int res = llext_load(ldr, name, &ext);
if (res == 0) {
shell_print(sh, "Successfully loaded extension %s, addr %p\n", ext->name, ext);
} else {
shell_print(sh, "Failed to load extension %s, return code %d\n", name, res);
}
return 0;
}
static int cmd_llext_unload(const struct shell *sh, size_t argc, char *argv[])
{
struct llext *ext = llext_by_name(argv[1]);
if (ext == NULL) {
shell_print(sh, "No such extension %s", argv[1]);
return -EINVAL;
}
llext_unload(ext);
shell_print(sh, "Unloaded extension %s\n", argv[1]);
return 0;
}
static int cmd_llext_call_fn(const struct shell *sh, size_t argc, char *argv[])
{
struct llext *ext = llext_by_name(argv[1]);
if (ext == NULL) {
shell_print(sh, "No such extension %s", argv[1]);
return -EINVAL;
}
llext_call_fn(ext, argv[2]);
return 0;
}
/* clang-format off */
SHELL_STATIC_SUBCMD_SET_CREATE(sub_llext,
SHELL_CMD(list, NULL, LLEXT_LIST_HELP, cmd_llext_list),
SHELL_CMD_ARG(load_hex, NULL, LLEXT_LOAD_HEX_HELP, cmd_llext_load_hex,
3, 0),
SHELL_CMD_ARG(unload, &msub_llext_name, LLEXT_UNLOAD_HELP, cmd_llext_unload, 2, 0),
SHELL_CMD_ARG(list_symbols, &msub_llext_name, LLEXT_LIST_SYMBOLS_HELP,
cmd_llext_list_symbols, 2, 0),
SHELL_CMD_ARG(call_fn, &msub_llext_name, LLEXT_CALL_FN_HELP,
cmd_llext_call_fn, 3, 0),
SHELL_SUBCMD_SET_END
);
/* clang-format on */
SHELL_CMD_REGISTER(llext, &sub_llext, "Loadable extension commands", NULL);