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#ifdef EMPTY_APP_SHARED_ALIGN 96 EMPTY_APP_SHARED_ALIGN; 97#endif 98 _app_smem{0}_start = .; 99 _app_smem{0}_end = .; 100 }} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) 101""" 102 103size_cal_string = """ 104 z_data_smem_{0}_part_size = z_data_smem_{0}_part_end - z_data_smem_{0}_part_start; 105 z_data_smem_{0}_bss_size = z_data_smem_{0}_bss_end - z_data_smem_{0}_bss_start; 106""" 107 108section_regex = re.compile(r'data_smem_([A-Za-z0-9_]*)_(data|bss)*') 109 110elf_part_size_regex = re.compile(r'z_data_smem_(.*)_part_size') 111 112def find_obj_file_partitions(filename, partitions): 113 with open(filename, 'rb') as f: 114 try: 115 full_lib = ELFFile(f) 116 except elftools.common.exceptions.ELFError as e: 117 exit(f"Error: {filename}: {e}") 118 119 if not full_lib: 120 sys.exit("Error parsing file: " + filename) 121 122 sections = [x for x in full_lib.iter_sections()] 123 for section in sections: 124 m = section_regex.match(section.name) 125 if not m: 126 continue 127 128 partition_name = m.groups()[0] 129 if partition_name not in partitions: 130 partitions[partition_name] = {SZ: section.header.sh_size} 131 132 if args.verbose: 133 partitions[partition_name][SRC] = filename 134 135 else: 136 partitions[partition_name][SZ] += section.header.sh_size 137 138 139 return partitions 140 141 142def parse_obj_files(partitions): 143 # Iterate over all object files to find partitions 144 for dirpath, _, files in os.walk(args.directory): 145 for filename in files: 146 if re.match(r".*\.obj$", filename): 147 fullname = os.path.join(dirpath, filename) 148 fsize = os.path.getsize(fullname) 149 if fsize != 0: 150 find_obj_file_partitions(fullname, partitions) 151 152 153def parse_compile_command_file(partitions): 154 # Iterate over all entries to find object files. 155 # Thereafter process each object file to find partitions 156 object_pattern = re.compile(r'-o\s+(\S*)') 157 with open(args.compile_commands_file, 'rb') as f: 158 commands = json.load(f) 159 for command in commands: 160 build_dir = command.get('directory') 161 compile_command = command.get('command') 162 compile_arg = object_pattern.search(compile_command) 163 obj_file = None if compile_arg is None else compile_arg.group(1) 164 if obj_file: 165 fullname = os.path.join(build_dir, obj_file) 166 # Because of issue #40635, then not all objects referenced by 167 # the compile_commands.json file may be available, therefore 168 # only include existing files. 169 if os.path.exists(fullname): 170 find_obj_file_partitions(fullname, partitions) 171 172 173def parse_elf_file(partitions): 174 with open(args.elf, 'rb') as f: 175 try: 176 elffile = ELFFile(f) 177 except elftools.common.exceptions.ELFError as e: 178 exit(f"Error: {args.elf}: {e}") 179 180 symbol_tbls = [s for s in elffile.iter_sections() 181 if isinstance(s, SymbolTableSection)] 182 183 for section in symbol_tbls: 184 for symbol in section.iter_symbols(): 185 if symbol['st_shndx'] != "SHN_ABS": 186 continue 187 188 x = elf_part_size_regex.match(symbol.name) 189 if not x: 190 continue 191 192 partition_name = x.groups()[0] 193 size = symbol['st_value'] 194 if partition_name not in partitions: 195 partitions[partition_name] = {SZ: size} 196 197 if args.verbose: 198 partitions[partition_name][SRC] = args.elf 199 200 else: 201 partitions[partition_name][SZ] += size 202 203 204def generate_final_linker(linker_file, partitions, lnkr_sect=""): 205 string = "" 206 207 if len(partitions) > 0: 208 string = linker_start_seq.format(lnkr_sect, lnkr_sect.upper()) 209 size_string = '' 210 for partition, item in partitions.items(): 211 string += data_template.format(partition) 212 if LIB in item: 213 for lib in item[LIB]: 214 string += library_data_template.format(lib) 215 string += bss_template.format(partition, lnkr_sect) 216 if LIB in item: 217 for lib in item[LIB]: 218 string += library_bss_template.format(lib) 219 string += footer_template.format(partition) 220 size_string += size_cal_string.format(partition) 221 222 string += linker_end_seq.format(lnkr_sect) 223 string += size_string 224 else: 225 string = empty_app_smem.format(lnkr_sect, lnkr_sect.upper()) 226 227 with open(linker_file, "w") as fw: 228 fw.write(string) 229 230 231def parse_args(): 232 global args 233 parser = argparse.ArgumentParser( 234 description=__doc__, 235 formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) 236 parser.add_argument("-d", "--directory", required=False, default=None, 237 help="Root build directory") 238 parser.add_argument("-e", "--elf", required=False, default=None, 239 help="ELF file") 240 parser.add_argument("-f", "--compile-commands-file", required=False, 241 default=None, help="CMake compile commands file") 242 parser.add_argument("-o", "--output", required=False, 243 help="Output ld file") 244 parser.add_argument("-v", "--verbose", action="count", default=0, 245 help="Verbose Output") 246 parser.add_argument("-l", "--library", nargs=2, action="append", default=[], 247 metavar=("LIBRARY", "PARTITION"), 248 help="Include globals for a particular library or object filename into a designated partition") 249 parser.add_argument("--pinoutput", required=False, 250 help="Output ld file for pinned sections") 251 parser.add_argument("--pinpartitions", action="store", required=False, default="", 252 help="Comma separated names of partitions to be pinned in physical memory") 253 254 args = parser.parse_args() 255 256 257def main(): 258 parse_args() 259 partitions = {} 260 261 if args.directory is not None: 262 parse_obj_files(partitions) 263 if args.compile_commands_file is not None: 264 parse_compile_command_file(partitions) 265 elif args.elf is not None: 266 parse_elf_file(partitions) 267 else: 268 return 269 270 for lib, ptn in args.library: 271 if ptn not in partitions: 272 partitions[ptn] = {} 273 274 if LIB not in partitions[ptn]: 275 partitions[ptn][LIB] = [lib] 276 else: 277 partitions[ptn][LIB].append(lib) 278 279 if args.pinoutput: 280 pin_part_names = args.pinpartitions.split(',') 281 282 generic_partitions = {key: value for key, value in partitions.items() 283 if key not in pin_part_names} 284 pinned_partitions = {key: value for key, value in partitions.items() 285 if key in pin_part_names} 286 else: 287 generic_partitions = partitions 288 289 # Sample partitions.items() list before sorting: 290 # [ ('part1', {'size': 64}), ('part3', {'size': 64}, ... 291 # ('part0', {'size': 334}) ] 292 decreasing_tuples = sorted(generic_partitions.items(), 293 key=lambda x: (x[1][SZ], x[0]), reverse=True) 294 295 partsorted = OrderedDict(decreasing_tuples) 296 297 generate_final_linker(args.output, partsorted) 298 if args.verbose: 299 print("Partitions retrieved:") 300 for key in partsorted: 301 print(" {0}: size {1}: {2}".format(key, 302 partsorted[key][SZ], 303 partsorted[key][SRC])) 304 305 if args.pinoutput: 306 decreasing_tuples = sorted(pinned_partitions.items(), 307 key=lambda x: (x[1][SZ], x[0]), reverse=True) 308 309 partsorted = OrderedDict(decreasing_tuples) 310 311 generate_final_linker(args.pinoutput, partsorted, lnkr_sect="_pinned") 312 if args.verbose: 313 print("Pinned partitions retrieved:") 314 for key in partsorted: 315 print(" {0}: size {1}: {2}".format(key, 316 partsorted[key][SZ], 317 partsorted[key][SRC])) 318 319 320if __name__ == '__main__': 321 main() 322