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 either a linker script fragment or linker-script generator 31fragments containing the definition of the app shared memory section, which 32is further divided, for each partition found, into data and BSS for each 33partition. 34""" 35 36#The linker script fragments looks something like this: 37# SECTION_PROLOGUE(_APP_SMEM_SECTION_NAME,,) 38# { 39# APP_SHARED_ALIGN; 40# _app_smem_start = .; 41 42# /* Auto generated code do not modify */ 43# SMEM_PARTITION_ALIGN(z_data_smem_ztest_mem_partition_bss_end - z_data_smem_ztest_mem_partition_part_start); 44# z_data_smem_ztest_mem_partition_part_start = .; 45# KEEP(*(data_smem_ztest_mem_partition_data*)) 46 47# z_data_smem_ztest_mem_partition_bss_start = .; 48# KEEP(*(data_smem_ztest_mem_partition_bss*)) 49 50# z_data_smem_ztest_mem_partition_bss_end = .; 51# SMEM_PARTITION_ALIGN(z_data_smem_ztest_mem_partition_bss_end - z_data_smem_ztest_mem_partition_part_start); 52# z_data_smem_ztest_mem_partition_part_end = .; 53 54# /* Auto generated code do not modify */ 55# SMEM_PARTITION_ALIGN(z_data_smem_z_libc_partition_bss_end - z_data_smem_z_libc_partition_part_start); 56# z_data_smem_z_libc_partition_part_start = .; 57# KEEP(*(data_smem_z_libc_partition_data*)) 58 59# z_data_smem_z_libc_partition_bss_start = .; 60# KEEP(*(data_smem_z_libc_partition_bss*)) 61 62# z_data_smem_z_libc_partition_bss_end = .; 63# SMEM_PARTITION_ALIGN(z_data_smem_z_libc_partition_bss_end - z_data_smem_z_libc_partition_part_start); 64# z_data_smem_z_libc_partition_part_end = .; 65 66# APP_SHARED_ALIGN; 67# _app_smem_end = .; 68# } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) 69 70# z_data_smem_ztest_mem_partition_part_size = z_data_smem_ztest_mem_partition_part_end - z_data_smem_ztest_mem_partition_part_start; 71# z_data_smem_ztest_mem_partition_bss_size = z_data_smem_ztest_mem_partition_bss_end - z_data_smem_ztest_mem_partition_bss_start; 72 73# z_data_smem_z_libc_partition_part_size = z_data_smem_z_libc_partition_part_end - z_data_smem_z_libc_partition_part_start; 74# z_data_smem_z_libc_partition_bss_size = z_data_smem_z_libc_partition_bss_end - z_data_smem_z_libc_partition_bss_start; 75 76# the linker script generator fragments looks something like this: 77# There is a zephyr_linker_group() called APP_SMEM_GROUP for these sections 78# defined in linker.cmake 79# _APP_SMEM_SECTION_NAME is #defined in sections.h to "app_smem". Ideally we should have that here too. 80# 81# zephyr_linker_section(NAME _APP_SMEM_SECTION_NAME GROUP APP_SMEM_GROUP NOINPUT ALIGN_WITH_INPUT) 82# zephyr_linker_section_configure( 83# SECTION 84# _APP_SMEM_SECTION_NAME 85# SYMBOLS 86# _app_smem_start 87# ) 88# zephyr_linker_section_configure( 89# SECTION 90# _APP_SMEM_SECTION_NAME 91# INPUT 92# "data_smem_ztest_mem_partition_data*" 93# KEEP 94# SYMBOLS 95# z_data_smem_ztest_mem_partition_part_start 96# ) 97# zephyr_linker_section_configure( 98# SECTION 99# _APP_SMEM_SECTION_NAME 100# INPUT 101# "data_smem_ztest_mem_partition_bss*" 102# KEEP 103# SYMBOLS 104# z_data_smem_ztest_mem_partition_bss_start 105# z_data_smem_ztest_mem_partition_bss_end 106# ) 107# zephyr_linker_section_configure( 108# SECTION 109# _APP_SMEM_SECTION_NAME 110# SYMBOLS 111# z_data_smem_ztest_mem_partition_part_end 112# ) 113 114# zephyr_linker_section_configure( 115# SECTION 116# _APP_SMEM_SECTION_NAME 117# INPUT 118# "data_smem_z_libc_partition_data*" 119# KEEP 120# SYMBOLS 121# z_data_smem_z_libc_partition_part_start 122# ) 123# zephyr_linker_section_configure( 124# SECTION 125# _APP_SMEM_SECTION_NAME 126# INPUT 127# "data_smem_z_libc_partition_bss*" 128# KEEP 129# SYMBOLS 130# z_data_smem_z_libc_partition_bss_start 131# z_data_smem_z_libc_partition_bss_end 132# ) 133# zephyr_linker_section_configure( 134# SECTION 135# _APP_SMEM_SECTION_NAME 136# SYMBOLS 137# z_data_smem_z_libc_partition_part_end 138# ) 139# zephyr_linker_section_configure( 140# SECTION 141# _APP_SMEM_SECTION_NAME 142# SYMBOLS 143# _app_smem_end 144# ) 145# zephyr_linker_symbol( 146# SYMBOL 147# z_data_smem_ztest_mem_partition_part_size 148# EXPR 149# "(@z_data_smem_ztest_mem_partition_part_end@ - @z_data_smem_ztest_mem_partition_part_start@)" 150# ) 151# zephyr_linker_symbol( 152# SYMBOL 153# z_data_smem_ztest_mem_partition_bss_size 154# EXPR 155# "(@z_data_smem_ztest_mem_partition_bss_end@ - @z_data_smem_ztest_mem_partition_bss_start@)" 156# ) 157 158# zephyr_linker_symbol( 159# SYMBOL 160# z_data_smem_z_libc_partition_part_size 161# EXPR 162# "(@z_data_smem_z_libc_partition_part_end@ - @z_data_smem_z_libc_partition_part_start@)" 163# ) 164# zephyr_linker_symbol( 165# SYMBOL 166# z_data_smem_z_libc_partition_bss_size 167# EXPR 168# "(@z_data_smem_z_libc_partition_bss_end@ - @z_data_smem_z_libc_partition_bss_start@)" 169# ) 170 171 172import sys 173import argparse 174import json 175import os 176import re 177from collections import OrderedDict 178from elftools.elf.elffile import ELFFile 179from elftools.elf.sections import SymbolTableSection 180import elftools.common.exceptions 181 182SZ = 'size' 183SRC = 'sources' 184LIB = 'libraries' 185 186# This script will create sections and linker variables to place the 187# application shared memory partitions. 188# these are later read by the macros defined in app_memdomain.h for 189# initialization purpose when USERSPACE is enabled. 190 191class LDScriptTemlate: 192 data_template = """ 193 /* Auto generated code do not modify */ 194 SMEM_PARTITION_ALIGN(z_data_smem_{partition}_bss_end - z_data_smem_{partition}_part_start); 195 z_data_smem_{partition}_part_start = .; 196 KEEP(*(data_smem_{partition}_data*)) 197 """ 198 199 library_data_template = """ 200 \"*{lib}:*\"(.data .data.* .sdata .sdata.*) 201 """ 202 203 bss_template = """ 204 z_data_smem_{partition}_bss_start = .; 205 KEEP(*(data_smem_{partition}_bss*)) 206 """ 207 208 library_bss_template = """ 209 \"*{lib}:*\"(.bss .bss.* .sbss .sbss.* COMMON COMMON.*) 210 """ 211 212 footer_template = """ 213 z_data_smem_{partition}_bss_end = .; 214 SMEM_PARTITION_ALIGN(z_data_smem_{partition}_bss_end - z_data_smem_{partition}_part_start); 215 z_data_smem_{partition}_part_end = .; 216 """ 217 218 linker_start_seq = """ 219 SECTION_PROLOGUE(_APP_SMEM{SECTION}_SECTION_NAME,,) 220 {{ 221 APP_SHARED_ALIGN; 222 _app_smem{section}_start = .; 223 """ 224 225 linker_end_seq = """ 226 APP_SHARED_ALIGN; 227 _app_smem{section}_end = .; 228 }} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) 229 """ 230 231 empty_app_smem = """ 232 SECTION_PROLOGUE(_APP_SMEM{SECTION}_SECTION_NAME,,) 233 {{ 234 #ifdef EMPTY_APP_SHARED_ALIGN 235 EMPTY_APP_SHARED_ALIGN; 236 #endif 237 _app_smem{section}_start = .; 238 _app_smem{section}_end = .; 239 }} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) 240 """ 241 242 size_cal_string = """ 243 z_data_smem_{partition}_part_size = z_data_smem_{partition}_part_end - z_data_smem_{partition}_part_start; 244 z_data_smem_{partition}_bss_size = z_data_smem_{partition}_bss_end - z_data_smem_{partition}_bss_start; 245 """ 246 247def cmake_list_append(list, props): 248 tokens = [fr"{key}\;{value}" for key, value in props.items()] 249 stuff = r"\;".join(tokens) 250 return """list(APPEND """+ list + """ \"{{""" + stuff + """}}\")\n""" 251 252def zephyr_linker_section(name, group, align = None, noinput = True, align_with_input = True, hidden = False, noinit = False): 253 props = {"""NAME""": name, """GROUP""": group} 254 if align : 255 props['ALIGN'] = align 256 #The bool flags are always there: 257 props['NOIPUT'] = noinput 258 props['ALIGN_WITH_INPUT'] = align_with_input 259 props['HIDDEN'] = hidden 260 props['NOINIT'] = noinit 261 return cmake_list_append("SECTIONS", props) 262 263def zephyr_linker_section_configure(section, input = None, symbols = None, align = None, any = False, first = False, keep = False): 264 props = {"SECTION": section} 265 if input: 266 props["INPUT"] = input 267 #ANY\;FALSE\;FIRST\;FALSE\;KEEP\;FALSE\ 268 props['ANY'] = any 269 props['FIRST'] = first 270 props['KEEP'] = keep 271 if symbols: 272 props["SYMBOLS"] = symbols 273 if align: 274 props["ALIGN"] = align 275 return cmake_list_append("SECTION_SETTINGS", props) 276 277def zephyr_linker_symbol(symbol, expr) : 278 return cmake_list_append("SYMBOLS", {'SYMBOL': symbol, 'EXPR':expr}) 279 280class CmakeTemplate: 281 section_name = "@_APP_SMEM{SECTION}_SECTION_NAME@" 282 data_template = ( 283 zephyr_linker_section_configure(section=section_name, align="@SMEM_PARTITION_ALIGN_BYTES@")+ 284 zephyr_linker_section_configure(section=section_name, input="data_smem_{partition}_data*", symbols="z_data_smem_{partition}_part_start", keep=True) 285 ) 286 287 library_data_template = zephyr_linker_section_configure(section=section_name, input="*{lib}:*(.data .data.* .sdata .sdata.*)") 288 289 bss_template = ( 290 zephyr_linker_section_configure(section=section_name, input="data_smem_{partition}_bss*", symbols="z_data_smem_{partition}_bss_start", keep=True) 291 ) 292 293 library_bss_template = zephyr_linker_section_configure(section=section_name, input="*{lib}:*(.bss .bss.* .sbss .sbss.* COMMON COMMON.*)") 294 295 footer_template = ( 296 zephyr_linker_section_configure(section=section_name, symbols="z_data_smem_{partition}_bss_end", keep=True) + 297 zephyr_linker_section_configure(section=section_name, align="@SMEM_PARTITION_ALIGN_BYTES@") + 298 zephyr_linker_section_configure(section=section_name, symbols="z_data_smem_{partition}_part_end", keep=True) 299 ) 300 301 linker_start_seq = ( 302 zephyr_linker_section(name=section_name, group="APP_SMEM_GROUP", noinput=True, align_with_input=True) + 303 zephyr_linker_section_configure(section=section_name, align="@APP_SHARED_ALIGN_BYTES@", symbols="_app_smem{section}_start")) 304 305 linker_end_seq = ( 306 zephyr_linker_section_configure(section=section_name, align="@APP_SHARED_ALIGN_BYTES@") + 307 zephyr_linker_section_configure(section=section_name, symbols="_app_smem{section}_end") ) 308 309 empty_app_smem = ( 310 zephyr_linker_section(name=section_name, group="APP_SMEM_GROUP", align="@APP_SHARED_ALIGN_BYTES@", noinput=True, align_with_input=True) + 311 zephyr_linker_section_configure(section = section_name, symbols="_app_smem{section}_start") + 312 zephyr_linker_section_configure(section = section_name, symbols="_app_smem{section}_end") ) 313 314 size_cal_string = ( 315 zephyr_linker_symbol(symbol='z_data_smem_{partition}_part_size', expr='@z_data_smem_{partition}_part_end@ - @z_data_smem_{partition}_part_start@') + 316 zephyr_linker_symbol(symbol='z_data_smem_{partition}_bss_size', expr='@z_data_smem_{partition}_bss_end@ - @z_data_smem_{partition}_bss_start@')) 317 318section_regex = re.compile(r'data_smem_([A-Za-z0-9_]*)_(data|bss)*') 319 320elf_part_size_regex = re.compile(r'z_data_smem_(.*)_part_size') 321 322def find_obj_file_partitions(filename, partitions): 323 with open(filename, 'rb') as f: 324 try: 325 full_lib = ELFFile(f) 326 except elftools.common.exceptions.ELFError as e: 327 exit(f"Error: {filename}: {e}") 328 329 if not full_lib: 330 sys.exit("Error parsing file: " + filename) 331 332 sections = [x for x in full_lib.iter_sections()] 333 for section in sections: 334 m = section_regex.match(section.name) 335 if not m: 336 continue 337 338 partition_name = m.groups()[0] 339 if partition_name not in partitions: 340 partitions[partition_name] = {SZ: section.header.sh_size} 341 342 if args.verbose: 343 partitions[partition_name][SRC] = filename 344 345 else: 346 partitions[partition_name][SZ] += section.header.sh_size 347 348 349 return partitions 350 351 352def parse_obj_files(partitions): 353 # Iterate over all object files to find partitions 354 for dirpath, _, files in os.walk(args.directory): 355 for filename in files: 356 if re.match(r".*\.obj$", filename): 357 fullname = os.path.join(dirpath, filename) 358 fsize = os.path.getsize(fullname) 359 if fsize != 0: 360 find_obj_file_partitions(fullname, partitions) 361 362 363def parse_compile_command_file(partitions): 364 # Iterate over all entries to find object files. 365 # Thereafter process each object file to find partitions 366 object_pattern = re.compile(r'-o\s+(\S*)') 367 with open(args.compile_commands_file, 'rb') as f: 368 commands = json.load(f) 369 for command in commands: 370 build_dir = command.get('directory') 371 compile_command = command.get('command') 372 compile_arg = object_pattern.search(compile_command) 373 obj_file = None if compile_arg is None else compile_arg.group(1) 374 if obj_file: 375 fullname = os.path.join(build_dir, obj_file) 376 # Because of issue #40635, then not all objects referenced by 377 # the compile_commands.json file may be available, therefore 378 # only include existing files. 379 if os.path.exists(fullname): 380 find_obj_file_partitions(fullname, partitions) 381 382 383def parse_elf_file(partitions): 384 with open(args.elf, 'rb') as f: 385 try: 386 elffile = ELFFile(f) 387 except elftools.common.exceptions.ELFError as e: 388 exit(f"Error: {args.elf}: {e}") 389 390 symbol_tbls = [s for s in elffile.iter_sections() 391 if isinstance(s, SymbolTableSection)] 392 393 for section in symbol_tbls: 394 for symbol in section.iter_symbols(): 395 if symbol['st_shndx'] != "SHN_ABS": 396 continue 397 398 x = elf_part_size_regex.match(symbol.name) 399 if not x: 400 continue 401 402 partition_name = x.groups()[0] 403 size = symbol['st_value'] 404 if partition_name not in partitions: 405 partitions[partition_name] = {SZ: size} 406 407 if args.verbose: 408 partitions[partition_name][SRC] = args.elf 409 410 else: 411 partitions[partition_name][SZ] += size 412 413def generate_final(linker_file, partitions, lnkr_sect=""): 414 if linker_file.endswith(".ld"): 415 template = LDScriptTemlate 416 else: 417 template = CmakeTemplate 418 generate_final_linker(template, linker_file, partitions, lnkr_sect) 419 420def generate_final_linker(template, linker_file, partitions, lnkr_sect): 421 string = "" 422 props = { 'section' : lnkr_sect, 'SECTION' : lnkr_sect.upper() } 423 if len(partitions) > 0: 424 string = template.linker_start_seq.format(**props) 425 size_string = '' 426 for partition, item in partitions.items(): 427 part_props = props 428 part_props["partition"] = partition 429 string += template.data_template.format(**part_props) 430 if LIB in item: 431 for lib in item[LIB]: 432 lib_props = part_props 433 lib_props["lib"] = lib 434 string += template.library_data_template.format(**lib_props) 435 string += template.bss_template.format(**part_props) 436 if LIB in item: 437 for lib in item[LIB]: 438 lib_props = part_props 439 lib_props["lib"] = lib 440 string += template.library_bss_template.format(**lib_props) 441 string += template.footer_template.format(**part_props) 442 size_string += template.size_cal_string.format(**part_props) 443 444 string += template.linker_end_seq.format(**props) 445 string += size_string 446 else: 447 string = template.empty_app_smem.format(**props) #lnkr_sect, lnkr_sect.upper()) 448 449 with open(linker_file, "w") as fw: 450 fw.write(string) 451 452def parse_args(): 453 global args 454 parser = argparse.ArgumentParser( 455 description=__doc__, 456 formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) 457 parser.add_argument("-d", "--directory", required=False, default=None, 458 help="Root build directory") 459 parser.add_argument("-e", "--elf", required=False, default=None, 460 help="ELF file") 461 parser.add_argument("-f", "--compile-commands-file", required=False, 462 default=None, help="CMake compile commands file") 463 parser.add_argument("-o", "--output", required=False, 464 help="Output ld file") 465 parser.add_argument("-v", "--verbose", action="count", default=0, 466 help="Verbose Output") 467 parser.add_argument("-l", "--library", nargs=2, action="append", default=[], 468 metavar=("LIBRARY", "PARTITION"), 469 help="Include globals for a particular library or object filename into a designated partition") 470 parser.add_argument("--pinoutput", required=False, 471 help="Output ld file for pinned sections") 472 parser.add_argument("--pinpartitions", action="store", required=False, default="", 473 help="Comma separated names of partitions to be pinned in physical memory") 474 475 args, _ = parser.parse_known_args() 476 477 478def main(): 479 parse_args() 480 partitions = {} 481 482 if args.directory is not None: 483 parse_obj_files(partitions) 484 if args.compile_commands_file is not None: 485 parse_compile_command_file(partitions) 486 elif args.elf is not None: 487 parse_elf_file(partitions) 488 else: 489 return 490 491 for lib, ptn in args.library: 492 if ptn not in partitions: 493 partitions[ptn] = {} 494 495 if LIB not in partitions[ptn]: 496 partitions[ptn][LIB] = [lib] 497 else: 498 partitions[ptn][LIB].append(lib) 499 500 if args.pinoutput: 501 pin_part_names = args.pinpartitions.split(',') 502 503 generic_partitions = {key: value for key, value in partitions.items() 504 if key not in pin_part_names} 505 pinned_partitions = {key: value for key, value in partitions.items() 506 if key in pin_part_names} 507 else: 508 generic_partitions = partitions 509 510 # Sample partitions.items() list before sorting: 511 # [ ('part1', {'size': 64}), ('part3', {'size': 64}, ... 512 # ('part0', {'size': 334}) ] 513 decreasing_tuples = sorted(generic_partitions.items(), 514 key=lambda x: (x[1][SZ], x[0]), reverse=True) 515 516 partsorted = OrderedDict(decreasing_tuples) 517 518 generate_final(args.output, partsorted) 519 520 if args.verbose: 521 print("Partitions retrieved:") 522 for key in partsorted: 523 print(" {0}: size {1}: {2}".format(key, 524 partsorted[key][SZ], 525 partsorted[key][SRC])) 526 527 if args.pinoutput: 528 decreasing_tuples = sorted(pinned_partitions.items(), 529 key=lambda x: (x[1][SZ], x[0]), reverse=True) 530 531 partsorted = OrderedDict(decreasing_tuples) 532 533 generate_final(args.pinoutput, partsorted, lnkr_sect="_pinned") 534 if args.verbose: 535 print("Pinned partitions retrieved:") 536 for key in partsorted: 537 print(" {0}: size {1}: {2}".format(key, 538 partsorted[key][SZ], 539 partsorted[key][SRC])) 540 541 542if __name__ == '__main__': 543 main() 544