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 either a linker script fragment or linker-script generator
31fragments containing the definition of the app shared memory section, which
32is further divided, for each partition found, into data and BSS for each
33partition.
34"""
35
36#The linker script fragments looks something like this:
37# SECTION_PROLOGUE(_APP_SMEM_SECTION_NAME,,)
38# 	{
39# 		APP_SHARED_ALIGN;
40# 		_app_smem_start = .;
41
42# 		/* Auto generated code do not modify */
43# 		SMEM_PARTITION_ALIGN(z_data_smem_ztest_mem_partition_bss_end - z_data_smem_ztest_mem_partition_part_start);
44# 		z_data_smem_ztest_mem_partition_part_start = .;
45# 		KEEP(*(data_smem_ztest_mem_partition_data*))
46
47# 		z_data_smem_ztest_mem_partition_bss_start = .;
48# 		KEEP(*(data_smem_ztest_mem_partition_bss*))
49
50# 		z_data_smem_ztest_mem_partition_bss_end = .;
51# 		SMEM_PARTITION_ALIGN(z_data_smem_ztest_mem_partition_bss_end - z_data_smem_ztest_mem_partition_part_start);
52# 		z_data_smem_ztest_mem_partition_part_end = .;
53
54# 		/* Auto generated code do not modify */
55# 		SMEM_PARTITION_ALIGN(z_data_smem_z_libc_partition_bss_end - z_data_smem_z_libc_partition_part_start);
56# 		z_data_smem_z_libc_partition_part_start = .;
57# 		KEEP(*(data_smem_z_libc_partition_data*))
58
59# 		z_data_smem_z_libc_partition_bss_start = .;
60# 		KEEP(*(data_smem_z_libc_partition_bss*))
61
62# 		z_data_smem_z_libc_partition_bss_end = .;
63# 		SMEM_PARTITION_ALIGN(z_data_smem_z_libc_partition_bss_end - z_data_smem_z_libc_partition_part_start);
64# 		z_data_smem_z_libc_partition_part_end = .;
65
66# 		APP_SHARED_ALIGN;
67# 		_app_smem_end = .;
68# 	} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
69
70# 	z_data_smem_ztest_mem_partition_part_size = z_data_smem_ztest_mem_partition_part_end - z_data_smem_ztest_mem_partition_part_start;
71# 	z_data_smem_ztest_mem_partition_bss_size = z_data_smem_ztest_mem_partition_bss_end - z_data_smem_ztest_mem_partition_bss_start;
72
73# 	z_data_smem_z_libc_partition_part_size = z_data_smem_z_libc_partition_part_end - z_data_smem_z_libc_partition_part_start;
74# 	z_data_smem_z_libc_partition_bss_size = z_data_smem_z_libc_partition_bss_end - z_data_smem_z_libc_partition_bss_start;
75
76# the linker script generator fragments looks something like this:
77# There is a zephyr_linker_group() called APP_SMEM_GROUP for these sections
78# defined in linker.cmake
79# _APP_SMEM_SECTION_NAME is #defined in sections.h to "app_smem". Ideally we should have that here too.
80#
81# zephyr_linker_section(NAME _APP_SMEM_SECTION_NAME GROUP APP_SMEM_GROUP NOINPUT ALIGN_WITH_INPUT)
82#   zephyr_linker_section_configure(
83#     SECTION
84#     _APP_SMEM_SECTION_NAME
85#     SYMBOLS
86#     _app_smem_start
87#     )
88#   zephyr_linker_section_configure(
89#     SECTION
90#     _APP_SMEM_SECTION_NAME
91#     INPUT
92#     "data_smem_ztest_mem_partition_data*"
93#     KEEP
94#     SYMBOLS
95#     z_data_smem_ztest_mem_partition_part_start
96#     )
97#   zephyr_linker_section_configure(
98#     SECTION
99#     _APP_SMEM_SECTION_NAME
100#     INPUT
101#     "data_smem_ztest_mem_partition_bss*"
102#     KEEP
103#     SYMBOLS
104#     z_data_smem_ztest_mem_partition_bss_start
105#     z_data_smem_ztest_mem_partition_bss_end
106#     )
107#   zephyr_linker_section_configure(
108#     SECTION
109#     _APP_SMEM_SECTION_NAME
110#     SYMBOLS
111#     z_data_smem_ztest_mem_partition_part_end
112#     )
113
114#   zephyr_linker_section_configure(
115#     SECTION
116#     _APP_SMEM_SECTION_NAME
117#     INPUT
118#     "data_smem_z_libc_partition_data*"
119#     KEEP
120#     SYMBOLS
121#     z_data_smem_z_libc_partition_part_start
122#     )
123#   zephyr_linker_section_configure(
124#     SECTION
125#     _APP_SMEM_SECTION_NAME
126#     INPUT
127#     "data_smem_z_libc_partition_bss*"
128#     KEEP
129#     SYMBOLS
130#     z_data_smem_z_libc_partition_bss_start
131#     z_data_smem_z_libc_partition_bss_end
132#     )
133#   zephyr_linker_section_configure(
134#     SECTION
135#     _APP_SMEM_SECTION_NAME
136#     SYMBOLS
137#     z_data_smem_z_libc_partition_part_end
138#     )
139#   zephyr_linker_section_configure(
140#     SECTION
141#     _APP_SMEM_SECTION_NAME
142#     SYMBOLS
143#     _app_smem_end
144#     )
145#   zephyr_linker_symbol(
146#     SYMBOL
147#     z_data_smem_ztest_mem_partition_part_size
148#     EXPR
149#     "(@z_data_smem_ztest_mem_partition_part_end@ - @z_data_smem_ztest_mem_partition_part_start@)"
150#     )
151#   zephyr_linker_symbol(
152#     SYMBOL
153#     z_data_smem_ztest_mem_partition_bss_size
154#     EXPR
155#     "(@z_data_smem_ztest_mem_partition_bss_end@ - @z_data_smem_ztest_mem_partition_bss_start@)"
156#     )
157
158#   zephyr_linker_symbol(
159#     SYMBOL
160#     z_data_smem_z_libc_partition_part_size
161#     EXPR
162#     "(@z_data_smem_z_libc_partition_part_end@ - @z_data_smem_z_libc_partition_part_start@)"
163#     )
164#   zephyr_linker_symbol(
165#     SYMBOL
166#     z_data_smem_z_libc_partition_bss_size
167#     EXPR
168#     "(@z_data_smem_z_libc_partition_bss_end@ - @z_data_smem_z_libc_partition_bss_start@)"
169#     )
170
171
172import sys
173import argparse
174import json
175import os
176import re
177from collections import OrderedDict
178from elftools.elf.elffile import ELFFile
179from elftools.elf.sections import SymbolTableSection
180import elftools.common.exceptions
181
182SZ = 'size'
183SRC = 'sources'
184LIB = 'libraries'
185
186# This script will create sections and linker variables to place the
187# application shared memory partitions.
188# these are later read by the macros defined in app_memdomain.h for
189# initialization purpose when USERSPACE is enabled.
190
191class LDScriptTemlate:
192    data_template = """
193            /* Auto generated code do not modify */
194            SMEM_PARTITION_ALIGN(z_data_smem_{partition}_bss_end - z_data_smem_{partition}_part_start);
195            z_data_smem_{partition}_part_start = .;
196            KEEP(*(data_smem_{partition}_data*))
197    """
198
199    library_data_template = """
200            \"*{lib}:*\"(.data .data.* .sdata .sdata.*)
201    """
202
203    bss_template = """
204            z_data_smem_{partition}_bss_start = .;
205            KEEP(*(data_smem_{partition}_bss*))
206    """
207
208    library_bss_template = """
209            \"*{lib}:*\"(.bss .bss.* .sbss .sbss.* COMMON COMMON.*)
210    """
211
212    footer_template = """
213            z_data_smem_{partition}_bss_end = .;
214            SMEM_PARTITION_ALIGN(z_data_smem_{partition}_bss_end - z_data_smem_{partition}_part_start);
215            z_data_smem_{partition}_part_end = .;
216    """
217
218    linker_start_seq = """
219        SECTION_PROLOGUE(_APP_SMEM{SECTION}_SECTION_NAME,,)
220        {{
221            APP_SHARED_ALIGN;
222            _app_smem{section}_start = .;
223    """
224
225    linker_end_seq = """
226            APP_SHARED_ALIGN;
227            _app_smem{section}_end = .;
228        }} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
229    """
230
231    empty_app_smem = """
232        SECTION_PROLOGUE(_APP_SMEM{SECTION}_SECTION_NAME,,)
233        {{
234    #ifdef EMPTY_APP_SHARED_ALIGN
235            EMPTY_APP_SHARED_ALIGN;
236    #endif
237            _app_smem{section}_start = .;
238            _app_smem{section}_end = .;
239        }} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
240    """
241
242    size_cal_string = """
243        z_data_smem_{partition}_part_size = z_data_smem_{partition}_part_end - z_data_smem_{partition}_part_start;
244        z_data_smem_{partition}_bss_size = z_data_smem_{partition}_bss_end - z_data_smem_{partition}_bss_start;
245    """
246
247def cmake_list_append(list, props):
248    tokens = [fr"{key}\;{value}" for key, value in props.items()]
249    stuff = r"\;".join(tokens)
250    return """list(APPEND """+ list + """ \"{{""" + stuff + """}}\")\n"""
251
252def zephyr_linker_section(name, group, align = None, noinput = True, align_with_input = True, hidden = False, noinit = False):
253    props = {"""NAME""": name, """GROUP""": group}
254    if align :
255        props['ALIGN'] = align
256    #The bool flags are always there:
257    props['NOIPUT'] = noinput
258    props['ALIGN_WITH_INPUT'] = align_with_input
259    props['HIDDEN'] = hidden
260    props['NOINIT'] = noinit
261    return cmake_list_append("SECTIONS", props)
262
263def zephyr_linker_section_configure(section, input = None, symbols = None, align = None, any = False, first = False, keep = False):
264    props = {"SECTION": section}
265    if input:
266        props["INPUT"] = input
267    #ANY\;FALSE\;FIRST\;FALSE\;KEEP\;FALSE\
268    props['ANY'] = any
269    props['FIRST'] = first
270    props['KEEP'] = keep
271    if symbols:
272        props["SYMBOLS"] = symbols
273    if align:
274        props["ALIGN"] = align
275    return cmake_list_append("SECTION_SETTINGS", props)
276
277def zephyr_linker_symbol(symbol, expr) :
278    return cmake_list_append("SYMBOLS", {'SYMBOL': symbol, 'EXPR':expr})
279
280class CmakeTemplate:
281    section_name = "@_APP_SMEM{SECTION}_SECTION_NAME@"
282    data_template = (
283        zephyr_linker_section_configure(section=section_name, align="@SMEM_PARTITION_ALIGN_BYTES@")+
284        zephyr_linker_section_configure(section=section_name, input="data_smem_{partition}_data*", symbols="z_data_smem_{partition}_part_start", keep=True)
285    )
286
287    library_data_template = zephyr_linker_section_configure(section=section_name, input="*{lib}:*(.data .data.* .sdata .sdata.*)")
288
289    bss_template = (
290        zephyr_linker_section_configure(section=section_name, input="data_smem_{partition}_bss*", symbols="z_data_smem_{partition}_bss_start", keep=True)
291    )
292
293    library_bss_template = zephyr_linker_section_configure(section=section_name, input="*{lib}:*(.bss .bss.* .sbss .sbss.* COMMON COMMON.*)")
294
295    footer_template = (
296        zephyr_linker_section_configure(section=section_name, symbols="z_data_smem_{partition}_bss_end", keep=True) +
297        zephyr_linker_section_configure(section=section_name, align="@SMEM_PARTITION_ALIGN_BYTES@") +
298        zephyr_linker_section_configure(section=section_name, symbols="z_data_smem_{partition}_part_end", keep=True)
299    )
300
301    linker_start_seq = (
302        zephyr_linker_section(name=section_name, group="APP_SMEM_GROUP", noinput=True, align_with_input=True) +
303        zephyr_linker_section_configure(section=section_name, align="@APP_SHARED_ALIGN_BYTES@", symbols="_app_smem{section}_start"))
304
305    linker_end_seq = (
306        zephyr_linker_section_configure(section=section_name, align="@APP_SHARED_ALIGN_BYTES@") +
307        zephyr_linker_section_configure(section=section_name, symbols="_app_smem{section}_end") )
308
309    empty_app_smem = (
310        zephyr_linker_section(name=section_name, group="APP_SMEM_GROUP", align="@APP_SHARED_ALIGN_BYTES@", noinput=True, align_with_input=True) +
311        zephyr_linker_section_configure(section = section_name, symbols="_app_smem{section}_start") +
312        zephyr_linker_section_configure(section = section_name, symbols="_app_smem{section}_end") )
313
314    size_cal_string = (
315        zephyr_linker_symbol(symbol='z_data_smem_{partition}_part_size', expr='@z_data_smem_{partition}_part_end@ - @z_data_smem_{partition}_part_start@') +
316        zephyr_linker_symbol(symbol='z_data_smem_{partition}_bss_size', expr='@z_data_smem_{partition}_bss_end@ - @z_data_smem_{partition}_bss_start@'))
317
318section_regex = re.compile(r'data_smem_([A-Za-z0-9_]*)_(data|bss)*')
319
320elf_part_size_regex = re.compile(r'z_data_smem_(.*)_part_size')
321
322def find_obj_file_partitions(filename, partitions):
323    with open(filename, 'rb') as f:
324        try:
325            full_lib = ELFFile(f)
326        except elftools.common.exceptions.ELFError as e:
327            exit(f"Error: {filename}: {e}")
328
329        if not full_lib:
330            sys.exit("Error parsing file: " + filename)
331
332        sections = [x for x in full_lib.iter_sections()]
333        for section in sections:
334            m = section_regex.match(section.name)
335            if not m:
336                continue
337
338            partition_name = m.groups()[0]
339            if partition_name not in partitions:
340                partitions[partition_name] = {SZ: section.header.sh_size}
341
342                if args.verbose:
343                    partitions[partition_name][SRC] = filename
344
345            else:
346                partitions[partition_name][SZ] += section.header.sh_size
347
348
349    return partitions
350
351
352def parse_obj_files(partitions):
353    # Iterate over all object files to find partitions
354    for dirpath, _, files in os.walk(args.directory):
355        for filename in files:
356            if re.match(r".*\.obj$", filename):
357                fullname = os.path.join(dirpath, filename)
358                fsize = os.path.getsize(fullname)
359                if fsize != 0:
360                    find_obj_file_partitions(fullname, partitions)
361
362
363def parse_compile_command_file(partitions):
364    # Iterate over all entries to find object files.
365    # Thereafter process each object file to find partitions
366    object_pattern = re.compile(r'-o\s+(\S*)')
367    with open(args.compile_commands_file, 'rb') as f:
368        commands = json.load(f)
369        for command in commands:
370            build_dir = command.get('directory')
371            compile_command = command.get('command')
372            compile_arg = object_pattern.search(compile_command)
373            obj_file = None if compile_arg is None else compile_arg.group(1)
374            if obj_file:
375                fullname = os.path.join(build_dir, obj_file)
376                # Because of issue #40635, then not all objects referenced by
377                # the compile_commands.json file may be available, therefore
378                # only include existing files.
379                if os.path.exists(fullname):
380                    find_obj_file_partitions(fullname, partitions)
381
382
383def parse_elf_file(partitions):
384    with open(args.elf, 'rb') as f:
385        try:
386            elffile = ELFFile(f)
387        except elftools.common.exceptions.ELFError as e:
388            exit(f"Error: {args.elf}: {e}")
389
390        symbol_tbls = [s for s in elffile.iter_sections()
391                       if isinstance(s, SymbolTableSection)]
392
393        for section in symbol_tbls:
394            for symbol in section.iter_symbols():
395                if symbol['st_shndx'] != "SHN_ABS":
396                    continue
397
398                x = elf_part_size_regex.match(symbol.name)
399                if not x:
400                    continue
401
402                partition_name = x.groups()[0]
403                size = symbol['st_value']
404                if partition_name not in partitions:
405                    partitions[partition_name] = {SZ: size}
406
407                    if args.verbose:
408                        partitions[partition_name][SRC] = args.elf
409
410                else:
411                    partitions[partition_name][SZ] += size
412
413def generate_final(linker_file, partitions, lnkr_sect=""):
414    if linker_file.endswith(".ld"):
415        template = LDScriptTemlate
416    else:
417        template = CmakeTemplate
418    generate_final_linker(template, linker_file, partitions, lnkr_sect)
419
420def generate_final_linker(template, linker_file, partitions, lnkr_sect):
421    string = ""
422    props = { 'section' : lnkr_sect, 'SECTION' : lnkr_sect.upper() }
423    if len(partitions) > 0:
424        string = template.linker_start_seq.format(**props)
425        size_string = ''
426        for partition, item in partitions.items():
427            part_props = props
428            part_props["partition"] = partition
429            string += template.data_template.format(**part_props)
430            if LIB in item:
431                for lib in item[LIB]:
432                    lib_props = part_props
433                    lib_props["lib"] = lib
434                    string += template.library_data_template.format(**lib_props)
435            string += template.bss_template.format(**part_props)
436            if LIB in item:
437                for lib in item[LIB]:
438                    lib_props = part_props
439                    lib_props["lib"] = lib
440                    string += template.library_bss_template.format(**lib_props)
441            string += template.footer_template.format(**part_props)
442            size_string += template.size_cal_string.format(**part_props)
443
444        string += template.linker_end_seq.format(**props)
445        string += size_string
446    else:
447        string = template.empty_app_smem.format(**props) #lnkr_sect, lnkr_sect.upper())
448
449    with open(linker_file, "w") as fw:
450        fw.write(string)
451
452def parse_args():
453    global args
454    parser = argparse.ArgumentParser(
455        description=__doc__,
456        formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
457    parser.add_argument("-d", "--directory", required=False, default=None,
458                        help="Root build directory")
459    parser.add_argument("-e", "--elf", required=False, default=None,
460                        help="ELF file")
461    parser.add_argument("-f", "--compile-commands-file", required=False,
462                        default=None, help="CMake compile commands file")
463    parser.add_argument("-o", "--output", required=False,
464                        help="Output ld file")
465    parser.add_argument("-v", "--verbose", action="count", default=0,
466                        help="Verbose Output")
467    parser.add_argument("-l", "--library", nargs=2, action="append", default=[],
468                        metavar=("LIBRARY", "PARTITION"),
469                        help="Include globals for a particular library or object filename into a designated partition")
470    parser.add_argument("--pinoutput", required=False,
471                        help="Output ld file for pinned sections")
472    parser.add_argument("--pinpartitions", action="store", required=False, default="",
473                        help="Comma separated names of partitions to be pinned in physical memory")
474
475    args, _ = parser.parse_known_args()
476
477
478def main():
479    parse_args()
480    partitions = {}
481
482    if args.directory is not None:
483        parse_obj_files(partitions)
484    if args.compile_commands_file is not None:
485        parse_compile_command_file(partitions)
486    elif args.elf is not None:
487        parse_elf_file(partitions)
488    else:
489        return
490
491    for lib, ptn in args.library:
492        if ptn not in partitions:
493            partitions[ptn] = {}
494
495        if LIB not in partitions[ptn]:
496            partitions[ptn][LIB] = [lib]
497        else:
498            partitions[ptn][LIB].append(lib)
499
500    if args.pinoutput:
501        pin_part_names = args.pinpartitions.split(',')
502
503        generic_partitions = {key: value for key, value in partitions.items()
504                              if key not in pin_part_names}
505        pinned_partitions = {key: value for key, value in partitions.items()
506                             if key in pin_part_names}
507    else:
508        generic_partitions = partitions
509
510    # Sample partitions.items() list before sorting:
511    #   [ ('part1', {'size': 64}), ('part3', {'size': 64}, ...
512    #     ('part0', {'size': 334}) ]
513    decreasing_tuples = sorted(generic_partitions.items(),
514                           key=lambda x: (x[1][SZ], x[0]), reverse=True)
515
516    partsorted = OrderedDict(decreasing_tuples)
517
518    generate_final(args.output, partsorted)
519
520    if args.verbose:
521        print("Partitions retrieved:")
522        for key in partsorted:
523            print("    {0}: size {1}: {2}".format(key,
524                                                  partsorted[key][SZ],
525                                                  partsorted[key][SRC]))
526
527    if args.pinoutput:
528        decreasing_tuples = sorted(pinned_partitions.items(),
529                                   key=lambda x: (x[1][SZ], x[0]), reverse=True)
530
531        partsorted = OrderedDict(decreasing_tuples)
532
533        generate_final(args.pinoutput, partsorted, lnkr_sect="_pinned")
534        if args.verbose:
535            print("Pinned partitions retrieved:")
536            for key in partsorted:
537                print("    {0}: size {1}: {2}".format(key,
538                                                    partsorted[key][SZ],
539                                                    partsorted[key][SRC]))
540
541
542if __name__ == '__main__':
543    main()
544