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