Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/llext_relocatable.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONFIG_LLEXT_TYPE_ELF_RELOCATABLE=y
125 changes: 89 additions & 36 deletions scripts/llext_link_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really belong to this script? As opposed to its caller.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do deal with both cases internally, and passing a module name additionally to the file name would be redundant

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)

Expand Down
6 changes: 6 additions & 0 deletions scripts/xtensa-build-zephyr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down
6 changes: 3 additions & 3 deletions zephyr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down