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 a linker script fragment containing the definition of the
31app shared memory section, which is further divided, for each partition
32found, into data and BSS for each partition.
33"""
34
35import sys
36import argparse
37import json
38import os
39import re
40from collections import OrderedDict
41from elftools.elf.elffile import ELFFile
42from elftools.elf.sections import SymbolTableSection
43import elftools.common.exceptions
44
45SZ = 'size'
46SRC = 'sources'
47LIB = 'libraries'
48
49# This script will create sections and linker variables to place the
50# application shared memory partitions.
51# these are later read by the macros defined in app_memdomain.h for
52# initialization purpose when USERSPACE is enabled.
53data_template = """
54		/* Auto generated code do not modify */
55		SMEM_PARTITION_ALIGN(z_data_smem_{0}_bss_end - z_data_smem_{0}_part_start);
56		z_data_smem_{0}_part_start = .;
57		KEEP(*(data_smem_{0}_data*))
58"""
59
60library_data_template = """
61		*{0}:*(.data .data.* .sdata .sdata.*)
62"""
63
64bss_template = """
65		z_data_smem_{0}_bss_start = .;
66		KEEP(*(data_smem_{0}_bss*))
67"""
68
69library_bss_template = """
70		*{0}:*(.bss .bss.* .sbss .sbss.* COMMON COMMON.*)
71"""
72
73footer_template = """
74		z_data_smem_{0}_bss_end = .;
75		SMEM_PARTITION_ALIGN(z_data_smem_{0}_bss_end - z_data_smem_{0}_part_start);
76		z_data_smem_{0}_part_end = .;
77"""
78
79linker_start_seq = """
80	SECTION_PROLOGUE(_APP_SMEM{1}_SECTION_NAME,,)
81	{{
82		APP_SHARED_ALIGN;
83		_app_smem{0}_start = .;
84"""
85
86linker_end_seq = """
87		APP_SHARED_ALIGN;
88		_app_smem{0}_end = .;
89	}} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
90"""
91
92empty_app_smem = """
93	SECTION_PROLOGUE(_APP_SMEM{1}_SECTION_NAME,,)
94	{{
95#ifdef EMPTY_APP_SHARED_ALIGN
96		EMPTY_APP_SHARED_ALIGN;
97#endif
98		_app_smem{0}_start = .;
99		_app_smem{0}_end = .;
100	}} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
101"""
102
103size_cal_string = """
104	z_data_smem_{0}_part_size = z_data_smem_{0}_part_end - z_data_smem_{0}_part_start;
105	z_data_smem_{0}_bss_size = z_data_smem_{0}_bss_end - z_data_smem_{0}_bss_start;
106"""
107
108section_regex = re.compile(r'data_smem_([A-Za-z0-9_]*)_(data|bss)*')
109
110elf_part_size_regex = re.compile(r'z_data_smem_(.*)_part_size')
111
112def find_obj_file_partitions(filename, partitions):
113    with open(filename, 'rb') as f:
114        try:
115            full_lib = ELFFile(f)
116        except elftools.common.exceptions.ELFError as e:
117            exit(f"Error: {filename}: {e}")
118
119        if not full_lib:
120            sys.exit("Error parsing file: " + filename)
121
122        sections = [x for x in full_lib.iter_sections()]
123        for section in sections:
124            m = section_regex.match(section.name)
125            if not m:
126                continue
127
128            partition_name = m.groups()[0]
129            if partition_name not in partitions:
130                partitions[partition_name] = {SZ: section.header.sh_size}
131
132                if args.verbose:
133                    partitions[partition_name][SRC] = filename
134
135            else:
136                partitions[partition_name][SZ] += section.header.sh_size
137
138
139    return partitions
140
141
142def parse_obj_files(partitions):
143    # Iterate over all object files to find partitions
144    for dirpath, _, files in os.walk(args.directory):
145        for filename in files:
146            if re.match(r".*\.obj$", filename):
147                fullname = os.path.join(dirpath, filename)
148                fsize = os.path.getsize(fullname)
149                if fsize != 0:
150                    find_obj_file_partitions(fullname, partitions)
151
152
153def parse_compile_command_file(partitions):
154    # Iterate over all entries to find object files.
155    # Thereafter process each object file to find partitions
156    object_pattern = re.compile(r'-o\s+(\S*)')
157    with open(args.compile_commands_file, 'rb') as f:
158        commands = json.load(f)
159        for command in commands:
160            build_dir = command.get('directory')
161            compile_command = command.get('command')
162            compile_arg = object_pattern.search(compile_command)
163            obj_file = None if compile_arg is None else compile_arg.group(1)
164            if obj_file:
165                fullname = os.path.join(build_dir, obj_file)
166                # Because of issue #40635, then not all objects referenced by
167                # the compile_commands.json file may be available, therefore
168                # only include existing files.
169                if os.path.exists(fullname):
170                    find_obj_file_partitions(fullname, partitions)
171
172
173def parse_elf_file(partitions):
174    with open(args.elf, 'rb') as f:
175        try:
176            elffile = ELFFile(f)
177        except elftools.common.exceptions.ELFError as e:
178            exit(f"Error: {args.elf}: {e}")
179
180        symbol_tbls = [s for s in elffile.iter_sections()
181                       if isinstance(s, SymbolTableSection)]
182
183        for section in symbol_tbls:
184            for symbol in section.iter_symbols():
185                if symbol['st_shndx'] != "SHN_ABS":
186                    continue
187
188                x = elf_part_size_regex.match(symbol.name)
189                if not x:
190                    continue
191
192                partition_name = x.groups()[0]
193                size = symbol['st_value']
194                if partition_name not in partitions:
195                    partitions[partition_name] = {SZ: size}
196
197                    if args.verbose:
198                        partitions[partition_name][SRC] = args.elf
199
200                else:
201                    partitions[partition_name][SZ] += size
202
203
204def generate_final_linker(linker_file, partitions, lnkr_sect=""):
205    string = ""
206
207    if len(partitions) > 0:
208        string = linker_start_seq.format(lnkr_sect, lnkr_sect.upper())
209        size_string = ''
210        for partition, item in partitions.items():
211            string += data_template.format(partition)
212            if LIB in item:
213                for lib in item[LIB]:
214                    string += library_data_template.format(lib)
215            string += bss_template.format(partition, lnkr_sect)
216            if LIB in item:
217                for lib in item[LIB]:
218                    string += library_bss_template.format(lib)
219            string += footer_template.format(partition)
220            size_string += size_cal_string.format(partition)
221
222        string += linker_end_seq.format(lnkr_sect)
223        string += size_string
224    else:
225        string = empty_app_smem.format(lnkr_sect, lnkr_sect.upper())
226
227    with open(linker_file, "w") as fw:
228        fw.write(string)
229
230
231def parse_args():
232    global args
233    parser = argparse.ArgumentParser(
234        description=__doc__,
235        formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
236    parser.add_argument("-d", "--directory", required=False, default=None,
237                        help="Root build directory")
238    parser.add_argument("-e", "--elf", required=False, default=None,
239                        help="ELF file")
240    parser.add_argument("-f", "--compile-commands-file", required=False,
241                        default=None, help="CMake compile commands file")
242    parser.add_argument("-o", "--output", required=False,
243                        help="Output ld file")
244    parser.add_argument("-v", "--verbose", action="count", default=0,
245                        help="Verbose Output")
246    parser.add_argument("-l", "--library", nargs=2, action="append", default=[],
247                        metavar=("LIBRARY", "PARTITION"),
248                        help="Include globals for a particular library or object filename into a designated partition")
249    parser.add_argument("--pinoutput", required=False,
250                        help="Output ld file for pinned sections")
251    parser.add_argument("--pinpartitions", action="store", required=False, default="",
252                        help="Comma separated names of partitions to be pinned in physical memory")
253
254    args = parser.parse_args()
255
256
257def main():
258    parse_args()
259    partitions = {}
260
261    if args.directory is not None:
262        parse_obj_files(partitions)
263    if args.compile_commands_file is not None:
264        parse_compile_command_file(partitions)
265    elif args.elf is not None:
266        parse_elf_file(partitions)
267    else:
268        return
269
270    for lib, ptn in args.library:
271        if ptn not in partitions:
272            partitions[ptn] = {}
273
274        if LIB not in partitions[ptn]:
275            partitions[ptn][LIB] = [lib]
276        else:
277            partitions[ptn][LIB].append(lib)
278
279    if args.pinoutput:
280        pin_part_names = args.pinpartitions.split(',')
281
282        generic_partitions = {key: value for key, value in partitions.items()
283                              if key not in pin_part_names}
284        pinned_partitions = {key: value for key, value in partitions.items()
285                             if key in pin_part_names}
286    else:
287        generic_partitions = partitions
288
289    # Sample partitions.items() list before sorting:
290    #   [ ('part1', {'size': 64}), ('part3', {'size': 64}, ...
291    #     ('part0', {'size': 334}) ]
292    decreasing_tuples = sorted(generic_partitions.items(),
293                           key=lambda x: (x[1][SZ], x[0]), reverse=True)
294
295    partsorted = OrderedDict(decreasing_tuples)
296
297    generate_final_linker(args.output, partsorted)
298    if args.verbose:
299        print("Partitions retrieved:")
300        for key in partsorted:
301            print("    {0}: size {1}: {2}".format(key,
302                                                  partsorted[key][SZ],
303                                                  partsorted[key][SRC]))
304
305    if args.pinoutput:
306        decreasing_tuples = sorted(pinned_partitions.items(),
307                                   key=lambda x: (x[1][SZ], x[0]), reverse=True)
308
309        partsorted = OrderedDict(decreasing_tuples)
310
311        generate_final_linker(args.pinoutput, partsorted, lnkr_sect="_pinned")
312        if args.verbose:
313            print("Pinned partitions retrieved:")
314            for key in partsorted:
315                print("    {0}: size {1}: {2}".format(key,
316                                                    partsorted[key][SZ],
317                                                    partsorted[key][SRC]))
318
319
320if __name__ == '__main__':
321    main()
322