diff --git a/app/llext_relocatable.conf b/app/llext_relocatable.conf new file mode 100644 index 000000000000..76b5339e1bb0 --- /dev/null +++ b/app/llext_relocatable.conf @@ -0,0 +1 @@ +CONFIG_LLEXT_TYPE_ELF_RELOCATABLE=y diff --git a/scripts/llext_link_helper.py b/scripts/llext_link_helper.py index 6f1c994f7bb6..b1674e523784 100755 --- a/scripts/llext_link_helper.py +++ b/scripts/llext_link_helper.py @@ -11,7 +11,9 @@ import argparse import subprocess from elftools.elf.elffile import ELFFile +from elftools.elf.constants import SH_FLAGS import re +import pathlib args = None def parse_args(): @@ -29,59 +31,110 @@ def parse_args(): args = parser.parse_args() +def align_up(addr, align): + upper = addr + align - 1 + return upper - (upper % align) + +def max_alignment(addr, align1, align2): + if align2 > align1: + align1 = align2 + + upper = addr + align1 - 1 + return upper - (upper % align1) + def main(): parse_args() elf = ELFFile(open(args.file, 'rb')) text_addr = int(args.text_addr, 0) - p = re.compile(r'(^lib|\.so$)') - module = p.sub('', args.file) + text_size = 0 - if elf.num_segments() != 0: - # A shared object type image, it contains segments - sections = ['.text', '.rodata', '.data', '.bss'] - alignment = [0x1000, 0x1000, 0x10, 0x1000] + # File names differ when building shared or relocatable objects + if args.file[:-3] == '.so': + p = re.compile(r'(^lib|\.so$)') + fname = args.file else: - # A relocatable object, need to handle all sections separately - sections = ['.text', - f'._log_const.static.log_const_{module}_', - '.static_uuids', '.z_init_APPLICATION90_0_', '.module', - '.mod_buildinfo', '.data', '.trace_ctx', '.bss'] - alignment = [0x1000, 0x1000, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x1000] - - last_increment = 0 + fpath = pathlib.Path(args.file) + fname = fpath.name + p = re.compile(r'(^lib|_llext_lib\.obj$)') + module = p.sub('', fname) command = [args.command] - for i in range(len(sections)): - try: - offset = elf.get_section_by_name(sections[i]).header.sh_offset - size = elf.get_section_by_name(sections[i]).header.sh_size - except AttributeError: - print("section " + sections[i] + " not found in " + args.file) + writable = [] + readonly = [] + + # Create an object file with sections grouped by their properties, + # similar to how program segments are created: all executable sections, + # then all read-only data sections, and eventually all writable data + # sections like .data and .bss. Each group is aligned on a page boundary + # (0x1000) to make dynamic memory mapping possible. The resulting object + # file will either be a shared library or a relocatable (partially + # linked) object, depending on the build configuration. + for section in elf.iter_sections(): + s_flags = section.header['sh_flags'] + s_type = section.header['sh_type'] + s_name = section.name + s_size = section.header['sh_size'] + s_alignment = section.header['sh_addralign'] + + if not s_flags & SH_FLAGS.SHF_ALLOC: continue - if last_increment == 0: - # first section must be .text and it must be successful - if i != 0 or sections[i] != '.text': - break - - address = text_addr - elif alignment[i] != 0: - upper = offset + last_increment + alignment[i] - 1 - address = upper - (upper % alignment[i]) - else: - address = offset + last_increment + if (s_flags & (SH_FLAGS.SHF_ALLOC | SH_FLAGS.SHF_EXECINSTR) == + SH_FLAGS.SHF_ALLOC | SH_FLAGS.SHF_EXECINSTR and + s_type == 'SHT_PROGBITS'): + # An executable section, currently only a single .text is supported. + # In general additional executable sections are possible, e.g. + # .init. In the future support for arbitrary such sections can be + # added, similar to writable and read-only data below. + if s_name != '.text': + print(f"Warning! Non-standard executable section {s_name}") - last_increment = address - offset + text_addr = max_alignment(text_addr, 0x1000, s_alignment) + text_size = s_size - if sections[i] == '.text': command.append(f'-Wl,-Ttext=0x{text_addr:x}') - elif sections[i] == '.data': - command.append(f'-Wl,-Tdata=0x{address:x}') + + continue + + if (s_flags & (SH_FLAGS.SHF_WRITE | SH_FLAGS.SHF_ALLOC) == + SH_FLAGS.SHF_WRITE | SH_FLAGS.SHF_ALLOC): + # .data, .bss or other writable sections + writable.append(section) + continue + + if s_type == 'SHT_PROGBITS' and s_flags & SH_FLAGS.SHF_ALLOC: + # .rodata or other read-only sections + readonly.append(section) + + start_addr = align_up(text_addr + text_size, 0x1000) + + for section in readonly: + s_alignment = section.header['sh_addralign'] + s_name = section.name + + start_addr = align_up(start_addr, s_alignment) + + command.append(f'-Wl,--section-start={s_name}=0x{start_addr:x}') + + start_addr += section.header['sh_size'] + + start_addr = align_up(start_addr, 0x1000) + + for section in writable: + s_alignment = section.header['sh_addralign'] + s_name = section.name + + start_addr = align_up(start_addr, s_alignment) + + if s_name == '.data': + command.append(f'-Wl,-Tdata=0x{start_addr:x}') else: - command.append(f'-Wl,--section-start={sections[i]}=0x{address:x}') + command.append(f'-Wl,--section-start={s_name}=0x{start_addr:x}') + + start_addr += section.header['sh_size'] command.extend(args.params) diff --git a/scripts/xtensa-build-zephyr.py b/scripts/xtensa-build-zephyr.py index c0f9d48e82a7..c9c61c430bcc 100755 --- a/scripts/xtensa-build-zephyr.py +++ b/scripts/xtensa-build-zephyr.py @@ -809,6 +809,12 @@ def build_platforms(): if args.debug: overlays.append(str(pathlib.Path(SOF_TOP, "app", "debug_overlay.conf"))) + # The xt-cland Cadence toolchain currently cannot link shared + # libraries for Xtensa. Therefore when it's used we switch to + # building relocatable ELF objects. + if platf_build_environ.get("ZEPHYR_TOOLCHAIN_VARIANT") == 'xt-clang': + overlays.append(str(pathlib.Path(SOF_TOP, "app", "llext_relocatable.conf"))) + if overlays: overlays = ";".join(overlays) build_cmd.append(f"-DOVERLAY_CONFIG={overlays}") diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index d88103385b8d..86b6fa3779d9 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -78,10 +78,10 @@ function(sof_llext_build module) -o rimage_config.toml ) - if("${ZEPHYR_TOOLCHAIN_VARIANT}" STREQUAL "zephyr") - set(EXTRA_LINKER_PARAMS -nostdlib -nodefaultlibs -shared) - else() + if(CONFIG_LLEXT_TYPE_ELF_RELOCATABLE) set(EXTRA_LINKER_PARAMS -nostdlib -nodefaultlibs -r) + else() + set(EXTRA_LINKER_PARAMS -nostdlib -nodefaultlibs -shared) endif() get_target_property(proc_in_file ${module} lib_output)