diff options
Diffstat (limited to 'lib/romlib/romlib_generator.py')
-rwxr-xr-x | lib/romlib/romlib_generator.py | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/lib/romlib/romlib_generator.py b/lib/romlib/romlib_generator.py new file mode 100755 index 0000000..0682dd4 --- /dev/null +++ b/lib/romlib/romlib_generator.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019, Arm Limited. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +This module contains a set of classes and a runner that can generate code for the romlib module +based on the templates in the 'templates' directory. +""" + +import argparse +import os +import re +import subprocess +import string +import sys + +class IndexFileParser: + """ + Parses the contents of the index file into the items and dependencies variables. It + also resolves included files in the index files recursively with circular inclusion detection. + """ + + def __init__(self): + self.items = [] + self.dependencies = {} + self.include_chain = [] + + def add_dependency(self, parent, dependency): + """ Adds a dependency into the dependencies variable. """ + if parent in self.dependencies: + self.dependencies[parent].append(dependency) + else: + self.dependencies[parent] = [dependency] + + def get_dependencies(self, parent): + """ Gets all the recursive dependencies of a parent file. """ + parent = os.path.normpath(parent) + if parent in self.dependencies: + direct_deps = self.dependencies[parent] + deps = direct_deps + for direct_dep in direct_deps: + deps += self.get_dependencies(direct_dep) + return deps + + return [] + + def parse(self, file_name): + """ Opens and parses index file. """ + file_name = os.path.normpath(file_name) + + if file_name not in self.include_chain: + self.include_chain.append(file_name) + self.dependencies[file_name] = [] + else: + raise Exception("Circular dependency detected: " + file_name) + + with open(file_name, "r") as index_file: + for line in index_file.readlines(): + line_elements = line.split() + + if line.startswith("#") or not line_elements: + # Comment or empty line + continue + + if line_elements[0] == "reserved": + # Reserved slot in the jump table + self.items.append({"type": "reserved"}) + elif line_elements[0] == "include" and len(line_elements) > 1: + # Include other index file + included_file = os.path.normpath(line_elements[1]) + self.add_dependency(file_name, included_file) + self.parse(included_file) + elif len(line_elements) > 1: + # Library function + library_name = line_elements[0] + function_name = line_elements[1] + patch = bool(len(line_elements) > 2 and line_elements[2] == "patch") + + self.items.append({"type": "function", "library_name": library_name, + "function_name": function_name, "patch": patch}) + else: + raise Exception("Invalid line: '" + line + "'") + + self.include_chain.pop() + +class RomlibApplication: + """ Base class of romlib applications. """ + TEMPLATE_DIR = os.path.dirname(os.path.realpath(__file__)) + "/templates/" + + def __init__(self, prog): + self.args = argparse.ArgumentParser(prog=prog, description=self.__doc__) + self.config = None + + def parse_arguments(self, argv): + """ Parses the arguments that should come from the command line arguments. """ + self.config = self.args.parse_args(argv) + + def build_template(self, name, mapping=None, remove_comment=False): + """ + Loads a template and builds it with the defined mapping. Template paths are always relative + to this script. + """ + + with open(self.TEMPLATE_DIR + name, "r") as template_file: + if remove_comment: + # Removing copyright comment to make the generated code more readable when the + # template is inserted multiple times into the output. + template_lines = template_file.readlines() + end_of_comment_line = 0 + for index, line in enumerate(template_lines): + if line.find("*/") != -1: + end_of_comment_line = index + break + template_data = "".join(template_lines[end_of_comment_line + 1:]) + else: + template_data = template_file.read() + + template = string.Template(template_data) + return template.substitute(mapping) + +class IndexPreprocessor(RomlibApplication): + """ Removes empty and comment lines from the index file and resolves includes. """ + + def __init__(self, prog): + RomlibApplication.__init__(self, prog) + + self.args.add_argument("-o", "--output", help="Output file", metavar="output", + default="jmpvar.s") + self.args.add_argument("--deps", help="Dependency file") + self.args.add_argument("file", help="Input file") + + def main(self): + """ + After parsing the input index file it generates a clean output with all includes resolved. + Using --deps option it also outputs the dependencies in makefile format like gcc's with -M. + """ + + index_file_parser = IndexFileParser() + index_file_parser.parse(self.config.file) + + with open(self.config.output, "w") as output_file: + for item in index_file_parser.items: + if item["type"] == "function": + patch = "\tpatch" if item["patch"] else "" + output_file.write( + item["library_name"] + "\t" + item["function_name"] + patch + "\n") + else: + output_file.write("reserved\n") + + if self.config.deps: + with open(self.config.deps, "w") as deps_file: + deps = [self.config.file] + index_file_parser.get_dependencies(self.config.file) + deps_file.write(self.config.output + ": " + " \\\n".join(deps) + "\n") + +class TableGenerator(RomlibApplication): + """ Generates the jump table by parsing the index file. """ + + def __init__(self, prog): + RomlibApplication.__init__(self, prog) + + self.args.add_argument("-o", "--output", help="Output file", metavar="output", + default="jmpvar.s") + self.args.add_argument("--bti", help="Branch Target Identification", type=int) + self.args.add_argument("file", help="Input file") + + def main(self): + """ + Inserts the jmptbl definition and the jump entries into the output file. Also can insert + BTI related code before entries if --bti option set. It can output a dependency file of the + included index files. This can be directly included in makefiles. + """ + + index_file_parser = IndexFileParser() + index_file_parser.parse(self.config.file) + + with open(self.config.output, "w") as output_file: + output_file.write(self.build_template("jmptbl_header.S")) + bti = "_bti" if self.config.bti == 1 else "" + + for item in index_file_parser.items: + template_name = "jmptbl_entry_" + item["type"] + bti + ".S" + output_file.write(self.build_template(template_name, item, True)) + +class WrapperGenerator(RomlibApplication): + """ + Generates a wrapper function for each entry in the index file except for the ones that contain + the keyword patch. The generated wrapper file is called <lib>_<fn_name>.s. + """ + + def __init__(self, prog): + RomlibApplication.__init__(self, prog) + + self.args.add_argument("-b", help="Build directory", default=".", metavar="build") + self.args.add_argument("--bti", help="Branch Target Identification", type=int) + self.args.add_argument("--list", help="Only list assembly files", action="store_true") + self.args.add_argument("file", help="Input file") + + def main(self): + """ + Iterates through the items in the parsed index file and builds the template for each entry. + """ + + index_file_parser = IndexFileParser() + index_file_parser.parse(self.config.file) + + bti = "_bti" if self.config.bti == 1 else "" + function_offset = 0 + files = [] + + for item_index in range(0, len(index_file_parser.items)): + item = index_file_parser.items[item_index] + + if item["type"] == "reserved" or item["patch"]: + continue + + asm = self.config.b + "/" + item["function_name"] + ".s" + if self.config.list: + # Only listing files + files.append(asm) + else: + with open(asm, "w") as asm_file: + # The jump instruction is 4 bytes but BTI requires and extra instruction so + # this makes it 8 bytes per entry. + function_offset = item_index * (8 if self.config.bti else 4) + + item["function_offset"] = function_offset + asm_file.write(self.build_template("wrapper" + bti + ".S", item)) + + if self.config.list: + print(" ".join(files)) + +class VariableGenerator(RomlibApplication): + """ Generates the jump table global variable with the absolute address in ROM. """ + + def __init__(self, prog): + RomlibApplication.__init__(self, prog) + + self.args.add_argument("-o", "--output", help="Output file", metavar="output", + default="jmpvar.s") + self.args.add_argument("file", help="Input file") + + def main(self): + """ + Runs nm -a command on the input file and inserts the address of the .text section into the + template as the ROM address of the jmp_table. + """ + symbols = subprocess.check_output(["nm", "-a", self.config.file]) + + matching_symbol = re.search("([0-9A-Fa-f]+) . \\.text", str(symbols)) + if not matching_symbol: + raise Exception("No '.text' section was found in %s" % self.config.file) + + mapping = {"jmptbl_address": matching_symbol.group(1)} + + with open(self.config.output, "w") as output_file: + output_file.write(self.build_template("jmptbl_glob_var.S", mapping)) + +if __name__ == "__main__": + APPS = {"genvar": VariableGenerator, "pre": IndexPreprocessor, + "gentbl": TableGenerator, "genwrappers": WrapperGenerator} + + if len(sys.argv) < 2 or sys.argv[1] not in APPS: + print("usage: romlib_generator.py [%s] [args]" % "|".join(APPS.keys()), file=sys.stderr) + sys.exit(1) + + APP = APPS[sys.argv[1]]("romlib_generator.py " + sys.argv[1]) + APP.parse_arguments(sys.argv[2:]) + try: + APP.main() + sys.exit(0) + except FileNotFoundError as file_not_found_error: + print(file_not_found_error, file=sys.stderr) + except subprocess.CalledProcessError as called_process_error: + print(called_process_error.output, file=sys.stderr) + + sys.exit(1) |