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