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