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