blob: 8d2e88d545c8eec4223786f126e3efba503c883e [file] [log] [blame]
Imre Kis2d137c52019-07-09 18:30:58 +02001#!/usr/bin/env python3
2# Copyright (c) 2019, Arm Limited. All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
6"""
7This module contains a set of classes and a runner that can generate code for the romlib module
8based on the templates in the 'templates' directory.
9"""
10
11import argparse
12import os
13import re
14import subprocess
15import string
16import sys
17
18class IndexFileParser:
19 """
20 Parses the contents of the index file into the items and dependencies variables. It
21 also resolves included files in the index files recursively with circular inclusion detection.
22 """
23
24 def __init__(self):
25 self.items = []
26 self.dependencies = {}
27 self.include_chain = []
28
29 def add_dependency(self, parent, dependency):
30 """ Adds a dependency into the dependencies variable. """
31 if parent in self.dependencies:
32 self.dependencies[parent].append(dependency)
33 else:
34 self.dependencies[parent] = [dependency]
35
36 def get_dependencies(self, parent):
37 """ Gets all the recursive dependencies of a parent file. """
38 parent = os.path.normpath(parent)
39 if parent in self.dependencies:
40 direct_deps = self.dependencies[parent]
41 deps = direct_deps
42 for direct_dep in direct_deps:
43 deps += self.get_dependencies(direct_dep)
44 return deps
45
46 return []
47
48 def parse(self, file_name):
49 """ Opens and parses index file. """
50 file_name = os.path.normpath(file_name)
51
52 if file_name not in self.include_chain:
53 self.include_chain.append(file_name)
54 self.dependencies[file_name] = []
55 else:
56 raise Exception("Circular dependency detected: " + file_name)
57
58 with open(file_name, "r") as index_file:
59 for line in index_file.readlines():
60 line_elements = line.split()
61
62 if line.startswith("#") or not line_elements:
63 # Comment or empty line
64 continue
65
66 if line_elements[0] == "reserved":
67 # Reserved slot in the jump table
68 self.items.append({"type": "reserved"})
69 elif line_elements[0] == "include" and len(line_elements) > 1:
70 # Include other index file
71 included_file = os.path.normpath(line_elements[1])
72 self.add_dependency(file_name, included_file)
73 self.parse(included_file)
74 elif len(line_elements) > 1:
75 # Library function
76 library_name = line_elements[0]
77 function_name = line_elements[1]
78 patch = bool(len(line_elements) > 2 and line_elements[2] == "patch")
79
80 self.items.append({"type": "function", "library_name": library_name,
81 "function_name": function_name, "patch": patch})
82 else:
83 raise Exception("Invalid line: '" + line + "'")
84
85 self.include_chain.pop()
86
87class RomlibApplication:
88 """ Base class of romlib applications. """
89 TEMPLATE_DIR = os.path.dirname(os.path.realpath(__file__)) + "/templates/"
90
91 def __init__(self, prog):
92 self.args = argparse.ArgumentParser(prog=prog, description=self.__doc__)
93 self.config = None
94
95 def parse_arguments(self, argv):
96 """ Parses the arguments that should come from the command line arguments. """
97 self.config = self.args.parse_args(argv)
98
99 def build_template(self, name, mapping=None, remove_comment=False):
100 """
101 Loads a template and builds it with the defined mapping. Template paths are always relative
102 to this script.
103 """
104
105 with open(self.TEMPLATE_DIR + name, "r") as template_file:
106 if remove_comment:
107 # Removing copyright comment to make the generated code more readable when the
108 # template is inserted multiple times into the output.
109 template_lines = template_file.readlines()
110 end_of_comment_line = 0
111 for index, line in enumerate(template_lines):
112 if line.find("*/") != -1:
113 end_of_comment_line = index
114 break
115 template_data = "".join(template_lines[end_of_comment_line + 1:])
116 else:
117 template_data = template_file.read()
118
119 template = string.Template(template_data)
120 return template.substitute(mapping)
121
122class IndexPreprocessor(RomlibApplication):
123 """ Removes empty and comment lines from the index file and resolves includes. """
124
125 def __init__(self, prog):
126 RomlibApplication.__init__(self, prog)
127
128 self.args.add_argument("-o", "--output", help="Output file", metavar="output",
129 default="jmpvar.s")
130 self.args.add_argument("--deps", help="Dependency file")
131 self.args.add_argument("file", help="Input file")
132
133 def main(self):
134 """
135 After parsing the input index file it generates a clean output with all includes resolved.
136 Using --deps option it also outputs the dependencies in makefile format like gcc's with -M.
137 """
138
139 index_file_parser = IndexFileParser()
140 index_file_parser.parse(self.config.file)
141
142 with open(self.config.output, "w") as output_file:
143 for item in index_file_parser.items:
144 if item["type"] == "function":
145 patch = "\tpatch" if item["patch"] else ""
146 output_file.write(
147 item["library_name"] + "\t" + item["function_name"] + patch + "\n")
148 else:
149 output_file.write("reserved\n")
150
151 if self.config.deps:
152 with open(self.config.deps, "w") as deps_file:
153 deps = [self.config.file] + index_file_parser.get_dependencies(self.config.file)
154 deps_file.write(self.config.output + ": " + " \\\n".join(deps) + "\n")
155
156class TableGenerator(RomlibApplication):
157 """ Generates the jump table by parsing the index file. """
158
159 def __init__(self, prog):
160 RomlibApplication.__init__(self, prog)
161
162 self.args.add_argument("-o", "--output", help="Output file", metavar="output",
163 default="jmpvar.s")
164 self.args.add_argument("--bti", help="Branch Target Identification", type=int)
165 self.args.add_argument("file", help="Input file")
166
167 def main(self):
168 """
169 Inserts the jmptbl definition and the jump entries into the output file. Also can insert
170 BTI related code before entries if --bti option set. It can output a dependency file of the
171 included index files. This can be directly included in makefiles.
172 """
173
174 index_file_parser = IndexFileParser()
175 index_file_parser.parse(self.config.file)
176
177 with open(self.config.output, "w") as output_file:
178 output_file.write(self.build_template("jmptbl_header.S"))
179 bti = "_bti" if self.config.bti == 1 else ""
180
181 for item in index_file_parser.items:
182 template_name = "jmptbl_entry_" + item["type"] + bti + ".S"
183 output_file.write(self.build_template(template_name, item, True))
184
Jimmy Brisson1dfa2712024-07-22 12:55:43 -0500185class LinkArgs(RomlibApplication):
186 """ Generates the link arguments to wrap functions. """
187
188 def __init__(self, prog):
189 RomlibApplication.__init__(self, prog)
190 self.args.add_argument("file", help="Input file")
191
192 def main(self):
193 index_file_parser = IndexFileParser()
194 index_file_parser.parse(self.config.file)
195
196 fns = [item["function_name"] for item in index_file_parser.items
197 if not item["patch"] and item["type"] != "reserved"]
198
199 print(" ".join("-Wl,--wrap " + f for f in fns))
200
Imre Kis2d137c52019-07-09 18:30:58 +0200201class WrapperGenerator(RomlibApplication):
202 """
203 Generates a wrapper function for each entry in the index file except for the ones that contain
204 the keyword patch. The generated wrapper file is called <lib>_<fn_name>.s.
205 """
206
207 def __init__(self, prog):
208 RomlibApplication.__init__(self, prog)
209
210 self.args.add_argument("-b", help="Build directory", default=".", metavar="build")
211 self.args.add_argument("--bti", help="Branch Target Identification", type=int)
212 self.args.add_argument("--list", help="Only list assembly files", action="store_true")
213 self.args.add_argument("file", help="Input file")
214
215 def main(self):
216 """
217 Iterates through the items in the parsed index file and builds the template for each entry.
218 """
219
220 index_file_parser = IndexFileParser()
221 index_file_parser.parse(self.config.file)
222
223 bti = "_bti" if self.config.bti == 1 else ""
224 function_offset = 0
225 files = []
226
227 for item_index in range(0, len(index_file_parser.items)):
228 item = index_file_parser.items[item_index]
229
230 if item["type"] == "reserved" or item["patch"]:
231 continue
232
Jimmy Brisson1dfa2712024-07-22 12:55:43 -0500233 if not self.config.list:
234 # The jump instruction is 4 bytes but BTI requires and extra instruction so
235 # this makes it 8 bytes per entry.
236 function_offset = item_index * (8 if self.config.bti else 4)
Imre Kis2d137c52019-07-09 18:30:58 +0200237
Jimmy Brisson1dfa2712024-07-22 12:55:43 -0500238 item["function_offset"] = function_offset
239 files.append(self.build_template("wrapper" + bti + ".S", item))
Imre Kis2d137c52019-07-09 18:30:58 +0200240
241 if self.config.list:
Jimmy Brisson1dfa2712024-07-22 12:55:43 -0500242 print(self.config.b + "/wrappers.s")
243 else:
244 with open(self.config.b + "/wrappers.s", "w") as asm_file:
245 asm_file.write("\n".join(files))
Imre Kis2d137c52019-07-09 18:30:58 +0200246
247class VariableGenerator(RomlibApplication):
248 """ Generates the jump table global variable with the absolute address in ROM. """
249
250 def __init__(self, prog):
251 RomlibApplication.__init__(self, prog)
252
253 self.args.add_argument("-o", "--output", help="Output file", metavar="output",
254 default="jmpvar.s")
255 self.args.add_argument("file", help="Input file")
256
257 def main(self):
258 """
259 Runs nm -a command on the input file and inserts the address of the .text section into the
260 template as the ROM address of the jmp_table.
261 """
262 symbols = subprocess.check_output(["nm", "-a", self.config.file])
263
264 matching_symbol = re.search("([0-9A-Fa-f]+) . \\.text", str(symbols))
265 if not matching_symbol:
266 raise Exception("No '.text' section was found in %s" % self.config.file)
267
268 mapping = {"jmptbl_address": matching_symbol.group(1)}
269
270 with open(self.config.output, "w") as output_file:
271 output_file.write(self.build_template("jmptbl_glob_var.S", mapping))
272
273if __name__ == "__main__":
274 APPS = {"genvar": VariableGenerator, "pre": IndexPreprocessor,
Jimmy Brisson1dfa2712024-07-22 12:55:43 -0500275 "gentbl": TableGenerator, "genwrappers": WrapperGenerator,
276 "link-flags": LinkArgs}
Imre Kis2d137c52019-07-09 18:30:58 +0200277
278 if len(sys.argv) < 2 or sys.argv[1] not in APPS:
279 print("usage: romlib_generator.py [%s] [args]" % "|".join(APPS.keys()), file=sys.stderr)
280 sys.exit(1)
281
282 APP = APPS[sys.argv[1]]("romlib_generator.py " + sys.argv[1])
283 APP.parse_arguments(sys.argv[2:])
284 try:
285 APP.main()
286 sys.exit(0)
287 except FileNotFoundError as file_not_found_error:
288 print(file_not_found_error, file=sys.stderr)
289 except subprocess.CalledProcessError as called_process_error:
290 print(called_process_error.output, file=sys.stderr)
291
292 sys.exit(1)