llext: add support for SLID-based linking
This commit introduces support for an alternate linking method in the LLEXT subsystem, called "SLID" (short for Symbol Link Identifier), enabled by the CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID Kconfig option. SLID-based linking uses a unique identifier (integer) to identify exported symbols, instead of using the symbol name as done currently. This approach provides several benefits: * linking is faster because the comparison operation to determine whether we found the correct symbol in the export table is now an integer compare, instead of a string compare * binary size is reduced as symbol names can be dropped from the binary * confidentiality is improved as a side-effect, as symbol names are no longer present in the binary Signed-off-by: Mathieu Choplain <mathieu.choplain@st.com>
This commit is contained in:
parent
05ad2565fa
commit
8aa6ae43ce
45 changed files with 981 additions and 7 deletions
|
@ -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
|
||||
$<IF:$<TARGET_EXISTS:native_runner_executable>,${BYPRODUCT_KERNEL_EXE_NAME},${BYPRODUCT_KERNEL_ELF_NAME}>
|
||||
|
|
|
@ -254,6 +254,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.plt)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
|||
$<TARGET_PROPERTY:bintools,elfconvert_flag_infile>${llext_pkg_input}
|
||||
$<TARGET_PROPERTY:bintools,elfconvert_flag_outfile>${llext_pkg_output}
|
||||
$<TARGET_PROPERTY:bintools,elfconvert_flag_final>
|
||||
COMMAND ${slid_inject_cmd}
|
||||
DEPENDS ${llext_proc_target} ${llext_pkg_input}
|
||||
)
|
||||
|
||||
|
@ -5467,6 +5486,7 @@ function(add_llext_target target_name)
|
|||
$<TARGET_PROPERTY:bintools,strip_flag_infile>${llext_pkg_input}
|
||||
$<TARGET_PROPERTY:bintools,strip_flag_outfile>${llext_pkg_output}
|
||||
$<TARGET_PROPERTY:bintools,strip_flag_final>
|
||||
COMMAND ${slid_inject_cmd}
|
||||
DEPENDS ${llext_proc_target} ${llext_pkg_input}
|
||||
)
|
||||
|
||||
|
|
|
@ -81,6 +81,10 @@ SECTIONS {
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
GROUP_START(ROMABLE_REGION)
|
||||
|
||||
SECTION_PROLOGUE(_TEXT_SECTION_NAME,,ALIGN(1024)) {
|
||||
|
|
|
@ -98,6 +98,10 @@ SECTIONS
|
|||
{
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose',
|
||||
* before text section.
|
||||
|
|
|
@ -113,6 +113,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose',
|
||||
* before text section.
|
||||
|
|
|
@ -77,6 +77,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose',
|
||||
* before text section.
|
||||
|
|
|
@ -46,6 +46,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
SECTION_PROLOGUE(_VECTOR_SECTION_NAME,,)
|
||||
{
|
||||
. = ALIGN(0x1000);
|
||||
|
|
|
@ -85,6 +85,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* .plt and .iplt are here according to
|
||||
* 'nios2-zephyr-elf-ld --verbose', before text section.
|
||||
|
|
|
@ -21,6 +21,10 @@
|
|||
SECTIONS
|
||||
{
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
SECTION_PROLOGUE(rom_start,,)
|
||||
{
|
||||
/* Located in generated directory. This file is populated by the
|
||||
|
|
|
@ -122,6 +122,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The .plt and .iplt are here according to
|
||||
* 'riscv32-zephyr-elf-ld --verbose', before text section.
|
||||
|
|
|
@ -23,6 +23,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
__rom_region_start = .;
|
||||
|
||||
SECTION_PROLOGUE(_TEXT_SECTION_NAME,,)
|
||||
|
|
|
@ -77,6 +77,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.plt)
|
||||
|
|
|
@ -236,4 +236,7 @@ SECTIONS
|
|||
.shstrtab 0 : { *(.shstrtab) }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
}
|
||||
|
|
19
include/zephyr/linker/llext-sections.ld
Normal file
19
include/zephyr/linker/llext-sections.ld
Normal file
|
@ -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
|
|
@ -10,6 +10,7 @@
|
|||
#include <zephyr/sys/iterable_sections.h>
|
||||
#include <zephyr/toolchain.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
|
|
196
scripts/build/llext_inject_slids.py
Executable file
196
scripts/build/llext_inject_slids.py
Executable file
|
@ -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:]))
|
377
scripts/build/llext_prepare_exptab.py
Executable file
377
scripts/build/llext_prepare_exptab.py
Executable file
|
@ -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:]))
|
65
scripts/build/llext_slidlib.py
Executable file
65
scripts/build/llext_slidlib.py
Executable file
|
@ -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()
|
|
@ -92,6 +92,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The .plt and .iplt are here according to
|
||||
* 'riscv32-zephyr-elf-ld --verbose', before text section.
|
||||
|
|
|
@ -113,6 +113,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GEN_ISR_TABLES
|
||||
#include <zephyr/linker/intlist.ld>
|
||||
#endif
|
||||
|
|
|
@ -177,6 +177,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GEN_ISR_TABLES
|
||||
#include <zephyr/linker/intlist.ld>
|
||||
#endif
|
||||
|
|
|
@ -152,6 +152,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/* --- RTC BEGIN --- */
|
||||
|
||||
/* RTC fast memory holds RTC wake stub code,
|
||||
|
|
|
@ -55,6 +55,10 @@ SECTIONS
|
|||
{
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#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);
|
||||
|
|
|
@ -129,6 +129,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/* --- START OF RTC --- */
|
||||
|
||||
.rtc.text :
|
||||
|
|
|
@ -136,6 +136,10 @@ SECTIONS
|
|||
/* Virtual non-loadable sections */
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/* --- START OF RTC --- */
|
||||
|
||||
/* RTC fast memory holds RTC wake stub code,
|
||||
|
|
|
@ -135,6 +135,10 @@ SECTIONS
|
|||
/* Virtual non-loadable sections */
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/* --- START OF RTC --- */
|
||||
|
||||
/* RTC fast memory holds RTC wake stub code */
|
||||
|
|
|
@ -38,6 +38,10 @@ SECTIONS
|
|||
{
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/* Send .iram0 code to iram */
|
||||
.iram0.vectors : ALIGN(4)
|
||||
{
|
||||
|
|
|
@ -104,6 +104,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose',
|
||||
* before text section.
|
||||
|
|
|
@ -490,4 +490,8 @@ SECTIONS {
|
|||
#ifdef CONFIG_GEN_ISR_TABLES
|
||||
#include <zephyr/linker/intlist.ld>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -503,4 +503,8 @@ SECTIONS {
|
|||
#ifdef CONFIG_GEN_ISR_TABLES
|
||||
#include <zephyr/linker/intlist.ld>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -91,6 +91,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The .plt and .iplt are here according to
|
||||
* 'riscv32-zephyr-elf-ld --verbose', before text section.
|
||||
|
|
|
@ -173,6 +173,10 @@ SECTIONS
|
|||
{
|
||||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
.ResetVector.text : ALIGN(4)
|
||||
{
|
||||
_ResetVector_text_start = ABSOLUTE(.);
|
||||
|
|
|
@ -179,6 +179,10 @@ SECTIONS
|
|||
#endif
|
||||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
.ResetVector.text : ALIGN(4)
|
||||
{
|
||||
_ResetVector_text_start = ABSOLUTE(.);
|
||||
|
|
|
@ -173,6 +173,10 @@ SECTIONS
|
|||
{
|
||||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
.ResetVector.text : ALIGN(4)
|
||||
{
|
||||
_ResetVector_text_start = ABSOLUTE(.);
|
||||
|
|
|
@ -173,6 +173,10 @@ SECTIONS
|
|||
{
|
||||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
.ResetVector.text : ALIGN(4)
|
||||
{
|
||||
_ResetVector_text_start = ABSOLUTE(.);
|
||||
|
|
|
@ -71,6 +71,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* .plt and .iplt are here according to 'arm-zephyr-elf-ld --verbose',
|
||||
* before text section.
|
||||
|
|
|
@ -134,6 +134,10 @@ SECTIONS
|
|||
{
|
||||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
.ResetVector.text : ALIGN(4)
|
||||
{
|
||||
_ResetVector_text_start = ABSOLUTE(.);
|
||||
|
|
|
@ -87,6 +87,10 @@ SECTIONS
|
|||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
SECTION_PROLOGUE(.plt,,)
|
||||
{
|
||||
*(.plt)
|
||||
|
|
|
@ -13,6 +13,11 @@ SECTIONS
|
|||
{
|
||||
|
||||
#include <zephyr/linker/rel-sections.ld>
|
||||
|
||||
#ifdef CONFIG_LLEXT
|
||||
#include <zephyr/linker/llext-sections.ld>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_OPENAMP_RSC_TABLE
|
||||
|
||||
SECTION_PROLOGUE(.resource_table,, SUBALIGN(4))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue