| #!/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, allow_abbrev=False) |
| 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() |