1#!/usr/bin/env python 2 3# Copyright 2015 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17# Utility functions used to parse a list of DLL entry points. 18# Expected format: 19# 20# <empty-line> -> ignored 21# #<comment> -> ignored 22# %<verbatim> -> verbatim output for header files. 23# !<prefix> -> prefix name for header files. 24# <return-type> <function-name> <signature> ; -> entry point declaration. 25# 26# Anything else is an error. 27 28import argparse 29import re 30import sys 31 32re_func = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_]*)\((.*)\);$""") 33re_param = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_]*)$""") 34 35 36class Entry: 37 """Small class used to model a single DLL entry point.""" 38 39 def __init__(self, func_name, return_type, parameters): 40 """Initialize Entry instance. |func_name| is the function name, 41 |return_type| its return type, and |parameters| is a list of 42 (type,name) tuples from the entry's signature. 43 """ 44 self.func_name = func_name 45 self.return_type = return_type 46 self.parameters = "" 47 self.vartypes = [] 48 self.varnames = [] 49 self.call = "" 50 comma = "" 51 for param in parameters: 52 self.vartypes.append(param[0]) 53 self.varnames.append(param[1]) 54 self.parameters += "%s%s %s" % (comma, param[0], param[1]) 55 self.call += "%s%s" % (comma, param[1]) 56 comma = ", " 57 58 59def banner_command(argv): 60 """Return sanitized command-line description. 61 |argv| must be a list of command-line parameters, e.g. sys.argv. 62 Return a string corresponding to the command, with platform-specific 63 paths removed.""" 64 65 # Remove path from first parameter 66 argv = argv[:] 67 argv[0] = "host/commands/gen-entries.py" 68 return " ".join(argv) 69 70 71def parse_entries_file(lines): 72 """Parse an .entries file and return a tuple of: 73 entries: list of Entry instances from the file. 74 prefix_name: prefix name from the file, or None. 75 verbatim: list of verbatim lines from the file. 76 errors: list of errors in the file, prefixed by line number. 77 """ 78 entries = [] 79 verbatim = [] 80 errors = [] 81 lineno = 0 82 prefix_name = None 83 for line in lines: 84 lineno += 1 85 line = line.strip() 86 if len(line) == 0: # Ignore empty lines 87 continue 88 if line[0] == "#": # Ignore comments 89 continue 90 if line[0] == "!": # Prefix name 91 prefix_name = line[1:] 92 continue 93 if line[0] == "%": # Verbatim line copy 94 verbatim.append(line[1:]) 95 continue 96 # Must be a function signature. 97 m = re_func.match(line) 98 if not m: 99 errors.append("%d: '%s'" % (lineno, line)) 100 continue 101 102 return_type, func_name, parameters = m.groups() 103 return_type = return_type.strip() 104 parameters = parameters.strip() 105 params = [] 106 failure = False 107 if parameters != "void": 108 for parameter in parameters.split(','): 109 parameter = parameter.strip() 110 m = re_param.match(parameter) 111 if not m: 112 errors.append("%d: parameter '%s'" % (lineno, parameter)) 113 failure = True 114 break 115 else: 116 param_type, param_name = m.groups() 117 params.append((param_type.strip(), param_name.strip())) 118 119 if not failure: 120 entries.append(Entry(func_name, return_type, params)) 121 122 return (entries, prefix_name, verbatim, errors) 123 124 125def gen_functions_header(entries, prefix_name, verbatim, filename, with_args): 126 """Generate a C header containing a macro listing all entry points. 127 |entries| is a list of Entry instances. 128 |prefix_name| is a prefix-name, it will be converted to upper-case. 129 |verbatim| is a list of verbatim lines that must appear before the 130 macro declaration. Useful to insert #include <> statements. 131 |filename| is the name of the original file. 132 """ 133 prefix_name = prefix_name.upper() 134 135 print("// Auto-generated with: " + banner_command(sys.argv)) 136 print("// DO NOT EDIT THIS FILE") 137 print("") 138 print(f"#ifndef {prefix_name}_FUNCTIONS_H") 139 print(f"#define {prefix_name}_FUNCTIONS_H") 140 print("") 141 for line in verbatim: 142 print(line) 143 144 print(f"#define LIST_{prefix_name}_FUNCTIONS(X) \\") 145 for entry in entries: 146 if with_args: 147 print(f" X({entry.return_type}, {entry.func_name}, " 148 f"({entry.parameters}), ({entry.call})) \\") 149 else: 150 print(f" X({entry.return_type}, {entry.func_name}, " 151 f"({entry.parameters})) \\") 152 153 print("") 154 print("") 155 print(f"#endif // {prefix_name}_FUNCTIONS_H") 156 157 158def gen_dll_wrapper(entries, prefix_name, verbatim, filename): 159 """Generate a C source file that contains functions that act as wrappers 160 for entry points located in another shared library. This allows the 161 code that calls these functions to perform lazy-linking to system 162 libraries. 163 |entries|, |prefix_name|, |verbatim| and |filename| are the same as 164 for gen_functions_header() above. 165 """ 166 upper_name = prefix_name.upper() 167 168 ENTRY_PREFIX = "__dll_" 169 170 print("// Auto-generated with: " + banner_command(sys.argv)) 171 print("// DO NOT EDIT THIS FILE") 172 print("") 173 print("#include <dlfcn.h>") 174 for line in verbatim: 175 print(line) 176 177 print("") 178 print("///") 179 print("/// W R A P P E R P O I N T E R S") 180 print("///") 181 print("") 182 for entry in entries: 183 ptr_name = ENTRY_PREFIX + entry.func_name 184 print(f"static {entry.return_type} " 185 f"(*{ptr_name})({entry.parameters}) = 0;") 186 187 print("") 188 print("///") 189 print("/// W R A P P E R F U N C T I O N S") 190 print("///") 191 print("") 192 193 for entry in entries: 194 print(f"{entry.return_type} {entry.func_name}({entry.parameters}) {{") 195 ptr_name = ENTRY_PREFIX + entry.func_name 196 if entry.return_type != "void": 197 print(f" return {ptr_name}({entry.call});") 198 else: 199 print(" {ptr_name}({entry.call});") 200 print("}\n") 201 202 print("") 203 print("///") 204 print("/// I N I T I A L I Z A T I O N F U N C T I O N") 205 print("///") 206 print("") 207 208 print(f"int {prefix_name}_dynlink_init(void* lib) {{") 209 for entry in entries: 210 ptr_name = ENTRY_PREFIX + entry.func_name 211 print(f" {ptr_name} = ({entry.return_type}(*)({entry.parameters})) " 212 f"dlsym(lib, \"{entry.func_name}\");") 213 print(f" if (!{ptr_name}) return -1;") 214 print(" return 0;") 215 print("}") 216 217 218def gen_windows_def_file(entries): 219 """Generate a windows DLL .def file. |entries| is a list of Entry instances. 220 """ 221 print("EXPORTS") 222 for entry in entries: 223 print(" " + entry.func_name) 224 225 226def gen_unix_sym_file(entries): 227 """Generate an ELF linker version file. |entries| is a list of Entry 228 instances. 229 """ 230 print("VERSION {") 231 print("\tglobal:") 232 for entry in entries: 233 print(f"\t\t{entry.func_name};") 234 print("\tlocal:") 235 print("\t\t*;") 236 print("};") 237 238 239def gen_symbols(entries, underscore): 240 """Generate a list of symbols from |entries|, a list of Entry instances. 241 |underscore| is a boolean. If True, then prepend an underscore to each 242 symbol name. 243 """ 244 prefix = "" 245 if underscore: 246 prefix = "_" 247 for entry in entries: 248 print(prefix + entry.func_name) 249 250 251def parse_file(filename, lines, mode): 252 """Generate one of possible outputs from |filename|. |lines| must be a list 253 of text lines from the file, and |mode| is one of the --mode option 254 values. 255 """ 256 entries, prefix_name, verbatim, errors = parse_entries_file(lines) 257 if errors: 258 for error in errors: 259 print(f"ERROR: {filename}:{error}", file=sys.stderr) 260 sys.exit(1) 261 262 if not prefix_name: 263 prefix_name = "unknown" 264 265 if mode == 'def': 266 gen_windows_def_file(entries) 267 elif mode == 'sym': 268 gen_unix_sym_file(entries) 269 elif mode == 'wrapper': 270 gen_dll_wrapper(entries, prefix_name, verbatim, filename) 271 elif mode == 'symbols': 272 gen_symbols(entries, False) 273 elif mode == '_symbols': 274 gen_symbols(entries, True) 275 elif mode == 'functions': 276 gen_functions_header(entries, prefix_name, verbatim, filename, False) 277 elif mode == 'funcargs': 278 gen_functions_header(entries, prefix_name, verbatim, filename, True) 279 280 281# List of valid --mode option values. 282mode_list = [ 283 'def', 'sym', 'wrapper', 'symbols', '_symbols', 'functions', 'funcargs' 284] 285 286# Argument parsing. 287parser = argparse.ArgumentParser( 288 formatter_class=argparse.RawDescriptionHelpFormatter, 289 description="""\ 290A script used to parse an .entries input file containing a list of function 291declarations, and generate various output files depending on the value of 292the --mode option, which can be: 293 294 def Generate a windows DLL .def file. 295 sym Generate a Unix .so linker script. 296 wrapper Generate a C source file containing wrapper functions. 297 symbols Generate a simple list of symbols, one per line. 298 _symbols Generate a simple list of symbols, prefixed with _. 299 functions Generate a C header containing a macro listing all functions. 300 funcargs Like 'functions', but adds function call arguments to listing. 301 302""") 303parser.add_argument("--mode", help="Output mode", choices=mode_list) 304parser.add_argument("--output", help="output file") 305parser.add_argument("file", help=".entries file path") 306 307args = parser.parse_args() 308 309if not args.mode: 310 print("ERROR: Please use --mode=<name>, see --help.", file=sys.stderr) 311 sys.exit(1) 312 313if args.output: 314 sys.stdout = open(args.output, "w+") 315 316if args.file == '--': 317 parse_file("<stdin>", sys.stdin, args.mode) 318else: 319 parse_file(args.file, open(args.file), args.mode) 320