1#!/usr/bin/env python3 2# 3# Copyright (c) 2018 Intel Corporation 4# 5# SPDX-License-Identifier: Apache-2.0 6 7""" 8Script to generate a linker script organizing application memory partitions 9 10Applications may declare build-time memory domain partitions with 11K_APPMEM_PARTITION_DEFINE, and assign globals to them using K_APP_DMEM 12or K_APP_BMEM macros. For each of these partitions, we need to 13route all their data into appropriately-sized memory areas which meet the 14size/alignment constraints of the memory protection hardware. 15 16This linker script is created very early in the build process, before 17the build attempts to link the kernel binary, as the linker script this 18tool generates is a necessary pre-condition for kernel linking. We extract 19the set of memory partitions to generate by looking for variables which 20have been assigned to input sections that follow a defined naming convention. 21We also allow entire libraries to be pulled in to assign their globals 22to a particular memory partition via command line directives. 23 24This script takes as inputs: 25 26- The base directory to look for compiled objects 27- key/value pairs mapping static library files to what partitions their globals 28 should end up in. 29 30The output is a linker script fragment containing the definition of the 31app shared memory section, which is further divided, for each partition 32found, into data and BSS for each partition. 33""" 34 35import sys 36import argparse 37import json 38import os 39import re 40from collections import OrderedDict 41from elftools.elf.elffile import ELFFile 42from elftools.elf.sections import SymbolTableSection 43import elftools.common.exceptions 44 45SZ = 'size' 46SRC = 'sources' 47LIB = 'libraries' 48 49# This script will create sections and linker variables to place the 50# application shared memory partitions. 51# these are later read by the macros defined in app_memdomain.h for 52# initialization purpose when USERSPACE is enabled. 53data_template = """ 54 /* Auto generated code do not modify */ 55 SMEM_PARTITION_ALIGN(z_data_smem_{0}_bss_end - z_data_smem_{0}_part_start); 56 z_data_smem_{0}_part_start = .; 57 KEEP(*(data_smem_{0}_data*)) 58""" 59 60library_data_template = """ 61 *{0}:*(.data .data.* .sdata .sdata.*) 62""" 63 64bss_template = """ 65 z_data_smem_{0}_bss_start = .; 66 KEEP(*(data_smem_{0}_bss*)) 67""" 68 69library_bss_template = """ 70 *{0}:*(.bss .bss.* .sbss .sbss.* COMMON COMMON.*) 71""" 72 73footer_template = """ 74 z_data_smem_{0}_bss_end = .; 75 SMEM_PARTITION_ALIGN(z_data_smem_{0}_bss_end - z_data_smem_{0}_part_start); 76 z_data_smem_{0}_part_end = .; 77""" 78 79linker_start_seq = """ 80 SECTION_PROLOGUE(_APP_SMEM{1}_SECTION_NAME,,) 81 {{ 82 APP_SHARED_ALIGN; 83 _app_smem{0}_start = .; 84""" 85 86linker_end_seq = """ 87 APP_SHARED_ALIGN; 88 _app_smem{0}_end = .; 89 }} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) 90""" 91 92empty_app_smem = """ 93 SECTION_PROLOGUE(_APP_SMEM{1}_SECTION_NAME,,) 94 {{ 95 _app_smem{0}_start = .; 96 _app_smem{0}_end = .; 97 }} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) 98""" 99 100size_cal_string = """ 101 z_data_smem_{0}_part_size = z_data_smem_{0}_part_end - z_data_smem_{0}_part_start; 102 z_data_smem_{0}_bss_size = z_data_smem_{0}_bss_end - z_data_smem_{0}_bss_start; 103""" 104 105section_regex = re.compile(r'data_smem_([A-Za-z0-9_]*)_(data|bss)*') 106 107elf_part_size_regex = re.compile(r'z_data_smem_(.*)_part_size') 108 109def find_obj_file_partitions(filename, partitions): 110 with open(filename, 'rb') as f: 111 try: 112 full_lib = ELFFile(f) 113 except elftools.common.exceptions.ELFError as e: 114 exit(f"Error: {filename}: {e}") 115 116 if not full_lib: 117 sys.exit("Error parsing file: " + filename) 118 119 sections = [x for x in full_lib.iter_sections()] 120 for section in sections: 121 m = section_regex.match(section.name) 122 if not m: 123 continue 124 125 partition_name = m.groups()[0] 126 if partition_name not in partitions: 127 partitions[partition_name] = {SZ: section.header.sh_size} 128 129 if args.verbose: 130 partitions[partition_name][SRC] = filename 131 132 else: 133 partitions[partition_name][SZ] += section.header.sh_size 134 135 136 return partitions 137 138 139def parse_obj_files(partitions): 140 # Iterate over all object files to find partitions 141 for dirpath, _, files in os.walk(args.directory): 142 for filename in files: 143 if re.match(r".*\.obj$", filename): 144 fullname = os.path.join(dirpath, filename) 145 fsize = os.path.getsize(fullname) 146 if fsize != 0: 147 find_obj_file_partitions(fullname, partitions) 148 149 150def parse_compile_command_file(partitions): 151 # Iterate over all entries to find object files. 152 # Thereafter process each object file to find partitions 153 object_pattern = re.compile(r'-o\s+(\S*)') 154 with open(args.compile_commands_file, 'rb') as f: 155 commands = json.load(f) 156 for command in commands: 157 build_dir = command.get('directory') 158 compile_command = command.get('command') 159 compile_arg = object_pattern.search(compile_command) 160 obj_file = None if compile_arg is None else compile_arg.group(1) 161 if obj_file: 162 fullname = os.path.join(build_dir, obj_file) 163 # Because of issue #40635, then not all objects referenced by 164 # the compile_commands.json file may be available, therefore 165 # only include existing files. 166 if os.path.exists(fullname): 167 find_obj_file_partitions(fullname, partitions) 168 169 170def parse_elf_file(partitions): 171 with open(args.elf, 'rb') as f: 172 try: 173 elffile = ELFFile(f) 174 except elftools.common.exceptions.ELFError as e: 175 exit(f"Error: {args.elf}: {e}") 176 177 symbol_tbls = [s for s in elffile.iter_sections() 178 if isinstance(s, SymbolTableSection)] 179 180 for section in symbol_tbls: 181 for symbol in section.iter_symbols(): 182 if symbol['st_shndx'] != "SHN_ABS": 183 continue 184 185 x = elf_part_size_regex.match(symbol.name) 186 if not x: 187 continue 188 189 partition_name = x.groups()[0] 190 size = symbol['st_value'] 191 if partition_name not in partitions: 192 partitions[partition_name] = {SZ: size} 193 194 if args.verbose: 195 partitions[partition_name][SRC] = args.elf 196 197 else: 198 partitions[partition_name][SZ] += size 199 200 201def generate_final_linker(linker_file, partitions, lnkr_sect=""): 202 string = "" 203 204 if len(partitions) > 0: 205 string = linker_start_seq.format(lnkr_sect, lnkr_sect.upper()) 206 size_string = '' 207 for partition, item in partitions.items(): 208 string += data_template.format(partition) 209 if LIB in item: 210 for lib in item[LIB]: 211 string += library_data_template.format(lib) 212 string += bss_template.format(partition, lnkr_sect) 213 if LIB in item: 214 for lib in item[LIB]: 215 string += library_bss_template.format(lib) 216 string += footer_template.format(partition) 217 size_string += size_cal_string.format(partition) 218 219 string += linker_end_seq.format(lnkr_sect) 220 string += size_string 221 else: 222 string = empty_app_smem.format(lnkr_sect, lnkr_sect.upper()) 223 224 with open(linker_file, "w") as fw: 225 fw.write(string) 226 227 228def parse_args(): 229 global args 230 parser = argparse.ArgumentParser( 231 description=__doc__, 232 formatter_class=argparse.RawDescriptionHelpFormatter) 233 parser.add_argument("-d", "--directory", required=False, default=None, 234 help="Root build directory") 235 parser.add_argument("-e", "--elf", required=False, default=None, 236 help="ELF file") 237 parser.add_argument("-f", "--compile-commands-file", required=False, 238 default=None, help="CMake compile commands file") 239 parser.add_argument("-o", "--output", required=False, 240 help="Output ld file") 241 parser.add_argument("-v", "--verbose", action="count", default=0, 242 help="Verbose Output") 243 parser.add_argument("-l", "--library", nargs=2, action="append", default=[], 244 metavar=("LIBRARY", "PARTITION"), 245 help="Include globals for a particular library or object filename into a designated partition") 246 parser.add_argument("--pinoutput", required=False, 247 help="Output ld file for pinned sections") 248 parser.add_argument("--pinpartitions", action="store", required=False, default="", 249 help="Comma separated names of partitions to be pinned in physical memory") 250 251 args = parser.parse_args() 252 253 254def main(): 255 parse_args() 256 partitions = {} 257 258 if args.directory is not None: 259 parse_obj_files(partitions) 260 if args.compile_commands_file is not None: 261 parse_compile_command_file(partitions) 262 elif args.elf is not None: 263 parse_elf_file(partitions) 264 else: 265 return 266 267 for lib, ptn in args.library: 268 if ptn not in partitions: 269 partitions[ptn] = {} 270 271 if LIB not in partitions[ptn]: 272 partitions[ptn][LIB] = [lib] 273 else: 274 partitions[ptn][LIB].append(lib) 275 276 if args.pinoutput: 277 pin_part_names = args.pinpartitions.split(',') 278 279 generic_partitions = {key: value for key, value in partitions.items() 280 if key not in pin_part_names} 281 pinned_partitions = {key: value for key, value in partitions.items() 282 if key in pin_part_names} 283 else: 284 generic_partitions = partitions 285 286 # Sample partitions.items() list before sorting: 287 # [ ('part1', {'size': 64}), ('part3', {'size': 64}, ... 288 # ('part0', {'size': 334}) ] 289 decreasing_tuples = sorted(generic_partitions.items(), 290 key=lambda x: (x[1][SZ], x[0]), reverse=True) 291 292 partsorted = OrderedDict(decreasing_tuples) 293 294 generate_final_linker(args.output, partsorted) 295 if args.verbose: 296 print("Partitions retrieved:") 297 for key in partsorted: 298 print(" {0}: size {1}: {2}".format(key, 299 partsorted[key][SZ], 300 partsorted[key][SRC])) 301 302 if args.pinoutput: 303 decreasing_tuples = sorted(pinned_partitions.items(), 304 key=lambda x: (x[1][SZ], x[0]), reverse=True) 305 306 partsorted = OrderedDict(decreasing_tuples) 307 308 generate_final_linker(args.pinoutput, partsorted, lnkr_sect="_pinned") 309 if args.verbose: 310 print("Pinned partitions retrieved:") 311 for key in partsorted: 312 print(" {0}: size {1}: {2}".format(key, 313 partsorted[key][SZ], 314 partsorted[key][SRC])) 315 316 317if __name__ == '__main__': 318 main() 319