diff --git a/CMakeLists.txt b/CMakeLists.txt index c6b17350149..05c50b119bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1888,6 +1888,21 @@ if(CONFIG_BUILD_OUTPUT_INFO_HEADER) ) endif() +if (CONFIG_LLEXT AND CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID) + #slidgen must be the first post-build command to be executed + #on the Zephyr ELF to ensure that all other commands, such as + #binary file generation, are operating on a preparated ELF. + list(PREPEND + post_build_commands + COMMAND ${PYTHON_EXECUTABLE} + ${ZEPHYR_BASE}/scripts/build/llext_prepare_exptab.py + --elf-file ${PROJECT_BINARY_DIR}/${KERNEL_ELF_NAME} + --slid-listing ${PROJECT_BINARY_DIR}/slid_listing.txt + -vvv + ) + +endif() + if(NOT CMAKE_C_COMPILER_ID STREQUAL "ARMClang") set(check_init_priorities_input $,${BYPRODUCT_KERNEL_EXE_NAME},${BYPRODUCT_KERNEL_ELF_NAME}> diff --git a/boards/qemu/x86/qemu_x86_tiny.ld b/boards/qemu/x86/qemu_x86_tiny.ld index 3b3ecde113c..1b635f331ee 100644 --- a/boards/qemu/x86/qemu_x86_tiny.ld +++ b/boards/qemu/x86/qemu_x86_tiny.ld @@ -254,6 +254,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /DISCARD/ : { *(.plt) diff --git a/cmake/llext-edk.cmake b/cmake/llext-edk.cmake index 9501133f3ab..da9c33b127f 100644 --- a/cmake/llext-edk.cmake +++ b/cmake/llext-edk.cmake @@ -31,6 +31,11 @@ cmake_minimum_required(VERSION 3.20.0) +if (CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID) + message(FATAL_ERROR + "The LLEXT EDK is not compatible with CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID.") +endif() + set(llext_edk ${PROJECT_BINARY_DIR}/${llext_edk_name}) set(llext_edk_inc ${llext_edk}/include) diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index 2c1e1c66355..9fe387e2388 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -5431,6 +5431,23 @@ function(add_llext_target target_name) COMMAND_EXPAND_LISTS ) + # LLEXT ELF processing for importing via SLID + # + # This command must be executed as last step of the packaging process, + # to ensure that the ELF processed for binary generation contains SLIDs. + # If executed too early, it is possible that some tools executed to modify + # the ELF file (e.g., strip) undo the work performed here. + if (CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID) + set(slid_inject_cmd + ${PYTHON_EXECUTABLE} + ${ZEPHYR_BASE}/scripts/build/llext_inject_slids.py + --elf-file ${llext_pkg_output} + -vvv + ) + else() + set(slid_inject_cmd ${CMAKE_COMMAND} -E true) + endif() + # Type-specific packaging of the built binary file into an .llext file if(CONFIG_LLEXT_TYPE_ELF_OBJECT) @@ -5438,6 +5455,7 @@ function(add_llext_target target_name) add_custom_command( OUTPUT ${llext_pkg_output} COMMAND ${CMAKE_COMMAND} -E copy ${llext_pkg_input} ${llext_pkg_output} + COMMAND ${slid_inject_cmd} DEPENDS ${llext_proc_target} ${llext_pkg_input} ) @@ -5453,6 +5471,7 @@ function(add_llext_target target_name) $${llext_pkg_input} $${llext_pkg_output} $ + COMMAND ${slid_inject_cmd} DEPENDS ${llext_proc_target} ${llext_pkg_input} ) @@ -5467,6 +5486,7 @@ function(add_llext_target target_name) $${llext_pkg_input} $${llext_pkg_output} $ + COMMAND ${slid_inject_cmd} DEPENDS ${llext_proc_target} ${llext_pkg_input} ) diff --git a/include/zephyr/arch/arc/v2/linker.ld b/include/zephyr/arch/arc/v2/linker.ld index a7751d96119..c65aa5a6484 100644 --- a/include/zephyr/arch/arc/v2/linker.ld +++ b/include/zephyr/arch/arc/v2/linker.ld @@ -81,6 +81,10 @@ SECTIONS { #include +#ifdef CONFIG_LLEXT +#include +#endif + GROUP_START(ROMABLE_REGION) SECTION_PROLOGUE(_TEXT_SECTION_NAME,,ALIGN(1024)) { diff --git a/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld b/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld index 947c18de313..cffe2f3856e 100644 --- a/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld +++ b/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld @@ -98,6 +98,10 @@ SECTIONS { #include +#ifdef CONFIG_LLEXT +#include +#endif + /* * .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose', * before text section. diff --git a/include/zephyr/arch/arm/cortex_m/scripts/linker.ld b/include/zephyr/arch/arm/cortex_m/scripts/linker.ld index 88598cf5790..094e4a5303f 100644 --- a/include/zephyr/arch/arm/cortex_m/scripts/linker.ld +++ b/include/zephyr/arch/arm/cortex_m/scripts/linker.ld @@ -113,6 +113,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /* * .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose', * before text section. diff --git a/include/zephyr/arch/arm64/scripts/linker.ld b/include/zephyr/arch/arm64/scripts/linker.ld index 5a8e1404a98..d2f573520c1 100644 --- a/include/zephyr/arch/arm64/scripts/linker.ld +++ b/include/zephyr/arch/arm64/scripts/linker.ld @@ -77,6 +77,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /* * .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose', * before text section. diff --git a/include/zephyr/arch/mips/linker.ld b/include/zephyr/arch/mips/linker.ld index cbff890822c..f7479c23e36 100644 --- a/include/zephyr/arch/mips/linker.ld +++ b/include/zephyr/arch/mips/linker.ld @@ -46,6 +46,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + SECTION_PROLOGUE(_VECTOR_SECTION_NAME,,) { . = ALIGN(0x1000); diff --git a/include/zephyr/arch/nios2/linker.ld b/include/zephyr/arch/nios2/linker.ld index f7ba64088fc..d65805ba7cf 100644 --- a/include/zephyr/arch/nios2/linker.ld +++ b/include/zephyr/arch/nios2/linker.ld @@ -85,6 +85,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /* * .plt and .iplt are here according to * 'nios2-zephyr-elf-ld --verbose', before text section. diff --git a/include/zephyr/arch/posix/linker.ld b/include/zephyr/arch/posix/linker.ld index 06dfb3254f1..0943546c0c7 100644 --- a/include/zephyr/arch/posix/linker.ld +++ b/include/zephyr/arch/posix/linker.ld @@ -21,6 +21,10 @@ SECTIONS { +#ifdef CONFIG_LLEXT +#include +#endif + SECTION_PROLOGUE(rom_start,,) { /* Located in generated directory. This file is populated by the diff --git a/include/zephyr/arch/riscv/common/linker.ld b/include/zephyr/arch/riscv/common/linker.ld index ccb16cc2c67..8ef264b9f1a 100644 --- a/include/zephyr/arch/riscv/common/linker.ld +++ b/include/zephyr/arch/riscv/common/linker.ld @@ -122,6 +122,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /* * The .plt and .iplt are here according to * 'riscv32-zephyr-elf-ld --verbose', before text section. diff --git a/include/zephyr/arch/sparc/linker.ld b/include/zephyr/arch/sparc/linker.ld index 725339ef0b7..ccf333d4863 100644 --- a/include/zephyr/arch/sparc/linker.ld +++ b/include/zephyr/arch/sparc/linker.ld @@ -23,6 +23,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + __rom_region_start = .; SECTION_PROLOGUE(_TEXT_SECTION_NAME,,) diff --git a/include/zephyr/arch/x86/ia32/linker.ld b/include/zephyr/arch/x86/ia32/linker.ld index 69ff5412326..e796b3c3e85 100644 --- a/include/zephyr/arch/x86/ia32/linker.ld +++ b/include/zephyr/arch/x86/ia32/linker.ld @@ -77,6 +77,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /DISCARD/ : { *(.plt) diff --git a/include/zephyr/arch/x86/intel64/linker.ld b/include/zephyr/arch/x86/intel64/linker.ld index 8949b94e657..1bc014570c6 100644 --- a/include/zephyr/arch/x86/intel64/linker.ld +++ b/include/zephyr/arch/x86/intel64/linker.ld @@ -236,4 +236,7 @@ SECTIONS .shstrtab 0 : { *(.shstrtab) } #endif +#ifdef CONFIG_LLEXT + #include +#endif } diff --git a/include/zephyr/linker/llext-sections.ld b/include/zephyr/linker/llext-sections.ld new file mode 100644 index 00000000000..b8cc32e3d1b --- /dev/null +++ b/include/zephyr/linker/llext-sections.ld @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + + /* + * Special section used by LLEXT if CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID + * is enabled. Declare this section to prevent it from being considered orphan. + * + * This section is used to temporarily save the exported symbols' names in the + * Zephyr ELF for post-processing, but it is not included in the final binary. + * + * NOTE: This section MUST start at address 0, as the post-processing scripts + * assume that the address of any data in this section (i.e., symbol names) is + * strictly equivalent to the offset inside the section. + */ +#ifdef CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID + SECTION_PROLOGUE(llext_exports_strtab, 0 (COPY), ) + { + KEEP(*(llext_exports_strtab)) + } +#endif diff --git a/include/zephyr/llext/symbol.h b/include/zephyr/llext/symbol.h index e42dc37c9ef..b9d5c6138a8 100644 --- a/include/zephyr/llext/symbol.h +++ b/include/zephyr/llext/symbol.h @@ -10,6 +10,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -27,14 +28,27 @@ extern "C" { * 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. + * + * @note When updating this structure, make sure to also update the + * 'scripts/build/llext_prepare_exptab.py' build script. */ struct llext_const_symbol { - /** Name of symbol */ - const char *const name; + /** At build time, we always write to 'name'. + * At runtime, which field is used depends + * on CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID. + */ + union { + /** Name of symbol */ + const char *const name; + + /** Symbol Link Identifier */ + const uintptr_t slid; + }; /** Address of symbol */ const void *const addr; }; +BUILD_ASSERT(sizeof(struct llext_const_symbol) == 2 * sizeof(uintptr_t)); /** * @brief Symbols are named memory addresses @@ -75,10 +89,19 @@ struct llext_symtable { * * @param x Symbol to export to extensions */ +#ifdef CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID +#define EXPORT_SYMBOL(x) \ + static const char Z_GENERIC_SECTION("llext_exports_strtab") __used \ + x ## _sym_name[] = STRINGIFY(x); \ + static const STRUCT_SECTION_ITERABLE(llext_const_symbol, x ## _sym) = { \ + .name = x ## _sym_name, .addr = (const void *)&x, \ + } +#else #define EXPORT_SYMBOL(x) \ static const STRUCT_SECTION_ITERABLE(llext_const_symbol, x ## _sym) = { \ .name = STRINGIFY(x), .addr = (const void *)&x, \ } +#endif /** * @brief Exports a symbol from an extension to the base image diff --git a/scripts/build/llext_inject_slids.py b/scripts/build/llext_inject_slids.py new file mode 100755 index 00000000000..19e87edba93 --- /dev/null +++ b/scripts/build/llext_inject_slids.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024 STMicroelectronics +# SPDX-License-Identifier: Apache-2.0 + +"""Injects SLIDs in LLEXT ELFs' symbol tables. + +When Kconfig option CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID is enabled, +all imports from the Zephyr kernel & application are resolved using +SLIDs instead of symbol names. This script stores the SLID of all +imported symbols in their associated entry in the ELF symbol table +to allow the LLEXT subsystem to link it properly at runtime. + +Note that this script is idempotent in theory. However, to prevent +any catastrophic problem, the script will abort if the 'st_value' +field of the `ElfX_Sym` structure is found to be non-zero, which is +the case after one invocation. For this reason, in practice, the script +cannot actually be executed twice on the same ELF file. +""" + +import argparse +import logging +import shutil +import sys + +from elftools.elf.elffile import ELFFile +from elftools.elf.sections import SymbolTableSection + +import llext_slidlib + +class LLEXTSymtabPreparator(): + def __init__(self, elf_path, log): + self.log = log + self.elf_path = elf_path + self.elf_fd = open(elf_path, "rb+") + self.elf = ELFFile(self.elf_fd) + + def _find_symtab(self): + supported_symtab_sections = [ + ".symtab", + ".dynsym", + ] + + symtab = None + for section_name in supported_symtab_sections: + symtab = self.elf.get_section_by_name(section_name) + if not isinstance(symtab, SymbolTableSection): + self.log.debug(f"section {section_name} not found.") + else: + self.log.info(f"processing '{section_name}' symbol table...") + self.log.debug(f"(symbol table is at file offset 0x{symtab['sh_offset']:X})") + break + return symtab + + def _find_imports_in_symtab(self, symtab): + i = 0 + imports = [] + for sym in symtab.iter_symbols(): + #Check if symbol is an import + if sym.entry['st_info']['type'] == 'STT_NOTYPE' and \ + sym.entry['st_info']['bind'] == 'STB_GLOBAL' and \ + sym.entry['st_shndx'] == 'SHN_UNDEF': + + self.log.debug(f"found imported symbol '{sym.name}' at index {i}") + imports.append((i, sym)) + + i += 1 + return imports + + def _prepare_inner(self): + #1) Locate the symbol table + symtab = self._find_symtab() + if symtab is None: + self.log.error("no symbol table found in file") + return 1 + + #2) Find imported symbols in symbol table + imports = self._find_imports_in_symtab(symtab) + self.log.info(f"LLEXT has {len(imports)} import(s)") + + #3) Write SLIDs in each symbol's 'st_value' field + def make_stvalue_reader_writer(): + byteorder = "little" if self.elf.little_endian else "big" + if self.elf.elfclass == 32: + sizeof_Elf_Sym = 0x10 #sizeof(Elf32_Sym) + offsetof_st_value = 0x4 #offsetof(Elf32_Sym, st_value) + sizeof_st_value = 0x4 #sizeof(Elf32_Sym.st_value) + else: + sizeof_Elf_Sym = 0x18 + offsetof_st_value = 0x8 + sizeof_st_value = 0x8 + + def seek(symidx): + self.elf_fd.seek( + symtab['sh_offset'] + + symidx * sizeof_Elf_Sym + + offsetof_st_value) + + def reader(symbol_index): + seek(symbol_index) + return int.from_bytes(self.elf_fd.read(sizeof_st_value), byteorder) + + def writer(symbol_index, st_value): + seek(symbol_index) + self.elf_fd.write(int.to_bytes(st_value, sizeof_st_value, byteorder)) + + return reader, writer + + rd_st_val, wr_st_val = make_stvalue_reader_writer() + slid_size = self.elf.elfclass // 8 + + for (index, symbol) in imports: + slid = llext_slidlib.generate_slid(symbol.name, slid_size) + slid_as_str = llext_slidlib.format_slid(slid, slid_size) + msg = f"{symbol.name} -> {slid_as_str}" + + self.log.info(msg) + + # Make sure we're not overwriting something actually important + original_st_value = rd_st_val(index) + if original_st_value != 0: + self.log.error(f"unexpected non-zero st_value for symbol {symbol.name}") + return 1 + + wr_st_val(index, slid) + + return 0 + + def prepare_llext(self): + res = self._prepare_inner() + self.elf_fd.close() + return res + +# Disable duplicate code warning for the code that follows, +# as it is expected for these functions to be similar. +# pylint: disable=duplicate-code +def _parse_args(argv): + """Parse the command line arguments.""" + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + allow_abbrev=False) + + parser.add_argument("-f", "--elf-file", required=True, + help="LLEXT ELF file to process") + parser.add_argument("-o", "--output-file", + help=("Additional output file where processed ELF " + "will be copied")) + parser.add_argument("-sl", "--slid-listing", + help="write the SLID listing to a file") + parser.add_argument("-v", "--verbose", action="count", + help=("enable verbose output, can be used multiple times " + "to increase verbosity level")) + parser.add_argument("--always-succeed", action="store_true", + help="always exit with a return code of 0, used for testing") + + return parser.parse_args(argv) + +def _init_log(verbose): + """Initialize a logger object.""" + log = logging.getLogger(__file__) + + console = logging.StreamHandler() + console.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) + log.addHandler(console) + + if verbose and verbose > 1: + log.setLevel(logging.DEBUG) + elif verbose and verbose > 0: + log.setLevel(logging.INFO) + else: + log.setLevel(logging.WARNING) + + return log + +def main(argv=None): + args = _parse_args(argv) + + log = _init_log(args.verbose) + + log.info(f"inject_slids_in_llext: {args.elf_file}") + + preparator = LLEXTSymtabPreparator(args.elf_file, log) + + res = preparator.prepare_llext() + + if args.always_succeed: + return 0 + + if res == 0 and args.output_file: + shutil.copy(args.elf_file, args.output_file) + + return res + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/scripts/build/llext_prepare_exptab.py b/scripts/build/llext_prepare_exptab.py new file mode 100755 index 00000000000..c659709af17 --- /dev/null +++ b/scripts/build/llext_prepare_exptab.py @@ -0,0 +1,377 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024 STMicroelectronics +# SPDX-License-Identifier: Apache-2.0 + +""" +Script to prepare the LLEXT exports table of a Zephyr ELF + +This script performs compile-time processing of the LLEXT exports +table for usage at runtime by the LLEXT subsystem code. The table +is a special section filled with 'llext_const_symbol' structures +generated by the EXPORT_SYMBOL macro. + +Currently, the preparatory work consists mostly of sorting the +exports table to allow usage of binary search algorithms at runtime. +If CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID option is enabled, SLIDs +of all exported functions are also injected in the export table by +this script. (In this case, the preparation process is destructive) +""" + +import llext_slidlib + +from elftools.elf.elffile import ELFFile +from elftools.elf.sections import Section + +import argparse +import logging +import pathlib +import struct +import sys + +#!!!!! WARNING !!!!! +# +#These constants MUST be kept in sync with the linker scripts +#and the EXPORT_SYMBOL macro located in 'subsys/llext/llext.h'. +#Otherwise, the LLEXT subsystem will be broken! +# +#!!!!! WARNING !!!!! + +LLEXT_EXPORT_TABLE_SECTION_NAME = "llext_const_symbol_area" +LLEXT_EXPORT_NAMES_SECTION_NAME = "llext_exports_strtab" + +def _llext_const_symbol_struct(ptr_size: int, endianness: str): + """ + ptr_size -- Platform pointer size in bytes + endianness -- Platform endianness ('little'/'big') + """ + endspec = "<" if endianness == 'little' else ">" + if ptr_size == 4: + ptrspec = "I" + elif ptr_size == 8: + ptrspec = "Q" + + # struct llext_const_symbol + # contains just two pointers. + lcs_spec = endspec + 2 * ptrspec + return struct.Struct(lcs_spec) + +#ELF Shdr flag applied to the export table section, to indicate +#the section has already been prepared by this script. This is +#mostly a security measure to prevent the script from running +#twice on the same ELF file, which can result in catastrophic +#failures if SLID-based linking is enabled (in this case, the +#preparation process is destructive). +# +#This flag is part of the SHF_MASKOS mask, of which all bits +#are "reserved for operating system-specific semantics". +#See: https://refspecs.linuxbase.org/elf/gabi4+/ch4.sheader.html +SHF_LLEXT_PREPARATION_DONE = 0x08000000 + +class SectionDescriptor(): + """ELF Section descriptor + + This is a wrapper class around pyelftools' "Section" object. + """ + def __init__(self, elffile, section_name): + self.name = section_name + self.section = elffile.get_section_by_name(section_name) + if not isinstance(self.section, Section): + raise KeyError(f"section {section_name} not found") + + self.shdr_index = elffile.get_section_index(section_name) + self.shdr_offset = elffile['e_shoff'] + \ + self.shdr_index * elffile['e_shentsize'] + self.size = self.section['sh_size'] + self.flags = self.section['sh_flags'] + self.offset = self.section['sh_offset'] + +class LLEXTExptabManipulator(): + """Class used to wrap the LLEXT export table manipulation.""" + def __init__(self, elf_fd, exptab_file_offset, lcs_struct, exports_count): + self.fd = elf_fd + self.exports_count = exports_count + self.base_offset = exptab_file_offset + self.lcs_struct = lcs_struct + + def _seek_to_sym(self, index): + self.fd.seek(self.base_offset + index * self.lcs_struct.size) + + def __getitem__(self, index): + if not isinstance(index, int): + raise TypeError(f"invalid type {type(index)} for index") + + if index >= self.exports_count: + raise IndexError(f"index {index} is out of bounds (max {self.exports_count})") + + self._seek_to_sym(index) + return self.lcs_struct.unpack(self.fd.read(self.lcs_struct.size)) + + def __setitem__(self, index, item): + if not isinstance(index, int): + raise TypeError(f"invalid type {type(index)} for index") + + if index >= self.exports_count: + raise IndexError(f"index {index} is out of bounds (max {self.exports_count})") + + (addr_or_slid, sym_addr) = item + + self._seek_to_sym(index) + self.fd.write(self.lcs_struct.pack(addr_or_slid, sym_addr)) + +class ZephyrElfExptabPreparator(): + """Prepares the LLEXT export table of a Zephyr ELF. + + Attributes: + elf_path: path to the Zephyr ELF to prepare + log: a logging.Logger object + slid_listing_path: path to the file where SLID listing should be saved + """ + def __init__(self, elf_path: str, log: logging.Logger, slid_listing_path: str | None): + self.elf_path = elf_path + self.elf_fd = open(self.elf_path, 'rb+') + self.elf = ELFFile(self.elf_fd) + self.log = log + + # Lazy-open the SLID listing file to ensure it is only created when necessary + self.slid_listing_path = slid_listing_path + self.slid_listing_fd = None + + def _prepare_exptab_for_slid_linking(self): + """ + IMPLEMENTATION NOTES: + In the linker script, we declare the export names table + as starting at address 0. Thanks to this, all "pointers" + to that section are equal to the offset inside the section. + Also note that symbol names are always NUL-terminated. + + The export table is sorted by SLID in ASCENDING order. + """ + def read_symbol_name(name_ptr): + raw_name = b'' + self.elf_fd.seek(self.expstrtab_section.offset + name_ptr) + + c = self.elf_fd.read(1) + while c != b'\0': + raw_name += c + c = self.elf_fd.read(1) + + return raw_name.decode("utf-8") + + #1) Load the export table + exports_list = [] + for (name_ptr, export_address) in self.exptab_manipulator: + export_name = read_symbol_name(name_ptr) + exports_list.append((export_name, export_address)) + + #2) Generate the SLID for all exports + collided = False + sorted_exptab = dict() + for export_name, export_addr in exports_list: + slid = llext_slidlib.generate_slid(export_name, self.ptrsize) + + collision = sorted_exptab.get(slid) + if collision: + #Don't abort immediately on collision: if there are others, we want to log them all. + self.log.error(f"SLID collision: {export_name} and {collision[0]} have the same SLID 0x{slid:X}") + collided = True + else: + sorted_exptab[slid] = (export_name, export_addr) + + if collided: + return 1 + + #3) Sort the export table (order specified above) + sorted_exptab = dict(sorted(sorted_exptab.items())) + + #4) Write the updated export table to ELF, and dump + #to SLID listing if requested by caller + if self.slid_listing_path: + self.slid_listing_fd = open(self.slid_listing_path, "w") + + def slidlist_write(msg): + if self.slid_listing_fd: + self.slid_listing_fd.write(msg + "\n") + + slidlist_write(f"/* SLID listing generated by {__file__} */") + slidlist_write("//") + slidlist_write("// This file contains the 'SLID -> name' mapping for all") + slidlist_write("// symbols exported to LLEXT by this Zephyr executable.") + slidlist_write("") + + self.log.info("SLID -> export name mapping:") + + i = 0 + for (slid, name_and_symaddr) in sorted_exptab.items(): + slid_as_str = llext_slidlib.format_slid(slid, self.ptrsize) + msg = f"{slid_as_str} -> {name_and_symaddr[0]}" + self.log.info(msg) + slidlist_write(msg) + + self.exptab_manipulator[i] = (slid, name_and_symaddr[1]) + i += 1 + + if self.slid_listing_fd: + self.slid_listing_fd.close() + + return 0 + + def _prepare_exptab_for_str_linking(self): + #TODO: sort the export table by symbol + # name to allow binary search too + # + # Plan of action: + # 1) Locate in which section the names are located + # 2) Load the export table and resolve names + # 3) Sort the exports by name + # WARN: THIS MUST USE THE SAME SORTING RULES + # AS LLEXT CODE OR DICHOTOMIC SEARCH WILL BREAK + # Using a custom sorting function might be required. + # 4) Write back the updated export table + # + # N.B.: reusing part of the code in _prepare_elf_for_slid_linking + # might be possible and desireable. + # + # As of writing, this function will never be called as this script + # is only called if CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID is enabled, + # which makes _prepare_exptab_for_slid_linking be called instead. + # + self.log.warn(f"_prepare_exptab_for_str_linking: do nothing") + return 0 + + def _set_prep_done_shdr_flag(self): + #Offset and size of the 'sh_flags' member of + #the Elf_Shdr structure. The offset does not + #change between ELF32 and ELF64. Size in both + #is equal to pointer size (4 bytes for ELF32, + #8 bytes for ELF64). + SHF_OFFSET = 8 + SHF_SIZE = self.ptrsize + + off = self.exptab_section.shdr_offset + SHF_OFFSET + + #Read existing sh_flags, set the PREPARATION_DONE flag + #and write back the new value. + self.elf_fd.seek(off) + sh_flags = int.from_bytes(self.elf_fd.read(SHF_SIZE), self.endianness) + + sh_flags |= SHF_LLEXT_PREPARATION_DONE + + self.elf_fd.seek(off) + self.elf_fd.write(int.to_bytes(sh_flags, self.ptrsize, self.endianness)) + + def _prepare_inner(self): + # Locate the export table section + try: + self.exptab_section = SectionDescriptor( + self.elf, LLEXT_EXPORT_TABLE_SECTION_NAME) + except KeyError as e: + self.log.error(e.args[0]) + return 1 + + # Abort if the ELF has already been processed + if (self.exptab_section.flags & SHF_LLEXT_PREPARATION_DONE) != 0: + self.log.warning("exptab section flagged with LLEXT_PREPARATION_DONE " + "- not preparing again") + return 0 + + # Get the struct.Struct for export table entry + self.ptrsize = self.elf.elfclass // 8 + self.endianness = 'little' if self.elf.little_endian else 'big' + self.lcs_struct = _llext_const_symbol_struct(self.ptrsize, self.endianness) + + # Verify that the export table size is coherent + if (self.exptab_section.size % self.lcs_struct.size) != 0: + self.log.error(f"export table size (0x{self.exptab_section.size:X}) " + f"not aligned to 'llext_const_symbol' size (0x{self.lcs_struct.size:X})") + return 1 + + # Create the export table manipulator + num_exports = self.exptab_section.size // self.lcs_struct.size + self.exptab_manipulator = LLEXTExptabManipulator( + self.elf_fd, self.exptab_section.offset, self.lcs_struct, num_exports) + + # Attempt to locate the export names section + try: + self.expstrtab_section = SectionDescriptor( + self.elf, LLEXT_EXPORT_NAMES_SECTION_NAME) + except KeyError: + self.expstrtab_section = None + + self.log.debug(f"exports table section at file offset 0x{self.exptab_section.offset:X}") + if self.expstrtab_section: + self.log.debug(f"exports strtab section at file offset 0x{self.expstrtab_section.offset:X}") + else: + self.log.debug("no exports strtab section in ELF") + self.log.info(f"{num_exports} symbols are exported to LLEXTs by this ELF") + + # Perform the export table preparation + if self.expstrtab_section: + res = self._prepare_exptab_for_slid_linking() + else: + res = self._prepare_exptab_for_str_linking() + + if res == 0: # Add the "prepared" flag to export table section + self._set_prep_done_shdr_flag() + + def prepare_elf(self): + res = self._prepare_inner() + self.elf_fd.close() + return res + +# pylint: disable=duplicate-code +def _parse_args(argv): + """Parse the command line arguments.""" + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + allow_abbrev=False) + + parser.add_argument("-f", "--elf-file", default=pathlib.Path("build", "zephyr", "zephyr.elf"), + help="ELF file to process") + parser.add_argument("-sl", "--slid-listing", + help=("write the SLID listing to a file (only useful" + "when CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID is enabled) ")) + parser.add_argument("-v", "--verbose", action="count", + help=("enable verbose output, can be used multiple times " + "to increase verbosity level")) + parser.add_argument("--always-succeed", action="store_true", + help="always exit with a return code of 0, used for testing") + + return parser.parse_args(argv) + +def _init_log(verbose): + """Initialize a logger object.""" + log = logging.getLogger(__file__) + + console = logging.StreamHandler() + console.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) + log.addHandler(console) + + if verbose and verbose > 1: + log.setLevel(logging.DEBUG) + elif verbose and verbose > 0: + log.setLevel(logging.INFO) + else: + log.setLevel(logging.WARNING) + + return log + +def main(argv=None): + args = _parse_args(argv) + + log = _init_log(args.verbose) + + log.info(f"prepare_llext_exptab: {args.elf_file}") + + preparator = ZephyrElfExptabPreparator(args.elf_file, log, args.slid_listing) + + res = preparator.prepare_elf() + + if args.always_succeed: + return 0 + + return res + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/scripts/build/llext_slidlib.py b/scripts/build/llext_slidlib.py new file mode 100755 index 00000000000..99157c526dc --- /dev/null +++ b/scripts/build/llext_slidlib.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024 STMicroelectronics +# SPDX-License-Identifier: Apache-2.0 + +""" +This file implements the Symbol Link Identifer (SLID) +generation code, for use by the LLEXT subsystem. +SLID-based linking is enabled by the Kconfig +option 'CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID'. + +When executed as a script, this file can be used as +an interactive prompt to calculate the SLID of arbitrary +symbols, which can be useful for debugging purposes. + +IMPLEMENTATION NOTES: + Currently, SLIDs are generated by taking the first + [pointer size] bytes of the symbol name's SHA-256 + hash, taken in big-endian order. + + This ordering provides one advantage: the 32-bit + SLID for an export is present in the top 32 bits of + the 64-bit SLID for the same export. +""" + +from hashlib import sha256 + +def generate_slid(symbol_name: str, slid_size: int) -> int: + """ + Generates the Symbol Link Identifier (SLID) for a symbol. + + symbol_name: Name of the symbol for which to generate a SLID + slid_side: Size of the SLID in bytes (4/8) + """ + if slid_size not in (4, 8): + raise AssertionError(f"Invalid SLID size {slid_size}") + + m = sha256() + m.update(symbol_name.encode("utf-8")) + hash = m.digest() + return int.from_bytes(hash[0:slid_size], byteorder='big', signed=False) + +def format_slid(slid: int, slid_size: int) -> str: + if slid_size == 4: + fmt = f"0x{slid:08X}" + elif slid_size == 8: + fmt = f"0x{slid:016X}" + return fmt + +def repl(): + while True: + sym_name = input("Symbol name? ") + slid32 = generate_slid(sym_name, 4) + slid64 = generate_slid(sym_name, 8) + print(f" 32-bit SLID for '{sym_name}': {format_slid(slid32, 4)}") + print(f" 64-bit SLID for '{sym_name}': {format_slid(slid64, 8)}") + print() + +if __name__ == "__main__": + print("LLEXT SLID calculation REPL") + print("Press ^C to exit.") + try: + repl() + except KeyboardInterrupt: + print() diff --git a/soc/andestech/ae350/linker.ld b/soc/andestech/ae350/linker.ld index 9098aa2072f..9419856a499 100644 --- a/soc/andestech/ae350/linker.ld +++ b/soc/andestech/ae350/linker.ld @@ -92,6 +92,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /* * The .plt and .iplt are here according to * 'riscv32-zephyr-elf-ld --verbose', before text section. diff --git a/soc/cdns/dc233c/include/xtensa-dc233c.ld b/soc/cdns/dc233c/include/xtensa-dc233c.ld index 87ad18b5424..6ca62979633 100644 --- a/soc/cdns/dc233c/include/xtensa-dc233c.ld +++ b/soc/cdns/dc233c/include/xtensa-dc233c.ld @@ -113,6 +113,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + #ifdef CONFIG_GEN_ISR_TABLES #include #endif diff --git a/soc/cdns/xtensa_sample_controller/include/xtensa-sample-controller.ld b/soc/cdns/xtensa_sample_controller/include/xtensa-sample-controller.ld index 842ec8141f4..bd93a0861b4 100644 --- a/soc/cdns/xtensa_sample_controller/include/xtensa-sample-controller.ld +++ b/soc/cdns/xtensa_sample_controller/include/xtensa-sample-controller.ld @@ -177,6 +177,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + #ifdef CONFIG_GEN_ISR_TABLES #include #endif diff --git a/soc/espressif/esp32/default.ld b/soc/espressif/esp32/default.ld index 338a90b9964..4e07ecf46de 100644 --- a/soc/espressif/esp32/default.ld +++ b/soc/espressif/esp32/default.ld @@ -152,6 +152,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT + #include +#endif + /* --- RTC BEGIN --- */ /* RTC fast memory holds RTC wake stub code, diff --git a/soc/espressif/esp32/default_appcpu.ld b/soc/espressif/esp32/default_appcpu.ld index a28399f57c4..57a35c47450 100644 --- a/soc/espressif/esp32/default_appcpu.ld +++ b/soc/espressif/esp32/default_appcpu.ld @@ -55,6 +55,10 @@ SECTIONS { #include +#ifdef CONFIG_LLEXT +#include +#endif + _image_iram_start = LOADADDR(.iram0.vectors); _image_iram_size = LOADADDR(_TEXT_SECTION_NAME) + SIZEOF(_TEXT_SECTION_NAME) - _image_iram_start; _image_iram_vaddr = ADDR(.iram0.vectors); diff --git a/soc/espressif/esp32c3/default.ld b/soc/espressif/esp32c3/default.ld index 17e88337355..849f568f1ae 100644 --- a/soc/espressif/esp32c3/default.ld +++ b/soc/espressif/esp32c3/default.ld @@ -129,6 +129,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT + #include +#endif + /* --- START OF RTC --- */ .rtc.text : diff --git a/soc/espressif/esp32s2/default.ld b/soc/espressif/esp32s2/default.ld index 0799af2ea4a..03c1d75a927 100644 --- a/soc/espressif/esp32s2/default.ld +++ b/soc/espressif/esp32s2/default.ld @@ -136,6 +136,10 @@ SECTIONS /* Virtual non-loadable sections */ #include +#ifdef CONFIG_LLEXT +#include +#endif + /* --- START OF RTC --- */ /* RTC fast memory holds RTC wake stub code, diff --git a/soc/espressif/esp32s3/default.ld b/soc/espressif/esp32s3/default.ld index 93c9c3044b9..17277f5ebcc 100644 --- a/soc/espressif/esp32s3/default.ld +++ b/soc/espressif/esp32s3/default.ld @@ -135,6 +135,10 @@ SECTIONS /* Virtual non-loadable sections */ #include +#ifdef CONFIG_LLEXT +#include +#endif + /* --- START OF RTC --- */ /* RTC fast memory holds RTC wake stub code */ diff --git a/soc/espressif/esp32s3/default_appcpu.ld b/soc/espressif/esp32s3/default_appcpu.ld index d5e0e2fe3e9..efc684161b5 100644 --- a/soc/espressif/esp32s3/default_appcpu.ld +++ b/soc/espressif/esp32s3/default_appcpu.ld @@ -38,6 +38,10 @@ SECTIONS { #include + #ifdef CONFIG_LLEXT + #include + #endif + /* Send .iram0 code to iram */ .iram0.vectors : ALIGN(4) { diff --git a/soc/infineon/cat1b/cyw20829/linker.ld b/soc/infineon/cat1b/cyw20829/linker.ld index 11b36b51170..efe1dffa603 100644 --- a/soc/infineon/cat1b/cyw20829/linker.ld +++ b/soc/infineon/cat1b/cyw20829/linker.ld @@ -104,6 +104,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /* * .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose', * before text section. diff --git a/soc/intel/intel_adsp/ace/ace-link.ld b/soc/intel/intel_adsp/ace/ace-link.ld index 454893055e1..b259c842995 100644 --- a/soc/intel/intel_adsp/ace/ace-link.ld +++ b/soc/intel/intel_adsp/ace/ace-link.ld @@ -490,4 +490,8 @@ SECTIONS { #ifdef CONFIG_GEN_ISR_TABLES #include #endif + +#ifdef CONFIG_LLEXT +#include +#endif } diff --git a/soc/intel/intel_adsp/cavs/include/xtensa-cavs-linker.ld b/soc/intel/intel_adsp/cavs/include/xtensa-cavs-linker.ld index e9ecbe2ba1b..03109061fc6 100644 --- a/soc/intel/intel_adsp/cavs/include/xtensa-cavs-linker.ld +++ b/soc/intel/intel_adsp/cavs/include/xtensa-cavs-linker.ld @@ -503,4 +503,8 @@ SECTIONS { #ifdef CONFIG_GEN_ISR_TABLES #include #endif + +#ifdef CONFIG_LLEXT +#include +#endif } diff --git a/soc/ite/ec/it8xxx2/linker.ld b/soc/ite/ec/it8xxx2/linker.ld index 3ad9bd21df3..8143d789d20 100644 --- a/soc/ite/ec/it8xxx2/linker.ld +++ b/soc/ite/ec/it8xxx2/linker.ld @@ -91,6 +91,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /* * The .plt and .iplt are here according to * 'riscv32-zephyr-elf-ld --verbose', before text section. diff --git a/soc/nxp/imx/imx8/adsp/linker.ld b/soc/nxp/imx/imx8/adsp/linker.ld index 52d3a46b238..caaaf8e60d1 100644 --- a/soc/nxp/imx/imx8/adsp/linker.ld +++ b/soc/nxp/imx/imx8/adsp/linker.ld @@ -173,6 +173,10 @@ SECTIONS { #include + +#ifdef CONFIG_LLEXT +#include +#endif .ResetVector.text : ALIGN(4) { _ResetVector_text_start = ABSOLUTE(.); diff --git a/soc/nxp/imx/imx8m/adsp/linker.ld b/soc/nxp/imx/imx8m/adsp/linker.ld index 97d969d476f..6ca8e72982e 100644 --- a/soc/nxp/imx/imx8m/adsp/linker.ld +++ b/soc/nxp/imx/imx8m/adsp/linker.ld @@ -179,6 +179,10 @@ SECTIONS #endif #include + +#ifdef CONFIG_LLEXT +#include +#endif .ResetVector.text : ALIGN(4) { _ResetVector_text_start = ABSOLUTE(.); diff --git a/soc/nxp/imx/imx8ulp/adsp/linker.ld b/soc/nxp/imx/imx8ulp/adsp/linker.ld index 4b5aec29742..96bd687917f 100644 --- a/soc/nxp/imx/imx8ulp/adsp/linker.ld +++ b/soc/nxp/imx/imx8ulp/adsp/linker.ld @@ -173,6 +173,10 @@ SECTIONS { #include + +#ifdef CONFIG_LLEXT +#include +#endif .ResetVector.text : ALIGN(4) { _ResetVector_text_start = ABSOLUTE(.); diff --git a/soc/nxp/imx/imx8x/adsp/linker.ld b/soc/nxp/imx/imx8x/adsp/linker.ld index 52d3a46b238..caaaf8e60d1 100644 --- a/soc/nxp/imx/imx8x/adsp/linker.ld +++ b/soc/nxp/imx/imx8x/adsp/linker.ld @@ -173,6 +173,10 @@ SECTIONS { #include + +#ifdef CONFIG_LLEXT +#include +#endif .ResetVector.text : ALIGN(4) { _ResetVector_text_start = ABSOLUTE(.); diff --git a/soc/nxp/imx/imx9/a55/linker.ld b/soc/nxp/imx/imx9/a55/linker.ld index 102d9f1b044..53982a309c8 100644 --- a/soc/nxp/imx/imx9/a55/linker.ld +++ b/soc/nxp/imx/imx9/a55/linker.ld @@ -71,6 +71,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + /* * .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose', * before text section. diff --git a/soc/nxp/imxrt/imxrt5xx/f1/linker.ld b/soc/nxp/imxrt/imxrt5xx/f1/linker.ld index adc8b87fb62..de1e8984653 100644 --- a/soc/nxp/imxrt/imxrt5xx/f1/linker.ld +++ b/soc/nxp/imxrt/imxrt5xx/f1/linker.ld @@ -134,6 +134,10 @@ SECTIONS { #include + +#ifdef CONFIG_LLEXT +#include +#endif .ResetVector.text : ALIGN(4) { _ResetVector_text_start = ABSOLUTE(.); diff --git a/soc/openisa/rv32m1/linker.ld b/soc/openisa/rv32m1/linker.ld index 784ba791722..b81a77868a9 100644 --- a/soc/openisa/rv32m1/linker.ld +++ b/soc/openisa/rv32m1/linker.ld @@ -87,6 +87,10 @@ SECTIONS #include +#ifdef CONFIG_LLEXT +#include +#endif + SECTION_PROLOGUE(.plt,,) { *(.plt) diff --git a/soc/st/stm32/stm32mp1x/linker.ld b/soc/st/stm32/stm32mp1x/linker.ld index 7f9a84501ef..83f3511b708 100644 --- a/soc/st/stm32/stm32mp1x/linker.ld +++ b/soc/st/stm32/stm32mp1x/linker.ld @@ -13,6 +13,11 @@ SECTIONS { #include + +#ifdef CONFIG_LLEXT +#include +#endif + #ifdef CONFIG_OPENAMP_RSC_TABLE SECTION_PROLOGUE(.resource_table,, SUBALIGN(4)) diff --git a/subsys/llext/Kconfig b/subsys/llext/Kconfig index 8984cb3ca39..4ad2aca1c03 100644 --- a/subsys/llext/Kconfig +++ b/subsys/llext/Kconfig @@ -66,6 +66,21 @@ config LLEXT_STORAGE_WRITABLE Select if LLEXT storage is writable, i.e. if extensions are stored in RAM and can be modified in place +config LLEXT_EXPORT_BUILTINS_BY_SLID + bool "Export built-in symbols to llexts via SLIDs" + help + When enabled, symbols exported from the Zephyr kernel + or application (via EXPORT_SYMBOL) are linked to LLEXTs + via Symbol Link Identifiers (SLIDs) instead of name. + + Enabling this option provides a huge size reduction, + makes the linking process faster and provides more + confidentiality, as exported symbol names are dropped + from the binary. However, it can make LLEXT debugging + harder and prevents usage of 'llext_find_sym' to look + up symbols from the built-in table by name. It also + requires the LLEXTs to be post-processed after build. + module = LLEXT module-str = llext source "subsys/logging/Kconfig.template.log_config" diff --git a/subsys/llext/llext.c b/subsys/llext/llext.c index 856b322c49a..a647ac3bc27 100644 --- a/subsys/llext/llext.c +++ b/subsys/llext/llext.c @@ -24,6 +24,12 @@ LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL); #define LLEXT_PAGE_SIZE 32 #endif +#ifdef CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID +#define SYM_NAME_OR_SLID(name, slid) ((const char *)slid) +#else +#define SYM_NAME_OR_SLID(name, slid) name +#endif + K_HEAP_DEFINE(llext_heap, CONFIG_LLEXT_HEAP_SIZE * 1024); static const char ELF_MAGIC[] = {0x7f, 'E', 'L', 'F'}; @@ -118,11 +124,27 @@ const void *llext_find_sym(const struct llext_symtable *sym_table, const char *s { if (sym_table == NULL) { /* Built-in symbol table */ +#ifdef CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID + /* 'sym_name' is actually a SLID to search for */ + uintptr_t slid = (uintptr_t)sym_name; + + /* TODO: perform a binary search instead of linear. + * Note that - as of writing - the llext_const_symbol_area + * section is sorted in ascending SLID order. + * (see scripts/build/llext_prepare_exptab.py) + */ + STRUCT_SECTION_FOREACH(llext_const_symbol, sym) { + if (slid == sym->slid) { + return sym->addr; + } + } +#else STRUCT_SECTION_FOREACH(llext_const_symbol, sym) { if (strcmp(sym->name, sym_name) == 0) { return sym->addr; } } +#endif } else { /* find symbols in module */ for (size_t i = 0; i < sym_table->sym_cnt; i++) { @@ -728,7 +750,8 @@ static void llext_link_plt(struct llext_loader *ldr, struct llext *ext, switch (stb) { case STB_GLOBAL: - link_addr = llext_find_sym(NULL, name); + link_addr = llext_find_sym(NULL, + SYM_NAME_OR_SLID(name, sym_tbl.st_value)); if (!link_addr) link_addr = llext_find_sym(&ext->sym_tab, name); @@ -864,7 +887,8 @@ static int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local link_addr = 0; } else if (sym.st_shndx == SHN_UNDEF) { /* If symbol is undefined, then we need to look it up */ - link_addr = (uintptr_t)llext_find_sym(NULL, name); + link_addr = (uintptr_t)llext_find_sym(NULL, + SYM_NAME_OR_SLID(name, sym.st_value)); if (link_addr == 0) { LOG_ERR("Undefined symbol with no entry in " diff --git a/tests/subsys/llext/simple/src/test_llext_simple.c b/tests/subsys/llext/simple/src/test_llext_simple.c index 68b318f5e1c..91a3c3e6f3d 100644 --- a/tests/subsys/llext/simple/src/test_llext_simple.c +++ b/tests/subsys/llext/simple/src/test_llext_simple.c @@ -24,6 +24,20 @@ LOG_MODULE_REGISTER(test_llext_simple); #define LLEXT_CONST const #endif +#ifdef CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID +#define LLEXT_FIND_BUILTIN_SYM(symbol_name) llext_find_sym(NULL, symbol_name ## _SLID) + +#ifdef CONFIG_64BIT +#define printk_SLID ((const char *)0x87B3105268827052ull) +#define z_impl_ext_syscall_fail_SLID ((const char *)0xD58BC0E7C64CD965ull) +#else +#define printk_SLID ((const char *)0x87B31052ull) +#define z_impl_ext_syscall_fail_SLID ((const char *)0xD58BC0E7ull) +#endif +#else +#define LLEXT_FIND_BUILTIN_SYM(symbol_name) llext_find_sym(NULL, # symbol_name) +#endif + struct llext_test { const char *name; bool try_userspace; @@ -271,7 +285,7 @@ ZTEST(llext, test_pre_located) */ ZTEST(llext, test_printk_exported) { - const void * const printk_fn = llext_find_sym(NULL, "printk"); + const void * const printk_fn = LLEXT_FIND_BUILTIN_SYM(printk); zassert_equal(printk_fn, printk, "printk should be an exported symbol"); } @@ -282,8 +296,9 @@ ZTEST(llext, test_printk_exported) */ ZTEST(llext, test_ext_syscall_fail) { - const void * const esf_fn = llext_find_sym(NULL, - "z_impl_ext_syscall_fail"); + const void * const esf_fn = LLEXT_FIND_BUILTIN_SYM(z_impl_ext_syscall_fail); + + zassert_not_null(esf_fn, "est_fn should not be NULL"); zassert_is_null(*(uintptr_t **)esf_fn, NULL, "ext_syscall_fail should be NULL"); diff --git a/tests/subsys/llext/simple/testcase.yaml b/tests/subsys/llext/simple/testcase.yaml index 6325bd51425..8a3aca97037 100644 --- a/tests/subsys/llext/simple/testcase.yaml +++ b/tests/subsys/llext/simple/testcase.yaml @@ -80,3 +80,71 @@ tests: - CONFIG_USERSPACE=y - CONFIG_MODULES=y - CONFIG_LLEXT_TEST_HELLO=m + llext.simple.readonly_slid_linking: + arch_exclude: xtensa # for now + filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE + extra_configs: + - arch:arm:CONFIG_ARM_MPU=n + - CONFIG_LLEXT_STORAGE_WRITABLE=n + - CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y + llext.simple.readonly_mpu_slid_linking: + min_ram: 128 + arch_exclude: xtensa # for now + filter: CONFIG_ARCH_HAS_USERSPACE + extra_configs: + - CONFIG_USERSPACE=y + - CONFIG_LLEXT_STORAGE_WRITABLE=n + - CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y + llext.simple.writable_slid_linking: + filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE + extra_configs: + - arch:arm:CONFIG_ARM_MPU=n + - CONFIG_LLEXT_STORAGE_WRITABLE=y + llext.simple.modules_enabled_writable_slid_linking: + filter: not CONFIG_MPU and not CONFIG_MMU + platform_key: + - simulation + - arch + platform_exclude: + - qemu_cortex_a9 # MMU + extra_configs: + - arch:arm:CONFIG_ARM_MPU=n + - CONFIG_MODULES=y + - CONFIG_LLEXT_STORAGE_WRITABLE=y + - CONFIG_LLEXT_TEST_HELLO=m + - CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y + llext.simple.modules_enabled_writable_relocatable_slid_linking: + arch_exclude: arm arm64 + filter: not CONFIG_MPU and not CONFIG_MMU + integration_platforms: + - qemu_xtensa + extra_configs: + - CONFIG_MODULES=y + - CONFIG_LLEXT_STORAGE_WRITABLE=y + - CONFIG_LLEXT_TYPE_ELF_RELOCATABLE=y + - CONFIG_LLEXT_TEST_HELLO=m + - CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y + llext.simple.modules_enabled_readonly_slid_linking: + filter: not CONFIG_MPU and not CONFIG_MMU + arch_exclude: xtensa # for now + platform_key: + - simulation + - arch + platform_exclude: + - qemu_cortex_a9 # MMU + extra_configs: + - arch:arm:CONFIG_ARM_MPU=n + - CONFIG_MODULES=y + - CONFIG_LLEXT_TEST_HELLO=m + - CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y + llext.simple.modules_enabled_readonly_mpu_slid_linking: + filter: CONFIG_ARCH_HAS_USERSPACE + arch_exclude: xtensa # for now + platform_key: + - simulation + - arch + extra_configs: + - CONFIG_USERSPACE=y + - CONFIG_MODULES=y + - CONFIG_LLEXT_TEST_HELLO=m + - CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y