322 lines
11 KiB
Python
322 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (c) 2018 Intel Corporation
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""
|
|
Script to generate a linker script organizing application memory partitions
|
|
|
|
Applications may declare build-time memory domain partitions with
|
|
K_APPMEM_PARTITION_DEFINE, and assign globals to them using K_APP_DMEM
|
|
or K_APP_BMEM macros. For each of these partitions, we need to
|
|
route all their data into appropriately-sized memory areas which meet the
|
|
size/alignment constraints of the memory protection hardware.
|
|
|
|
This linker script is created very early in the build process, before
|
|
the build attempts to link the kernel binary, as the linker script this
|
|
tool generates is a necessary pre-condition for kernel linking. We extract
|
|
the set of memory partitions to generate by looking for variables which
|
|
have been assigned to input sections that follow a defined naming convention.
|
|
We also allow entire libraries to be pulled in to assign their globals
|
|
to a particular memory partition via command line directives.
|
|
|
|
This script takes as inputs:
|
|
|
|
- The base directory to look for compiled objects
|
|
- key/value pairs mapping static library files to what partitions their globals
|
|
should end up in.
|
|
|
|
The output is a linker script fragment containing the definition of the
|
|
app shared memory section, which is further divided, for each partition
|
|
found, into data and BSS for each partition.
|
|
"""
|
|
|
|
import sys
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
from collections import OrderedDict
|
|
from elftools.elf.elffile import ELFFile
|
|
from elftools.elf.sections import SymbolTableSection
|
|
import elftools.common.exceptions
|
|
|
|
SZ = 'size'
|
|
SRC = 'sources'
|
|
LIB = 'libraries'
|
|
|
|
# This script will create sections and linker variables to place the
|
|
# application shared memory partitions.
|
|
# these are later read by the macros defined in app_memdomain.h for
|
|
# initialization purpose when USERSPACE is enabled.
|
|
data_template = """
|
|
/* Auto generated code do not modify */
|
|
SMEM_PARTITION_ALIGN(z_data_smem_{0}_bss_end - z_data_smem_{0}_part_start);
|
|
z_data_smem_{0}_part_start = .;
|
|
KEEP(*(data_smem_{0}_data*))
|
|
"""
|
|
|
|
library_data_template = """
|
|
*{0}:*(.data .data.* .sdata .sdata.*)
|
|
"""
|
|
|
|
bss_template = """
|
|
z_data_smem_{0}_bss_start = .;
|
|
KEEP(*(data_smem_{0}_bss*))
|
|
"""
|
|
|
|
library_bss_template = """
|
|
*{0}:*(.bss .bss.* .sbss .sbss.* COMMON COMMON.*)
|
|
"""
|
|
|
|
footer_template = """
|
|
z_data_smem_{0}_bss_end = .;
|
|
SMEM_PARTITION_ALIGN(z_data_smem_{0}_bss_end - z_data_smem_{0}_part_start);
|
|
z_data_smem_{0}_part_end = .;
|
|
"""
|
|
|
|
linker_start_seq = """
|
|
SECTION_PROLOGUE(_APP_SMEM{1}_SECTION_NAME,,)
|
|
{{
|
|
APP_SHARED_ALIGN;
|
|
_app_smem{0}_start = .;
|
|
"""
|
|
|
|
linker_end_seq = """
|
|
APP_SHARED_ALIGN;
|
|
_app_smem{0}_end = .;
|
|
}} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
|
|
"""
|
|
|
|
empty_app_smem = """
|
|
SECTION_PROLOGUE(_APP_SMEM{1}_SECTION_NAME,,)
|
|
{{
|
|
#ifdef EMPTY_APP_SHARED_ALIGN
|
|
EMPTY_APP_SHARED_ALIGN;
|
|
#endif
|
|
_app_smem{0}_start = .;
|
|
_app_smem{0}_end = .;
|
|
}} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
|
|
"""
|
|
|
|
size_cal_string = """
|
|
z_data_smem_{0}_part_size = z_data_smem_{0}_part_end - z_data_smem_{0}_part_start;
|
|
z_data_smem_{0}_bss_size = z_data_smem_{0}_bss_end - z_data_smem_{0}_bss_start;
|
|
"""
|
|
|
|
section_regex = re.compile(r'data_smem_([A-Za-z0-9_]*)_(data|bss)*')
|
|
|
|
elf_part_size_regex = re.compile(r'z_data_smem_(.*)_part_size')
|
|
|
|
def find_obj_file_partitions(filename, partitions):
|
|
with open(filename, 'rb') as f:
|
|
try:
|
|
full_lib = ELFFile(f)
|
|
except elftools.common.exceptions.ELFError as e:
|
|
exit(f"Error: {filename}: {e}")
|
|
|
|
if not full_lib:
|
|
sys.exit("Error parsing file: " + filename)
|
|
|
|
sections = [x for x in full_lib.iter_sections()]
|
|
for section in sections:
|
|
m = section_regex.match(section.name)
|
|
if not m:
|
|
continue
|
|
|
|
partition_name = m.groups()[0]
|
|
if partition_name not in partitions:
|
|
partitions[partition_name] = {SZ: section.header.sh_size}
|
|
|
|
if args.verbose:
|
|
partitions[partition_name][SRC] = filename
|
|
|
|
else:
|
|
partitions[partition_name][SZ] += section.header.sh_size
|
|
|
|
|
|
return partitions
|
|
|
|
|
|
def parse_obj_files(partitions):
|
|
# Iterate over all object files to find partitions
|
|
for dirpath, _, files in os.walk(args.directory):
|
|
for filename in files:
|
|
if re.match(r".*\.obj$", filename):
|
|
fullname = os.path.join(dirpath, filename)
|
|
fsize = os.path.getsize(fullname)
|
|
if fsize != 0:
|
|
find_obj_file_partitions(fullname, partitions)
|
|
|
|
|
|
def parse_compile_command_file(partitions):
|
|
# Iterate over all entries to find object files.
|
|
# Thereafter process each object file to find partitions
|
|
object_pattern = re.compile(r'-o\s+(\S*)')
|
|
with open(args.compile_commands_file, 'rb') as f:
|
|
commands = json.load(f)
|
|
for command in commands:
|
|
build_dir = command.get('directory')
|
|
compile_command = command.get('command')
|
|
compile_arg = object_pattern.search(compile_command)
|
|
obj_file = None if compile_arg is None else compile_arg.group(1)
|
|
if obj_file:
|
|
fullname = os.path.join(build_dir, obj_file)
|
|
# Because of issue #40635, then not all objects referenced by
|
|
# the compile_commands.json file may be available, therefore
|
|
# only include existing files.
|
|
if os.path.exists(fullname):
|
|
find_obj_file_partitions(fullname, partitions)
|
|
|
|
|
|
def parse_elf_file(partitions):
|
|
with open(args.elf, 'rb') as f:
|
|
try:
|
|
elffile = ELFFile(f)
|
|
except elftools.common.exceptions.ELFError as e:
|
|
exit(f"Error: {args.elf}: {e}")
|
|
|
|
symbol_tbls = [s for s in elffile.iter_sections()
|
|
if isinstance(s, SymbolTableSection)]
|
|
|
|
for section in symbol_tbls:
|
|
for symbol in section.iter_symbols():
|
|
if symbol['st_shndx'] != "SHN_ABS":
|
|
continue
|
|
|
|
x = elf_part_size_regex.match(symbol.name)
|
|
if not x:
|
|
continue
|
|
|
|
partition_name = x.groups()[0]
|
|
size = symbol['st_value']
|
|
if partition_name not in partitions:
|
|
partitions[partition_name] = {SZ: size}
|
|
|
|
if args.verbose:
|
|
partitions[partition_name][SRC] = args.elf
|
|
|
|
else:
|
|
partitions[partition_name][SZ] += size
|
|
|
|
|
|
def generate_final_linker(linker_file, partitions, lnkr_sect=""):
|
|
string = ""
|
|
|
|
if len(partitions) > 0:
|
|
string = linker_start_seq.format(lnkr_sect, lnkr_sect.upper())
|
|
size_string = ''
|
|
for partition, item in partitions.items():
|
|
string += data_template.format(partition)
|
|
if LIB in item:
|
|
for lib in item[LIB]:
|
|
string += library_data_template.format(lib)
|
|
string += bss_template.format(partition, lnkr_sect)
|
|
if LIB in item:
|
|
for lib in item[LIB]:
|
|
string += library_bss_template.format(lib)
|
|
string += footer_template.format(partition)
|
|
size_string += size_cal_string.format(partition)
|
|
|
|
string += linker_end_seq.format(lnkr_sect)
|
|
string += size_string
|
|
else:
|
|
string = empty_app_smem.format(lnkr_sect, lnkr_sect.upper())
|
|
|
|
with open(linker_file, "w") as fw:
|
|
fw.write(string)
|
|
|
|
|
|
def parse_args():
|
|
global args
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("-d", "--directory", required=False, default=None,
|
|
help="Root build directory")
|
|
parser.add_argument("-e", "--elf", required=False, default=None,
|
|
help="ELF file")
|
|
parser.add_argument("-f", "--compile-commands-file", required=False,
|
|
default=None, help="CMake compile commands file")
|
|
parser.add_argument("-o", "--output", required=False,
|
|
help="Output ld file")
|
|
parser.add_argument("-v", "--verbose", action="count", default=0,
|
|
help="Verbose Output")
|
|
parser.add_argument("-l", "--library", nargs=2, action="append", default=[],
|
|
metavar=("LIBRARY", "PARTITION"),
|
|
help="Include globals for a particular library or object filename into a designated partition")
|
|
parser.add_argument("--pinoutput", required=False,
|
|
help="Output ld file for pinned sections")
|
|
parser.add_argument("--pinpartitions", action="store", required=False, default="",
|
|
help="Comma separated names of partitions to be pinned in physical memory")
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
def main():
|
|
parse_args()
|
|
partitions = {}
|
|
|
|
if args.directory is not None:
|
|
parse_obj_files(partitions)
|
|
if args.compile_commands_file is not None:
|
|
parse_compile_command_file(partitions)
|
|
elif args.elf is not None:
|
|
parse_elf_file(partitions)
|
|
else:
|
|
return
|
|
|
|
for lib, ptn in args.library:
|
|
if ptn not in partitions:
|
|
partitions[ptn] = {}
|
|
|
|
if LIB not in partitions[ptn]:
|
|
partitions[ptn][LIB] = [lib]
|
|
else:
|
|
partitions[ptn][LIB].append(lib)
|
|
|
|
if args.pinoutput:
|
|
pin_part_names = args.pinpartitions.split(',')
|
|
|
|
generic_partitions = {key: value for key, value in partitions.items()
|
|
if key not in pin_part_names}
|
|
pinned_partitions = {key: value for key, value in partitions.items()
|
|
if key in pin_part_names}
|
|
else:
|
|
generic_partitions = partitions
|
|
|
|
# Sample partitions.items() list before sorting:
|
|
# [ ('part1', {'size': 64}), ('part3', {'size': 64}, ...
|
|
# ('part0', {'size': 334}) ]
|
|
decreasing_tuples = sorted(generic_partitions.items(),
|
|
key=lambda x: (x[1][SZ], x[0]), reverse=True)
|
|
|
|
partsorted = OrderedDict(decreasing_tuples)
|
|
|
|
generate_final_linker(args.output, partsorted)
|
|
if args.verbose:
|
|
print("Partitions retrieved:")
|
|
for key in partsorted:
|
|
print(" {0}: size {1}: {2}".format(key,
|
|
partsorted[key][SZ],
|
|
partsorted[key][SRC]))
|
|
|
|
if args.pinoutput:
|
|
decreasing_tuples = sorted(pinned_partitions.items(),
|
|
key=lambda x: (x[1][SZ], x[0]), reverse=True)
|
|
|
|
partsorted = OrderedDict(decreasing_tuples)
|
|
|
|
generate_final_linker(args.pinoutput, partsorted, lnkr_sect="_pinned")
|
|
if args.verbose:
|
|
print("Pinned partitions retrieved:")
|
|
for key in partsorted:
|
|
print(" {0}: size {1}: {2}".format(key,
|
|
partsorted[key][SZ],
|
|
partsorted[key][SRC]))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|