1#!/usr/bin/env python3
2#
3# Copyright (c) 2021 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""
8Process ELF file to generate placeholders for kobject
9hash table and lookup functions produced by gperf,
10since their sizes depend on how many kobjects have
11been declared. The output header files will be used
12during linking for intermediate output binaries so
13that the addresses of these kobjects would remain
14the same during later stages of linking.
15"""
16
17import sys
18import argparse
19import os
20from distutils.version import LooseVersion
21
22import elftools
23from elftools.elf.elffile import ELFFile
24
25
26if LooseVersion(elftools.__version__) < LooseVersion('0.24'):
27    sys.exit("pyelftools is out of date, need version 0.24 or later")
28
29
30def write_define(out_fp, prefix, name, value):
31    """Write the #define to output file"""
32    define_name = f"KOBJECT_{prefix}_{name}"
33    out_fp.write(f"#ifndef {define_name}\n")
34    out_fp.write(f"#define {define_name} {value}\n")
35    out_fp.write("#endif\n\n")
36
37
38def output_simple_header(one_sect):
39    """Write the header for kobject section"""
40
41    out_fn = os.path.join(args.outdir,
42                          f"linker-kobject-prebuilt-{one_sect['name']}.h")
43    out_fp = open(out_fn, "w")
44
45    if one_sect['exists']:
46        align = one_sect['align']
47        size = one_sect['size']
48        prefix = one_sect['define_prefix']
49
50        write_define(out_fp, prefix, 'ALIGN', align)
51        write_define(out_fp, prefix, 'SZ', size)
52
53    out_fp.close()
54
55
56def generate_linker_headers(obj):
57    """Generate linker header files to be included by the linker script"""
58
59    # Sections we are interested in
60    sections = {
61        ".data": {
62            "name": "data",
63            "define_prefix": "DATA",
64            "exists": False,
65            "multiplier": int(args.datapct) + 100,
66            },
67        ".rodata": {
68            "name": "rodata",
69            "define_prefix": "RODATA",
70            "exists": False,
71            "extra_bytes": args.rodata,
72            },
73        ".priv_stacks.noinit": {
74            "name": "priv-stacks",
75            "define_prefix": "PRIV_STACKS",
76            "exists": False,
77            },
78    }
79
80    for one_sect in obj.iter_sections():
81        # REALLY NEED to match exact type as all other sections
82        # (symbol, debug, etc.) are descendants where
83        # isinstance() would match.
84        if type(one_sect) is not elftools.elf.sections.Section: # pylint: disable=unidiomatic-typecheck
85            continue
86
87        name = one_sect.name
88        if name in sections.keys():
89            # Need section alignment and size
90            sections[name]['align'] = one_sect['sh_addralign']
91            sections[name]['size'] = one_sect['sh_size']
92            sections[name]['exists'] = True
93
94            if "multiplier" in sections[name]:
95                sections[name]['size'] *= sections[name]['multiplier'] / 100
96                sections[name]['size'] = int(sections[name]['size'])
97
98            if "extra_bytes" in sections[name]:
99                sections[name]['size'] += int(sections[name]['extra_bytes'])
100
101    for one_sect in sections:
102        output_simple_header(sections[one_sect])
103
104
105def parse_args():
106    """Parse command line arguments"""
107    global args
108
109    parser = argparse.ArgumentParser(
110        description=__doc__,
111        formatter_class=argparse.RawDescriptionHelpFormatter)
112
113    parser.add_argument("--object", required=True,
114                        help="Points to kobject_prebuilt_hash.c.obj")
115    parser.add_argument("--outdir", required=True,
116                        help="Output directory (<build_dir>/include/generated)")
117    parser.add_argument("--datapct", required=True,
118                        help="Multipler to the size of reserved space for DATA region")
119    parser.add_argument("--rodata", required=True,
120                        help="Extra bytes to reserve for RODATA region")
121    parser.add_argument("-v", "--verbose", action="store_true",
122                        help="Verbose messages")
123    args = parser.parse_args()
124    if "VERBOSE" in os.environ:
125        args.verbose = 1
126
127
128def main():
129    """Main program"""
130    parse_args()
131
132    with open(args.object, "rb") as obj_fp:
133        obj = ELFFile(obj_fp)
134
135        generate_linker_headers(obj)
136
137
138if __name__ == "__main__":
139    main()
140