Skip to content

Commit 92db7bc

Browse files
laurenmurphyx64fabiobaltieri
authored andcommitted
llext: compile llext with arc mwdt on nsim vpx5
Allow users to compile LLEXTs with the MetaWare Design Toolchain. Provides utility to dramatically shrink bloated extensions by removing unused section names left behind by MWDT strip. Test extensions will not fit in NSIM VPX5 memory otherwise; each is reduced from ~16k to ~1k. Signed-off-by: Lauren Murphy <[email protected]>
1 parent a610e0e commit 92db7bc

File tree

6 files changed

+258
-5
lines changed

6 files changed

+258
-5
lines changed

cmake/bintools/arcmwdt/elfconvert_command.cmake

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
# on the arguments used.
55

66
# Handle stripping
7-
if (STRIP_DEBUG OR STRIP_ALL)
7+
if(STRIP_DEBUG OR STRIP_ALL OR STRIP_UNNEEDED)
88
if(STRIP_ALL)
99
set(obj_copy_strip "-qs")
1010
elseif(STRIP_DEBUG)
1111
set(obj_copy_strip "-ql")
12+
elseif(STRIP_UNNEEDED)
13+
set(obj_copy_strip "-qlu")
1214
endif()
1315

14-
execute_process(
15-
COMMAND ${STRIP} ${obj_copy_strip}
16-
${INFILE} ${FILEOUT})
16+
# MWDT strip transforms input file in place with no output file option
17+
configure_file(${INFILE} ${OUTFILE} COPYONLY)
18+
execute_process(COMMAND ${STRIP} ${obj_copy_strip} ${OUTFILE})
1719
endif()
1820

1921
# no support of --srec-len in mwdt

cmake/bintools/arcmwdt/target_bintools.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set_property(TARGET bintools PROPERTY elfconvert_flag_final
2626

2727
set_property(TARGET bintools PROPERTY elfconvert_flag_strip_all "-DSTRIP_ALL=True")
2828
set_property(TARGET bintools PROPERTY elfconvert_flag_strip_debug "-DSTRIP_DEBUG=True")
29+
set_property(TARGET bintools PROPERTY elfconvert_flag_strip_unneeded "-DSTRIP_UNNEEDED=True")
2930

3031
set_property(TARGET bintools PROPERTY elfconvert_flag_intarget "-DINTARGET=")
3132
set_property(TARGET bintools PROPERTY elfconvert_flag_outtarget "-DOUTTARGET=")

cmake/compiler/arcmwdt/target.cmake

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ string(REPLACE ";" " " CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
2424

2525
set(NOSTDINC ${TOOLCHAIN_HOME}/arc/inc)
2626

27+
set(LLEXT_APPEND_FLAGS
28+
-nog
29+
)
30+
31+
set(LLEXT_REMOVE_FLAGS
32+
-ffunction-sections
33+
-fdata-sections
34+
)
35+
2736
# For CMake to be able to test if a compiler flag is supported by the toolchain
2837
# (check_c_compiler_flag function which we wrap with target_cc_option in extensions.cmake)
2938
# we rely on default MWDT header locations and don't manually specify headers directories.

cmake/modules/extensions.cmake

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5965,6 +5965,20 @@ function(add_llext_target target_name)
59655965
set(slid_inject_cmd ${CMAKE_COMMAND} -E true)
59665966
endif()
59675967

5968+
# When using the arcmwdt toolchain, the compiler may emit hundreds of
5969+
# .arcextmap.* sections that bloat the shstrtab. Even when the strip command
5970+
# is used to remove the sections, their names remain in the shstrtab.
5971+
# We must remove them ourselves.
5972+
if (${ZEPHYR_TOOLCHAIN_VARIANT} STREQUAL "arcmwdt")
5973+
set(mwdt_strip_arcextmap_shstrtab_cmd
5974+
${PYTHON_EXECUTABLE}
5975+
${ZEPHYR_BASE}/scripts/build/llext_mwdt_strip_arcextmap.py
5976+
${llext_pkg_output}
5977+
)
5978+
else()
5979+
set(mwdt_strip_arcextmap_shstrtab_cmd ${CMAKE_COMMAND} -E true)
5980+
endif()
5981+
59685982
# Remove sections that are unused by the llext loader
59695983
add_custom_command(
59705984
OUTPUT ${llext_pkg_output}
@@ -5976,6 +5990,7 @@ function(add_llext_target target_name)
59765990
$<TARGET_PROPERTY:bintools,elfconvert_flag_infile>${llext_pkg_input}
59775991
$<TARGET_PROPERTY:bintools,elfconvert_flag_outfile>${llext_pkg_output}
59785992
$<TARGET_PROPERTY:bintools,elfconvert_flag_final>
5993+
COMMAND ${mwdt_strip_arcextmap_shstrtab_cmd}
59795994
COMMAND ${slid_inject_cmd}
59805995
DEPENDS ${llext_pkg_input}
59815996
COMMAND_EXPAND_LISTS
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (c) 2025 Intel Corporation
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
'''Strip arcextmap names from section header string table
8+
9+
The MWDT strip utility does not strip the names of stripped sections from the
10+
section header string table. This script is a workaround to remove unused .arcextmap.*
11+
names from the section header string table, dramatically shrinking the file size. This is
12+
necessary for boards where MWDT CCAC emits debugging sections like .arcextmap.*,
13+
as even the smallest extension (<1k) will have a >16KB section header string table.
14+
'''
15+
16+
import argparse
17+
import logging
18+
import os
19+
import shutil
20+
import sys
21+
22+
from elftools.elf.elffile import ELFFile
23+
24+
logger = logging.getLogger('strip-arcextmap')
25+
LOGGING_FORMAT = '[%(levelname)s][%(name)s] %(message)s'
26+
27+
28+
def parse_args():
29+
parser = argparse.ArgumentParser(allow_abbrev=False)
30+
31+
parser.add_argument('file', help='Object file')
32+
parser.add_argument('--debug', action='store_true', help='Print extra debugging information')
33+
34+
return parser.parse_args()
35+
36+
37+
def get_field_size(struct, name):
38+
for field in struct.subcons:
39+
if field.name == name:
40+
return field.sizeof()
41+
42+
raise ValueError(f"Unknown field name: {name}")
43+
44+
45+
def get_field_offset(struct, name):
46+
offset = 0
47+
48+
for field in struct.subcons:
49+
if field.name == name:
50+
break
51+
offset += field.sizeof()
52+
53+
if offset < struct.sizeof():
54+
return offset
55+
56+
raise ValueError(f"Unknown field name: {name}")
57+
58+
59+
def write_field_in_struct_bytearr(elf, bytearr, name, num, ent_idx=0):
60+
"""Write a field in an ELF struct represented by a bytearray.
61+
62+
Given a bytearray read from an ELF file containing one or more entries
63+
from a table (ELF header entry, section header entry, or symbol), edit
64+
fields inline. User will write this bytearray back to the file.
65+
"""
66+
if name[0] == 'e':
67+
header = elf.structs.Elf_Ehdr
68+
elif name[0:2] == 'sh':
69+
header = elf.structs.Elf_Shdr
70+
elif name[0:2] == 'st':
71+
header = elf.structs.Elf_Sym
72+
else:
73+
raise ValueError(f"Unable to identify struct for {name}")
74+
75+
size = get_field_size(header, name)
76+
77+
# ent_idx is 0 for ELF header
78+
offset = (ent_idx * header.sizeof()) + get_field_offset(header, name)
79+
80+
if elf.little_endian:
81+
field = num.to_bytes(size, 'little')
82+
else:
83+
field = num.to_bytes(size, 'big')
84+
85+
bytearr[offset : offset + size] = field
86+
87+
88+
def strip_arcextmap_shstrtab(f, bak):
89+
elf = ELFFile(bak)
90+
91+
if elf.header['e_type'] == 'ET_CORE':
92+
logger.warning('Script not applicable to ET_CORE files')
93+
return
94+
95+
elfh = bytearray()
96+
97+
e_shentsize = elf.header['e_shentsize']
98+
99+
e_shoff = 0
100+
101+
sht = bytearray()
102+
pht = bytearray()
103+
104+
sht_sh_offsets = []
105+
sht_sh_names = []
106+
107+
shstrtab = bytearray()
108+
109+
# Read in ELF header (to move file pointer forward)
110+
bak.seek(0)
111+
elfh += bak.read(elf.header['e_ehsize'])
112+
f.write(elfh)
113+
114+
# Read in program header table (if present) and write it out
115+
if elf.header['e_phnum'] > 0:
116+
bak.seek(elf.header['e_phoff'])
117+
pht += bak.read(elf.header['e_phnum'] * elf.header['e_phentsize'])
118+
f.write(pht)
119+
120+
# Read in section header table
121+
bak.seek(elf.header['e_shoff'])
122+
sht += bak.read(elf.header['e_shnum'] * e_shentsize)
123+
124+
# Rebuild the section header string table
125+
sh_name = 0
126+
for section in elf.iter_sections():
127+
shstrtab += section.name.encode('utf-8') + b'\x00'
128+
sht_sh_names.append(sh_name)
129+
sh_name += len(section.name) + 1
130+
131+
shstrtab_sh_size = len(shstrtab)
132+
logger.debug(
133+
'.shstrtab sh_size shrunk from {} to {} bytes'.format(
134+
elf.get_section(elf.header['e_shstrndx'])['sh_size'], shstrtab_sh_size
135+
)
136+
)
137+
138+
# Write out sections and update headers
139+
for i, section in enumerate(elf.iter_sections()):
140+
if elf.header['e_shstrndx'] == i:
141+
section_data = shstrtab
142+
write_field_in_struct_bytearr(elf, sht, 'sh_size', shstrtab_sh_size, i)
143+
else:
144+
bak.seek(section['sh_offset'])
145+
section_data = bak.read(section['sh_size'])
146+
147+
# Accommodate for alignment
148+
sh_addralign = section['sh_addralign']
149+
# How many bytes we overshot alignment by
150+
past_by = f.tell() % sh_addralign if sh_addralign != 0 else 0
151+
if past_by > 0:
152+
logger.debug(
153+
f'Padding section {i} by {sh_addralign - past_by} bytes to align by {sh_addralign}'
154+
)
155+
f.seek(f.tell() + (sh_addralign - past_by))
156+
157+
sht_sh_offsets.append(f.tell())
158+
159+
logger.debug(
160+
'Section {} read from {}, written at {}'.format(
161+
i, hex(section['sh_offset']), hex(sht_sh_offsets[i])
162+
)
163+
)
164+
f.write(section_data)
165+
166+
# Update entry in section header table
167+
write_field_in_struct_bytearr(elf, sht, 'sh_name', sht_sh_names[i], i)
168+
write_field_in_struct_bytearr(elf, sht, 'sh_offset', sht_sh_offsets[i], i)
169+
170+
name_end = shstrtab[sht_sh_names[i] :].find(b'\x00')
171+
if name_end != -1:
172+
name = shstrtab[sht_sh_names[i] : sht_sh_names[i] + name_end].decode('utf-8')
173+
logger.debug(f'Section {i} name read as {section.name}, written out as {name}')
174+
else:
175+
logger.error(
176+
f'Unable to find null terminated string for section {i} in shrunk shstrtab'
177+
)
178+
179+
# Write the section header table to the file
180+
e_shoff = f.tell()
181+
f.write(sht)
182+
183+
# Modify the ELF header
184+
write_field_in_struct_bytearr(elf, elfh, 'e_shoff', e_shoff)
185+
186+
# Write back the ELF header
187+
f.seek(0)
188+
f.write(elfh)
189+
190+
191+
def main():
192+
args = parse_args()
193+
194+
logging.basicConfig(format=LOGGING_FORMAT)
195+
196+
if args.debug:
197+
logger.setLevel(logging.DEBUG)
198+
else:
199+
logger.setLevel(logging.WARNING)
200+
201+
if not os.path.isfile(args.file):
202+
logger.error(f'Cannot find file {args.file}, exiting...')
203+
sys.exit(1)
204+
205+
with open(args.file, 'rb') as f:
206+
try:
207+
ELFFile(f)
208+
except Exception:
209+
logger.error(f'File {args.file} is not a valid ELF file, exiting...')
210+
sys.exit(1)
211+
212+
try:
213+
# Back up extension.llext
214+
shutil.copy(args.file, f'{args.file}.bak')
215+
216+
# Read from extension.llext.bak, write out to extension.llext
217+
with open(f'{args.file}.bak', 'rb') as bak, open(args.file, 'wb') as f:
218+
strip_arcextmap_shstrtab(f, bak)
219+
220+
os.remove(f'{args.file}.bak')
221+
except Exception as e:
222+
logger.error(f'An error occurred while processing the file: {e}')
223+
sys.exit(1)
224+
225+
226+
if __name__ == "__main__":
227+
main()

subsys/llext/Kconfig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ menuconfig LLEXT
55
bool "Linkable loadable extensions"
66
select CACHE_MANAGEMENT if DCACHE
77
select KERNEL_WHOLE_ARCHIVE
8-
depends on !HARVARD
98
help
109
Enable the linkable loadable extension subsystem
1110

0 commit comments

Comments
 (0)