465 lines
15 KiB
Python
465 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import logging
|
|
import argparse
|
|
import csv
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import re
|
|
from io import StringIO
|
|
import configuration
|
|
|
|
sys.path.append(os.environ['IDF_PATH'] + '/tools/ldgen')
|
|
sys.path.append(os.environ['IDF_PATH'] + '/tools/ldgen/ldgen')
|
|
from entity import EntityDB
|
|
|
|
espidf_objdump = None
|
|
espidf_version = None
|
|
|
|
def lib_secs(lib, file, lib_path):
|
|
new_env = os.environ.copy()
|
|
new_env['LC_ALL'] = 'C'
|
|
try:
|
|
dump_output = subprocess.check_output([espidf_objdump, '-h', lib_path], env=new_env).decode()
|
|
except subprocess.CalledProcessError as e:
|
|
raise RuntimeError(f"Command '{e.cmd}' failed with exit code {e.returncode}")
|
|
|
|
dump = StringIO(dump_output)
|
|
dump.name = lib
|
|
|
|
sections_infos = EntityDB()
|
|
sections_infos.add_sections_info(dump)
|
|
|
|
secs = sections_infos.get_sections(lib, file.split('.')[0] + '.c')
|
|
if not secs:
|
|
secs = sections_infos.get_sections(lib, file.split('.')[0])
|
|
if not secs:
|
|
raise ValueError(f'Failed to get sections from lib {lib_path}')
|
|
|
|
return secs
|
|
|
|
def filter_secs(secs_a, secs_b):
|
|
return [s_a for s_a in secs_a if any(s_b in s_a for s_b in secs_b)]
|
|
|
|
def strip_secs(secs_a, secs_b):
|
|
secs = list(set(secs_a) - set(secs_b))
|
|
secs.sort()
|
|
return secs
|
|
|
|
def func2sect(func):
|
|
if ' ' in func:
|
|
func_l = func.split(' ')
|
|
else:
|
|
func_l = [func]
|
|
|
|
secs = []
|
|
for l in func_l:
|
|
if '.iram1.' not in l:
|
|
secs.append(f'.literal.{l}')
|
|
secs.append(f'.text.{l}')
|
|
else:
|
|
secs.append(l)
|
|
return secs
|
|
|
|
class filter_c:
|
|
def __init__(self, file):
|
|
with open(file) as f:
|
|
lines = f.read().splitlines()
|
|
self.libs_desc = ''
|
|
self.libs = ''
|
|
for line in lines:
|
|
if ') .iram1 EXCLUDE_FILE(*' in line and ') .iram1.*)' in line:
|
|
desc = r'\(EXCLUDE_FILE\((.*)\) .iram1 '
|
|
self.libs_desc = re.search(desc, line)[1]
|
|
self.libs = self.libs_desc.replace('*', '')
|
|
return
|
|
|
|
def match(self, lib):
|
|
if lib in self.libs:
|
|
print(f'Remove lib {lib}')
|
|
return True
|
|
return False
|
|
|
|
def add(self):
|
|
return self.libs_desc
|
|
|
|
class target_c:
|
|
def __init__(self, lib, lib_path, file, fsecs):
|
|
self.lib = lib
|
|
self.file = file
|
|
self.lib_path = lib_path
|
|
self.fsecs = func2sect(fsecs)
|
|
self.desc = f'*{lib}:{file.split(".")[0]}.*'
|
|
|
|
secs = lib_secs(lib, file, lib_path)
|
|
if '.iram1.' in self.fsecs[0]:
|
|
self.secs = filter_secs(secs, ('.iram1.',))
|
|
else:
|
|
self.secs = filter_secs(secs, ('.iram1.', '.text.', '.literal.'))
|
|
self.isecs = strip_secs(self.secs, self.fsecs)
|
|
|
|
self.has_exclude_iram = True
|
|
self.has_exclude_dram = True
|
|
|
|
def __str__(self):
|
|
return (
|
|
f'lib={self.lib}\n'
|
|
f'file={self.file}\n'
|
|
f'lib_path={self.lib_path}\n'
|
|
f'desc={self.desc}\n'
|
|
f'secs={self.secs}\n'
|
|
f'fsecs={self.fsecs}\n'
|
|
f'isecs={self.isecs}\n'
|
|
)
|
|
|
|
class target2_c:
|
|
def __init__(self, lib, file, iram_secs):
|
|
self.lib = lib
|
|
self.file = file
|
|
self.fsecs = iram_secs
|
|
if file != '*':
|
|
self.desc = f'*{lib}:{file.split(".")[0]}.*'
|
|
else:
|
|
self.desc = f'*{lib}:'
|
|
self.isecs = iram_secs
|
|
|
|
self.has_exclude_iram = False
|
|
self.has_exclude_dram = False
|
|
|
|
def __str__(self):
|
|
return (
|
|
f'lib={self.lib}\n'
|
|
f'file={self.file}\n'
|
|
f'lib_path={self.lib_path}\n'
|
|
f'desc={self.desc}\n'
|
|
f'secs={self.secs}\n'
|
|
f'fsecs={self.fsecs}\n'
|
|
f'isecs={self.isecs}\n'
|
|
)
|
|
|
|
# Remove specific functions from IRAM
|
|
class relink_c:
|
|
def __init__(self, input_file, library_file, object_file, function_file, sdkconfig_file, missing_function_info):
|
|
self.filter = filter_c(input_file)
|
|
|
|
libraries = configuration.generator(library_file, object_file, function_file, sdkconfig_file, missing_function_info, espidf_objdump)
|
|
self.targets = []
|
|
for lib_name in libraries.libs:
|
|
lib = libraries.libs[lib_name]
|
|
|
|
if self.filter.match(lib.name):
|
|
continue
|
|
|
|
for obj_name in lib.objs:
|
|
obj = lib.objs[obj_name]
|
|
target = target_c(lib.name, lib.path, obj.name, ' '.join(obj.sections()))
|
|
self.targets.append(target)
|
|
|
|
self.__transform__()
|
|
|
|
def __transform__(self):
|
|
iram1_exclude = []
|
|
iram1_include = []
|
|
flash_include = []
|
|
|
|
for t in self.targets:
|
|
secs = filter_secs(t.fsecs, ('.iram1.', ))
|
|
if len(secs) > 0:
|
|
iram1_exclude.append(t.desc)
|
|
|
|
secs = filter_secs(t.isecs, ('.iram1.', ))
|
|
if len(secs) > 0:
|
|
iram1_include.append(f' {t.desc}({" ".join(secs)})')
|
|
|
|
secs = t.fsecs
|
|
if len(secs) > 0:
|
|
flash_include.append(f' {t.desc}({" ".join(secs)})')
|
|
|
|
self.iram1_exclude = f' *(EXCLUDE_FILE({self.filter.add()} {" ".join(iram1_exclude)}) .iram1.*) *(EXCLUDE_FILE({self.filter.add()} {" ".join(iram1_exclude)}) .iram1)'
|
|
self.iram1_include = '\n'.join(iram1_include)
|
|
self.flash_include = '\n'.join(flash_include)
|
|
|
|
logging.debug(f'IRAM1 Exclude: {self.iram1_exclude}')
|
|
logging.debug(f'IRAM1 Include: {self.iram1_include}')
|
|
logging.debug(f'Flash Include: {self.flash_include}')
|
|
|
|
def __replace__(self, lines):
|
|
def is_iram_desc(line):
|
|
return '*(.iram1 .iram1.*)' in line or (') .iram1 EXCLUDE_FILE(*' in line and ') .iram1.*)' in line)
|
|
|
|
iram_start = False
|
|
flash_done = False
|
|
flash_text = '(.stub)'
|
|
|
|
if espidf_version == '5.0':
|
|
flash_text = '(.stub .gnu.warning'
|
|
|
|
new_lines = []
|
|
for line in lines:
|
|
if '.iram0.text :' in line:
|
|
logging.debug('start to process .iram0.text')
|
|
iram_start = True
|
|
new_lines.append(line)
|
|
elif '.dram0.data :' in line:
|
|
logging.debug('end to process .iram0.text')
|
|
iram_start = False
|
|
new_lines.append(line)
|
|
elif is_iram_desc(line):
|
|
if iram_start:
|
|
new_lines.extend([self.iram1_exclude, self.iram1_include])
|
|
else:
|
|
new_lines.append(line)
|
|
elif flash_text in line:
|
|
if not flash_done:
|
|
new_lines.extend([self.flash_include, line])
|
|
flash_done = True
|
|
else:
|
|
new_lines.append(line)
|
|
else:
|
|
new_lines.append(self._replace_func(line) if iram_start else line)
|
|
|
|
return new_lines
|
|
|
|
def _replace_func(self, line):
|
|
for t in self.targets:
|
|
if t.desc in line:
|
|
S = '.literal .literal.* .text .text.*'
|
|
if S in line:
|
|
if len(t.isecs) > 0:
|
|
return line.replace(S, ' '.join(t.isecs))
|
|
else:
|
|
return ''
|
|
|
|
S = f'{t.desc}({" ".join(t.fsecs)})'
|
|
if S in line:
|
|
return ''
|
|
|
|
replaced = False
|
|
for s in t.fsecs:
|
|
s2 = s + ' '
|
|
if s2 in line:
|
|
line = line.replace(s2, '')
|
|
replaced = True
|
|
s2 = s + ')'
|
|
if s2 in line:
|
|
line = line.replace(s2, ')')
|
|
replaced = True
|
|
if '( )' in line or '()' in line:
|
|
return ''
|
|
if replaced:
|
|
return line
|
|
|
|
index = f'*{t.lib}:(EXCLUDE_FILE'
|
|
if index in line and t.file.split('.')[0] not in line:
|
|
for m in self.targets:
|
|
index = f'*{m.lib}:(EXCLUDE_FILE'
|
|
if index in line and m.file.split('.')[0] not in line:
|
|
line = line.replace('EXCLUDE_FILE(', f'EXCLUDE_FILE({m.desc} ')
|
|
if len(m.isecs) > 0:
|
|
line += f'\n {m.desc}({" ".join(m.isecs)})'
|
|
return line
|
|
|
|
return line
|
|
|
|
def save(self, input_file, output_file, target):
|
|
with open(input_file) as f:
|
|
lines = f.read().splitlines()
|
|
lines = self.__replace__(lines)
|
|
with open(output_file, 'w+') as f:
|
|
f.write('\n'.join(lines))
|
|
|
|
# Link specific functions to IRAM
|
|
class relink2_c:
|
|
def __init__(self, input_file, library_file, object_file, function_file, sdkconfig_file, missing_function_info):
|
|
self.filter = filter_c(input_file)
|
|
|
|
rodata_exclude = []
|
|
iram_exclude = []
|
|
rodata = []
|
|
iram = []
|
|
|
|
libraries = configuration.generator(library_file, object_file, function_file, sdkconfig_file, missing_function_info, espidf_objdump)
|
|
self.targets = []
|
|
|
|
for lib_name in libraries.libs:
|
|
lib = libraries.libs[lib_name]
|
|
|
|
if self.filter.match(lib.name):
|
|
continue
|
|
|
|
for obj_name in lib.objs:
|
|
obj = lib.objs[obj_name]
|
|
if not obj.section_all:
|
|
self.targets.append(target_c(lib.name, lib.path, obj.name, ' '.join(obj.sections())))
|
|
else:
|
|
self.targets.append(target2_c(lib.name, obj.name, obj.sections()))
|
|
|
|
for target in self.targets:
|
|
rodata.append(f' {target.desc}(.rodata .rodata.* .sdata2 .sdata2.* .srodata .srodata.*)')
|
|
if target.has_exclude_dram:
|
|
rodata_exclude.append(target.desc)
|
|
iram.append(f' {target.desc}({" ".join(target.fsecs)})')
|
|
if target.has_exclude_iram:
|
|
iram_exclude.append(target.desc)
|
|
|
|
self.rodata_ex = f' *(EXCLUDE_FILE({" ".join(rodata_exclude)}) .rodata EXCLUDE_FILE({" ".join(rodata_exclude)}) .rodata.* EXCLUDE_FILE({" ".join(rodata_exclude)}) .sdata2 EXCLUDE_FILE({" ".join(rodata_exclude)}) .sdata2.* EXCLUDE_FILE({" ".join(rodata_exclude)}) .srodata EXCLUDE_FILE({" ".join(rodata_exclude)}) .srodata.*)'
|
|
self.rodata_in = '\n'.join(rodata)
|
|
|
|
self.iram_ex = f' *(EXCLUDE_FILE({" ".join(iram_exclude)}) .iram1 EXCLUDE_FILE({" ".join(iram_exclude)}) .iram1.*)'
|
|
self.iram_in = '\n'.join(iram)
|
|
|
|
logging.debug(f'RODATA In: {self.rodata_in}')
|
|
logging.debug(f'RODATA Ex: {self.rodata_ex}')
|
|
logging.debug(f'IRAM In: {self.iram_in}')
|
|
logging.debug(f'IRAM Ex: {self.iram_ex}')
|
|
|
|
def save(self, input_file, output_file, target):
|
|
with open(input_file) as f:
|
|
lines = f.read().splitlines()
|
|
lines = self.__replace__(lines, target)
|
|
with open(output_file, 'w+') as f:
|
|
f.write('\n'.join(lines))
|
|
|
|
def __replace__(self, lines, target):
|
|
iram_start = False
|
|
new_lines = []
|
|
iram_cache = []
|
|
|
|
flash_text = '*(.stub)'
|
|
iram_text = '*(.iram1 .iram1.*)'
|
|
if espidf_version == '5.3' and target == 'esp32c2':
|
|
iram_text = '_iram_text_start = ABSOLUTE(.);'
|
|
elif espidf_version == '5.0':
|
|
flash_text = '*(.stub .gnu.warning'
|
|
|
|
for line in lines:
|
|
if not iram_start:
|
|
if iram_text in line:
|
|
new_lines.append(f'{self.iram_in}\n')
|
|
iram_start = True
|
|
elif ' _text_start = ABSOLUTE(.);' in line:
|
|
new_lines.append(f'{line}\n{self.iram_ex}')
|
|
continue
|
|
elif flash_text in line:
|
|
new_lines.extend(iram_cache)
|
|
iram_cache = []
|
|
else:
|
|
if '} > iram0_0_seg' in line:
|
|
iram_start = False
|
|
|
|
if not iram_start:
|
|
new_lines.append(line)
|
|
else:
|
|
skip_str = ['libriscv.a:interrupt', 'libriscv.a:vectors']
|
|
if not any(s in line for s in skip_str):
|
|
iram_cache.append(line)
|
|
|
|
return new_lines
|
|
|
|
def main():
|
|
argparser = argparse.ArgumentParser(description='Relinker script generator')
|
|
|
|
argparser.add_argument(
|
|
'--input', '-i',
|
|
help='Linker template file',
|
|
type=str,
|
|
required=True)
|
|
|
|
argparser.add_argument(
|
|
'--output', '-o',
|
|
help='Output linker script',
|
|
type=str,
|
|
required=True)
|
|
|
|
argparser.add_argument(
|
|
'--library', '-l',
|
|
help='Library description directory',
|
|
type=str,
|
|
required=True)
|
|
|
|
argparser.add_argument(
|
|
'--object', '-b',
|
|
help='Object description file',
|
|
type=str,
|
|
required=True)
|
|
|
|
argparser.add_argument(
|
|
'--function', '-f',
|
|
help='Function description file',
|
|
type=str,
|
|
required=True)
|
|
|
|
argparser.add_argument(
|
|
'--sdkconfig', '-s',
|
|
help='sdkconfig file',
|
|
type=str,
|
|
required=True)
|
|
|
|
argparser.add_argument(
|
|
'--target', '-t',
|
|
help='target chip',
|
|
type=str,
|
|
required=True)
|
|
|
|
argparser.add_argument(
|
|
'--version', '-v',
|
|
help='IDF version',
|
|
type=str,
|
|
required=True)
|
|
|
|
argparser.add_argument(
|
|
'--objdump', '-g',
|
|
help='GCC objdump command',
|
|
type=str,
|
|
required=True)
|
|
|
|
argparser.add_argument(
|
|
'--debug', '-d',
|
|
help='Debug level (option is \'debug\')',
|
|
default='no',
|
|
type=str)
|
|
|
|
argparser.add_argument(
|
|
'--missing_function_info',
|
|
help='Print error information instead of throwing exception when missing function',
|
|
action='store_true')
|
|
|
|
argparser.add_argument(
|
|
'--link_to_iram',
|
|
help='True: Link specific functions to IRAM, False: Remove specific functions from IRAM',
|
|
action='store_true')
|
|
|
|
args = argparser.parse_args()
|
|
|
|
if args.debug == 'debug':
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
logging.debug(f'input: {args.input}')
|
|
logging.debug(f'output: {args.output}')
|
|
logging.debug(f'library: {args.library}')
|
|
logging.debug(f'object: {args.object}')
|
|
logging.debug(f'function: {args.function}')
|
|
logging.debug(f'version: {args.version}')
|
|
logging.debug(f'sdkconfig:{args.sdkconfig}')
|
|
logging.debug(f'objdump: {args.objdump}')
|
|
logging.debug(f'debug: {args.debug}')
|
|
logging.debug(f'missing_function_info: {args.missing_function_info}')
|
|
|
|
global espidf_objdump
|
|
espidf_objdump = args.objdump
|
|
|
|
global espidf_version
|
|
espidf_version = args.version
|
|
|
|
if not args.link_to_iram:
|
|
relink = relink_c(args.input, args.library, args.object, args.function, args.sdkconfig, args.missing_function_info)
|
|
else:
|
|
relink = relink2_c(args.input, args.library, args.object, args.function, args.sdkconfig, args.missing_function_info)
|
|
relink.save(args.input, args.output, args.target)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|