You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
6.6 KiB
198 lines
6.6 KiB
#!/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): |
|
e_type = self.elf.header['e_type'] |
|
if e_type == 'ET_DYN': |
|
symtab_name = ".dynsym" |
|
elif e_type == 'ET_REL': |
|
symtab_name = ".symtab" |
|
else: |
|
self.log.error(f"unexpected ELF file type {e_type}") |
|
return None |
|
|
|
symtab = self.elf.get_section_by_name(symtab_name) |
|
if not isinstance(symtab, SymbolTableSection): |
|
self.log.debug(f"section {symtab_name} not found.") |
|
return None |
|
|
|
self.log.info(f"processing symbol table from '{symtab_name}'...") |
|
self.log.debug(f"(symbol table is at file offset 0x{symtab['sh_offset']:X})") |
|
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:]))
|
|
|