1#!/usr/bin/env python3 2# 3# Copyright (c) 2018 Intel Corporation. 4# 5# SPDX-License-Identifier: Apache-2.0 6# 7 8""" 9This script will relocate .text, .rodata, .data and .bss sections from required files 10and places it in the required memory region. This memory region and file 11are given to this python script in the form of a file. 12A regular expression filter can be applied to select only the required sections from the file. 13 14Example of content in such an input file would be:: 15 16 SRAM2:COPY:/home/xyz/zephyr/samples/hello_world/src/main.c,.*foo|.*bar 17 SRAM1:COPY:/home/xyz/zephyr/samples/hello_world/src/main2.c,.*bar 18 FLASH2:NOCOPY:/home/xyz/zephyr/samples/hello_world/src/main3.c, 19 20One can also specify the program header for a given memory region: 21 22 SRAM2\\ :phdr0:COPY:/home/xyz/zephyr/samples/hello_world/src/main.c, 23 24To invoke this script:: 25 26 python3 gen_relocate_app.py -i input_file -o generated_linker -c generated_code 27 28Configuration that needs to be sent to the python script. 29 30- If the memory is like SRAM1/SRAM2/CCD/AON then place full object in 31 the sections 32- If the memory type is appended with _DATA / _TEXT/ _RODATA/ _BSS only the 33 selected memory is placed in the required memory region. Others are 34 ignored. 35- COPY/NOCOPY defines whether the script should generate the relocation code in 36 code_relocation.c or not 37- NOKEEP will suppress the default behavior of marking every relocated symbol 38 with KEEP() in the generated linker script. 39 40Multiple regions can be appended together like SRAM2_DATA_BSS 41this will place data and bss inside SRAM2. 42""" 43 44import argparse 45import glob 46import re 47import sys 48import warnings 49from collections import defaultdict 50from enum import Enum 51from pathlib import Path 52from typing import NamedTuple, NewType 53 54from elftools.elf.elffile import ELFFile 55from elftools.elf.sections import SymbolTableSection 56 57MemoryRegion = NewType('MemoryRegion', str) 58 59 60class SectionKind(Enum): 61 TEXT = "text" 62 RODATA = "rodata" 63 DATA = "data" 64 BSS = "bss" 65 NOINIT = "noinit" 66 LITERAL = "literal" 67 68 def __str__(self): 69 return self.name 70 71 @classmethod 72 def for_section_named(cls, name: str): 73 """ 74 Return the kind of section that includes a section with the given name. 75 76 >>> SectionKind.for_section_with_name(".rodata.str1.4") 77 <SectionKind.RODATA: 'rodata'> 78 >>> SectionKind.for_section_with_name(".device_deps") 79 None 80 """ 81 if ".text." in name: 82 return cls.TEXT 83 elif ".rodata." in name: 84 return cls.RODATA 85 elif ".data." in name: 86 return cls.DATA 87 elif ".bss." in name: 88 return cls.BSS 89 elif ".noinit." in name: 90 return cls.NOINIT 91 elif ".literal." in name: 92 return cls.LITERAL 93 else: 94 return None 95 96 97class OutputSection(NamedTuple): 98 obj_file_name: str 99 section_name: str 100 keep: bool = True 101 102 103PRINT_TEMPLATE = """ 104 KEEP(*{obj_file_name}({section_name})) 105""" 106 107PRINT_TEMPLATE_NOKEEP = """ 108 *{obj_file_name}({section_name}) 109""" 110 111SECTION_LOAD_MEMORY_SEQ = """ 112 __{mem}_{kind}_rom_start = LOADADDR(.{mem}_{kind}_reloc); 113""" 114 115LOAD_ADDRESS_LOCATION_FLASH = """ 116#ifdef CONFIG_XIP 117GROUP_DATA_LINK_IN({0}, ROMABLE_REGION) 118#else 119GROUP_DATA_LINK_IN({0}, {0}) 120#endif 121""" 122 123LOAD_ADDRESS_LOCATION_FLASH_NOCOPY = """ 124GROUP_LINK_IN({0}) 125""" 126 127LOAD_ADDRESS_LOCATION_BSS = "GROUP_LINK_IN({0})" 128 129LOAD_ADDRESS_LOCATION_NOLOAD = "GROUP_NOLOAD_LINK_IN({0}, {0})" 130 131MPU_RO_REGION_START = """ 132 133 _{mem}_mpu_ro_region_start = ORIGIN({mem_upper}); 134 135""" 136 137MPU_RO_REGION_END = """ 138 139 _{mem}_mpu_ro_region_end = .; 140 141""" 142 143# generic section creation format 144LINKER_SECTION_SEQ = """ 145 146/* Linker section for memory region {mem_upper} for {kind_name} section */ 147 148 SECTION_PROLOGUE(.{mem}_{kind}_reloc,{options},) 149 {{ 150 . = ALIGN(4); 151 {linker_sections} 152 . = ALIGN(4); 153 }} {load_address} 154 __{mem}_{kind}_reloc_end = .; 155 __{mem}_{kind}_reloc_start = ADDR(.{mem}_{kind}_reloc); 156 __{mem}_{kind}_reloc_size = __{mem}_{kind}_reloc_end - __{mem}_{kind}_reloc_start; 157""" 158 159LINKER_SECTION_SEQ_MPU = """ 160 161/* Linker section for memory region {mem_upper} for {kind_name} section */ 162 163 SECTION_PROLOGUE(.{mem}_{kind}_reloc,{options},) 164 {{ 165 __{mem}_{kind}_reloc_start = .; 166 {linker_sections} 167#if {align_size} 168 . = ALIGN({align_size}); 169#else 170 MPU_ALIGN(__{mem}_{kind}_reloc_size); 171#endif 172 __{mem}_{kind}_reloc_end = .; 173 }} {load_address} 174 __{mem}_{kind}_reloc_size = __{mem}_{kind}_reloc_end - __{mem}_{kind}_reloc_start; 175""" 176 177SOURCE_CODE_INCLUDES = """ 178/* Auto generated code. Do not modify.*/ 179#include <zephyr/kernel.h> 180#include <zephyr/linker/linker-defs.h> 181#include <zephyr/kernel_structs.h> 182#include <kernel_internal.h> 183""" 184 185EXTERN_LINKER_VAR_DECLARATION = """ 186extern char __{mem}_{kind}_reloc_start[]; 187extern char __{mem}_{kind}_rom_start[]; 188extern char __{mem}_{kind}_reloc_size[]; 189""" 190 191 192DATA_COPY_FUNCTION = """ 193void data_copy_xip_relocation(void) 194{{ 195{0} 196}} 197""" 198 199BSS_ZEROING_FUNCTION = """ 200void bss_zeroing_relocation(void) 201{{ 202{0} 203}} 204""" 205 206MEMCPY_TEMPLATE = """ 207 z_early_memcpy(&__{mem}_{kind}_reloc_start, &__{mem}_{kind}_rom_start, 208 (size_t) &__{mem}_{kind}_reloc_size); 209 210""" 211 212MEMSET_TEMPLATE = """ 213 z_early_memset(&__{mem}_bss_reloc_start, 0, 214 (size_t) &__{mem}_bss_reloc_size); 215""" 216 217 218def region_is_default_ram(region_name: str) -> bool: 219 """ 220 Test whether a memory region with the given name is the system's default 221 RAM region or not. 222 223 This is used to determine whether some items need to be omitted from 224 custom regions and instead be placed in the default. In particular, mutable 225 data placed in the default RAM section is ignored and is allowed to be 226 handled normally by the linker because it is placed in that region anyway. 227 """ 228 return region_name == args.default_ram_region 229 230 231def find_sections(filename: str, symbol_filter: str) -> 'dict[SectionKind, list[OutputSection]]': 232 """ 233 Locate relocatable sections in the given object file. 234 235 The output value maps categories of sections to the list of actual sections 236 located in the object file that fit in that category. 237 """ 238 obj_file_path = Path(filename) 239 240 with open(obj_file_path, 'rb') as obj_file_desc: 241 full_lib = ELFFile(obj_file_desc) 242 if not full_lib: 243 sys.exit("Error parsing file: " + filename) 244 245 sections = [x for x in full_lib.iter_sections()] 246 out = defaultdict(list) 247 248 for section in sections: 249 if not re.search(symbol_filter, section.name): 250 # Section is filtered-out 251 continue 252 section_kind = SectionKind.for_section_named(section.name) 253 if section_kind is None: 254 continue 255 256 out[section_kind].append(OutputSection(obj_file_path.name, section.name)) 257 258 # Common variables will be placed in the .bss section 259 # only after linking in the final executable. This "if" finds 260 # common symbols and warns the user of the problem. 261 # The solution to which is simply assigning a 0 to 262 # bss variable and it will go to the required place. 263 if isinstance(section, SymbolTableSection): 264 265 def is_common_symbol(s): 266 return s.entry["st_shndx"] == "SHN_COMMON" 267 268 for symbol in filter(is_common_symbol, section.iter_symbols()): 269 warnings.warn( 270 "Common variable found. Move " 271 + symbol.name 272 + " to bss by assigning it to 0/NULL", 273 stacklevel=2, 274 ) 275 276 return out 277 278 279def assign_to_correct_mem_region( 280 memory_region: str, full_list_of_sections: 'dict[SectionKind, list[OutputSection]]' 281) -> 'dict[MemoryRegion, dict[SectionKind, list[OutputSection]]]': 282 """ 283 Generate a mapping of memory region to collection of output sections to be 284 placed in each region. 285 """ 286 use_section_kinds, memory_region = section_kinds_from_memory_region(memory_region) 287 288 memory_region, _, align_size = memory_region.partition('_') 289 if align_size: 290 mpu_align[memory_region] = int(align_size) 291 292 keep_sections = '|NOKEEP' not in memory_region 293 memory_region = memory_region.replace('|NOKEEP', '') 294 295 output_sections = {} 296 for used_kind in use_section_kinds: 297 # Pass through section kinds that go into this memory region 298 output_sections[used_kind] = [ 299 section._replace(keep=keep_sections) for section in full_list_of_sections[used_kind] 300 ] 301 302 return {MemoryRegion(memory_region): output_sections} 303 304 305def section_kinds_from_memory_region(memory_region: str) -> 'tuple[set[SectionKind], str]': 306 """ 307 Get the section kinds requested by the given memory region name. 308 309 Region names can be like RAM_RODATA_TEXT or just RAM; a section kind may 310 follow the region name. If no kinds are specified all are assumed. 311 312 In addition to the parsed kinds, the input region minus specifiers for those 313 kinds is returned. 314 315 >>> section_kinds_from_memory_region('SRAM2_TEXT') 316 ({<SectionKind.TEXT: 'text'>}, 'SRAM2') 317 """ 318 out = set() 319 for kind in SectionKind: 320 specifier = f"_{kind}" 321 if specifier in memory_region: 322 out.add(kind) 323 memory_region = memory_region.replace(specifier, "") 324 if not out: 325 # No listed kinds implies all of the kinds 326 out = set(SectionKind) 327 return (out, memory_region) 328 329 330def print_linker_sections(list_sections: 'list[OutputSection]'): 331 out = '' 332 for section in sorted(list_sections): 333 template = PRINT_TEMPLATE if section.keep else PRINT_TEMPLATE_NOKEEP 334 out += template.format( 335 obj_file_name=section.obj_file_name, section_name=section.section_name 336 ) 337 return out 338 339 340def add_phdr(memory_type, phdrs): 341 return f'{memory_type} {phdrs.get(memory_type, "")}' 342 343 344def string_create_helper( 345 kind: SectionKind, 346 memory_type, 347 full_list_of_sections: 'dict[SectionKind, list[OutputSection]]', 348 load_address_in_flash, 349 is_copy, 350 phdrs, 351): 352 linker_string = '' 353 354 if load_address_in_flash: 355 if is_copy: 356 phdr_template = LOAD_ADDRESS_LOCATION_FLASH 357 else: 358 phdr_template = LOAD_ADDRESS_LOCATION_FLASH_NOCOPY 359 else: 360 if kind is SectionKind.NOINIT: 361 phdr_template = LOAD_ADDRESS_LOCATION_NOLOAD 362 else: 363 phdr_template = LOAD_ADDRESS_LOCATION_BSS 364 365 load_address_string = phdr_template.format(add_phdr(memory_type, phdrs)) 366 367 if full_list_of_sections[kind]: 368 # Create a complete list of funcs/ variables that goes in for this 369 # memory type 370 tmp = print_linker_sections(full_list_of_sections[kind]) 371 if region_is_default_ram(memory_type) and kind in ( 372 SectionKind.DATA, 373 SectionKind.BSS, 374 SectionKind.NOINIT, 375 ): 376 linker_string += tmp 377 else: 378 fields = { 379 "mem": memory_type.lower(), 380 "mem_upper": memory_type.upper(), 381 "kind": kind.value, 382 "kind_name": kind, 383 "linker_sections": tmp, 384 "load_address": load_address_string, 385 "options": "(NOLOAD)" if kind is SectionKind.NOINIT else "", 386 } 387 388 if not region_is_default_ram(memory_type) and kind is SectionKind.RODATA: 389 linker_string += LINKER_SECTION_SEQ_MPU.format( 390 align_size=mpu_align.get(memory_type, 0), 391 **fields, 392 ) 393 else: 394 if region_is_default_ram(memory_type) and kind in ( 395 SectionKind.TEXT, 396 SectionKind.LITERAL, 397 ): 398 linker_string += LINKER_SECTION_SEQ_MPU.format(align_size=0, **fields) 399 else: 400 linker_string += LINKER_SECTION_SEQ.format(**fields) 401 if load_address_in_flash: 402 linker_string += SECTION_LOAD_MEMORY_SEQ.format(**fields) 403 return linker_string 404 405 406def generate_linker_script( 407 linker_file, sram_data_linker_file, sram_bss_linker_file, complete_list_of_sections, phdrs 408): 409 gen_string = '' 410 gen_string_sram_data = '' 411 gen_string_sram_bss = '' 412 413 for memory_type, full_list_of_sections in sorted(complete_list_of_sections.items()): 414 is_copy = bool("|COPY" in memory_type) 415 memory_type = memory_type.split("|", 1)[0] 416 417 if region_is_default_ram(memory_type) and is_copy: 418 gen_string += MPU_RO_REGION_START.format( 419 mem=memory_type.lower(), mem_upper=memory_type.upper() 420 ) 421 422 gen_string += string_create_helper( 423 SectionKind.LITERAL, 424 memory_type, 425 full_list_of_sections, 426 True, 427 is_copy, 428 phdrs, 429 ) 430 gen_string += string_create_helper( 431 SectionKind.TEXT, memory_type, full_list_of_sections, True, is_copy, phdrs 432 ) 433 gen_string += string_create_helper( 434 SectionKind.RODATA, memory_type, full_list_of_sections, True, is_copy, phdrs 435 ) 436 437 if region_is_default_ram(memory_type) and is_copy: 438 gen_string += MPU_RO_REGION_END.format(mem=memory_type.lower()) 439 440 data_sections = ( 441 string_create_helper( 442 SectionKind.DATA, 443 memory_type, 444 full_list_of_sections, 445 True, 446 True, 447 phdrs, 448 ) 449 + string_create_helper( 450 SectionKind.BSS, 451 memory_type, 452 full_list_of_sections, 453 False, 454 True, 455 phdrs, 456 ) 457 + string_create_helper( 458 SectionKind.NOINIT, 459 memory_type, 460 full_list_of_sections, 461 False, 462 False, 463 phdrs, 464 ) 465 ) 466 467 if region_is_default_ram(memory_type): 468 gen_string_sram_data += data_sections 469 else: 470 gen_string += data_sections 471 472 # finally writing to the linker file 473 with open(linker_file, "w") as file_desc: 474 file_desc.write(gen_string) 475 476 with open(sram_data_linker_file, "w") as file_desc: 477 file_desc.write(gen_string_sram_data) 478 479 with open(sram_bss_linker_file, "w") as file_desc: 480 file_desc.write(gen_string_sram_bss) 481 482 483def generate_memcpy_code(memory_type, full_list_of_sections, code_generation): 484 generate_sections, memory_type = section_kinds_from_memory_region(memory_type) 485 486 # Non-BSS sections get copied to the destination memory, except data in 487 # main memory which gets copied automatically. 488 for kind in (SectionKind.TEXT, SectionKind.RODATA, SectionKind.DATA): 489 if region_is_default_ram(memory_type) and kind is SectionKind.DATA: 490 continue 491 492 if kind in generate_sections and full_list_of_sections[kind]: 493 code_generation["copy_code"] += MEMCPY_TEMPLATE.format( 494 mem=memory_type.lower(), kind=kind.value 495 ) 496 code_generation["extern"] += EXTERN_LINKER_VAR_DECLARATION.format( 497 mem=memory_type.lower(), kind=kind.value 498 ) 499 500 # BSS sections in main memory are automatically zeroed; others need to have 501 # zeroing code generated. 502 if ( 503 SectionKind.BSS in generate_sections 504 and full_list_of_sections[SectionKind.BSS] 505 and not region_is_default_ram(memory_type) 506 ): 507 code_generation["zero_code"] += MEMSET_TEMPLATE.format(mem=memory_type.lower()) 508 code_generation["extern"] += EXTERN_LINKER_VAR_DECLARATION.format( 509 mem=memory_type.lower(), kind=SectionKind.BSS.value 510 ) 511 512 return code_generation 513 514 515def dump_header_file(header_file, code_generation): 516 code_string = '' 517 # create a dummy void function if there is no code to generate for 518 # bss/data/text regions 519 520 code_string += code_generation["extern"] 521 code_string += DATA_COPY_FUNCTION.format(code_generation["copy_code"] or "return;") 522 code_string += BSS_ZEROING_FUNCTION.format(code_generation["zero_code"] or "return;") 523 524 with open(header_file, "w") as header_file_desc: 525 header_file_desc.write(SOURCE_CODE_INCLUDES) 526 header_file_desc.write(code_string) 527 528 529def parse_args(): 530 global args 531 parser = argparse.ArgumentParser( 532 description=__doc__, 533 formatter_class=argparse.RawDescriptionHelpFormatter, 534 allow_abbrev=False, 535 ) 536 parser.add_argument("-d", "--directory", required=True, help="obj file's directory") 537 parser.add_argument( 538 "-i", 539 "--input_rel_dict", 540 required=True, 541 type=argparse.FileType('r'), 542 help="input file with dict src:memory type(sram2 or ccm or aon etc)", 543 ) 544 parser.add_argument("-o", "--output", required=False, help="Output ld file") 545 parser.add_argument("-s", "--output_sram_data", required=False, help="Output sram data ld file") 546 parser.add_argument("-b", "--output_sram_bss", required=False, help="Output sram bss ld file") 547 parser.add_argument( 548 "-c", "--output_code", required=False, help="Output relocation code header file" 549 ) 550 parser.add_argument( 551 "-R", 552 "--default_ram_region", 553 default='SRAM', 554 help="Name of default RAM memory region for system", 555 ) 556 parser.add_argument("-v", "--verbose", action="count", default=0, help="Verbose Output") 557 args = parser.parse_args() 558 559 560def gen_all_obj_files(searchpath): 561 return list(Path(searchpath).rglob('*.o')) + list(Path(searchpath).rglob('*.obj')) 562 563 564# return the absolute path for the object file. 565def get_obj_filename(all_obj_files, filename): 566 # get the object file name which is almost always pended with .obj 567 obj_filename = filename.split("/")[-1] + ".obj" 568 569 for obj_file in all_obj_files: 570 if obj_file.name == obj_filename and filename.split("/")[-2] in obj_file.parent.name: 571 return str(obj_file) 572 573 574# Extracts all possible components for the input string: 575# <mem_region>[\ :program_header]:<flag_1>[;<flag_2>...]:<file_1>[;<file_2>...][,filter] 576# Returns a 5-tuple with them: (mem_region, program_header, flags, files, filter) 577# If no `program_header` is defined, returns an empty string 578# If no `filter` is defined, returns an empty string 579def parse_input_string(line): 580 # Be careful when splitting by : to avoid breaking absolute paths on Windows 581 mem_region, rest = line.split(':', 1) 582 583 phdr = '' 584 if mem_region.endswith(' '): 585 mem_region = mem_region.rstrip() 586 phdr, rest = rest.split(':', 1) 587 588 flag_list, rest = rest.split(':', 1) 589 flag_list = flag_list.split(';') 590 591 # Split file list by semicolons, in part to support generator expressions 592 file_list, symbol_filter = rest.split(',', 1) 593 file_list = file_list.split(';') 594 595 return mem_region, phdr, flag_list, file_list, symbol_filter 596 597 598# Create a dict with key as memory type and (files, symbol_filter) tuple 599# as a list of values. 600# Also, return another dict with program headers for memory regions 601def create_dict_wrt_mem(): 602 # need to support wild card * 603 rel_dict = dict() 604 phdrs = dict() 605 606 input_rel_dict = args.input_rel_dict.read().splitlines() 607 if not input_rel_dict: 608 sys.exit("Disable CONFIG_CODE_DATA_RELOCATION if no file needs relocation") 609 610 for line in input_rel_dict: 611 if ':' not in line: 612 continue 613 614 mem_region, phdr, flag_list, file_list, symbol_filter = parse_input_string(line) 615 616 # Handle any program header 617 if phdr != '': 618 phdrs[mem_region] = f':{phdr}' 619 620 file_name_list = [] 621 # Use glob matching on each file in the list 622 for file_glob in file_list: 623 glob_results = glob.glob(file_glob) 624 if not glob_results: 625 warnings.warn("File: " + file_glob + " Not found", stacklevel=2) 626 continue 627 elif len(glob_results) > 1: 628 warnings.warn( 629 "Regex in file lists is deprecated, please use file(GLOB) instead", stacklevel=2 630 ) 631 file_name_list.extend(glob_results) 632 if len(file_name_list) == 0: 633 continue 634 if mem_region == '': 635 continue 636 if args.verbose: 637 print("Memory region ", mem_region, " Selected for files:", file_name_list) 638 639 # Apply filter on files 640 file_name_filter_list = [(f, symbol_filter) for f in file_name_list] 641 642 mem_region = "|".join((mem_region, *flag_list)) 643 644 if mem_region in rel_dict: 645 rel_dict[mem_region].extend(file_name_filter_list) 646 else: 647 rel_dict[mem_region] = file_name_filter_list 648 649 return rel_dict, phdrs 650 651 652def main(): 653 global mpu_align 654 mpu_align = {} 655 parse_args() 656 searchpath = args.directory 657 all_obj_files = gen_all_obj_files(searchpath) 658 linker_file = args.output 659 sram_data_linker_file = args.output_sram_data 660 sram_bss_linker_file = args.output_sram_bss 661 rel_dict, phdrs = create_dict_wrt_mem() 662 complete_list_of_sections: dict[MemoryRegion, dict[SectionKind, list[OutputSection]]] = ( 663 defaultdict(lambda: defaultdict(list)) 664 ) 665 666 # Create/or truncate file contents if it already exists 667 # raw = open(linker_file, "w") 668 669 # for each memory_type, create text/rodata/data/bss sections for all obj files 670 for memory_type, files in rel_dict.items(): 671 full_list_of_sections: dict[SectionKind, list[OutputSection]] = defaultdict(list) 672 673 for filename, symbol_filter in files: 674 obj_filename = get_obj_filename(all_obj_files, filename) 675 # the obj file wasn't found. Probably not compiled. 676 if not obj_filename: 677 continue 678 679 file_sections = find_sections(obj_filename, symbol_filter) 680 # Merge sections from file into collection of sections for all files 681 for category, sections in file_sections.items(): 682 full_list_of_sections[category].extend(sections) 683 684 # cleanup and attach the sections to the memory type after cleanup. 685 sections_by_category = assign_to_correct_mem_region(memory_type, full_list_of_sections) 686 for region, section_category_map in sections_by_category.items(): 687 for category, sections in section_category_map.items(): 688 complete_list_of_sections[region][category].extend(sections) 689 690 generate_linker_script( 691 linker_file, sram_data_linker_file, sram_bss_linker_file, complete_list_of_sections, phdrs 692 ) 693 694 code_generation = {"copy_code": '', "zero_code": '', "extern": ''} 695 for mem_type, list_of_sections in sorted(complete_list_of_sections.items()): 696 if "|COPY" in mem_type: 697 mem_type = mem_type.split("|", 1)[0] 698 code_generation = generate_memcpy_code(mem_type, list_of_sections, code_generation) 699 700 dump_header_file(args.output_code, code_generation) 701 702 703if __name__ == '__main__': 704 main() 705