197 lines
6.5 KiB
Python
Executable File
197 lines
6.5 KiB
Python
Executable File
#!/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:]))
|