|
| 1 | +#!/usr/bin/env python |
| 2 | +# |
| 3 | +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD |
| 4 | +# |
| 5 | +# SPDX-License-Identifier: Apache-2.0 |
| 6 | + |
| 7 | +import logging |
| 8 | +import os |
| 9 | +import argparse |
| 10 | +import sys |
| 11 | +import re |
| 12 | +import subprocess |
| 13 | + |
| 14 | +def get_global_symbols(lines, type, undefined=False, symbol_types=None): |
| 15 | + """ |
| 16 | + Extract global symbols from the given lines of a symbol table. |
| 17 | +
|
| 18 | + :param lines: List of lines from the symbol table, each representing a symbol. |
| 19 | + :param type: Type of the input file ('e' for ELF files, 'l' for static libraries). |
| 20 | + :param undefined: If True, only extract undefined (UND) symbols; otherwise, extract all GLOBAL symbols. |
| 21 | + :param symbol_types: A list of symbol types to filter by (e.g., ['FUNC', 'OBJECT']). If None, no filtering by type. |
| 22 | + :return: List of symbol names if any match the criteria; otherwise, returns an empty list. |
| 23 | + """ |
| 24 | + symbols = [] |
| 25 | + |
| 26 | + if type == 'e': |
| 27 | + # Pattern for ELF files |
| 28 | + if not undefined: |
| 29 | + pattern = re.compile( |
| 30 | + r'^\s*\d+:\s+(\S+)\s+(\d+)\s+(FUNC|OBJECT)\s+GLOBAL\s+DEFAULT\s+(?:\d+|ABS|UND|COM|DEBUG)\s+(\S+)', |
| 31 | + re.MULTILINE |
| 32 | + ) |
| 33 | + else: |
| 34 | + pattern = re.compile( |
| 35 | + r'^\s*\d*:\s*\w*\s*\d*\s*NOTYPE\s*GLOBAL\s*DEFAULT\s*UND\s+(\S*)', |
| 36 | + re.MULTILINE |
| 37 | + ) |
| 38 | + |
| 39 | + for line in lines: |
| 40 | + match = pattern.match(line) |
| 41 | + if match: |
| 42 | + if not undefined: |
| 43 | + address, size, symbol_type, symbol_name = match.groups() |
| 44 | + |
| 45 | + # Filter by symbol type if specified |
| 46 | + if symbol_types and symbol_type not in symbol_types: |
| 47 | + continue |
| 48 | + |
| 49 | + symbols.append(symbol_name) |
| 50 | + else: |
| 51 | + symbol_name = match.group(1) |
| 52 | + symbols.append(symbol_name) |
| 53 | + |
| 54 | + elif type == 'l': |
| 55 | + # Patterns for static libraries |
| 56 | + func_pattern = re.compile(r'^\s*[0-9a-fA-F]+\s+[TD]\s+(\S+)$') |
| 57 | + var_pattern = re.compile(r'^\s*[0-9a-fA-F]+\s+[BD]\s+(\S+)$') |
| 58 | + |
| 59 | + for line in lines: |
| 60 | + if not undefined: |
| 61 | + func_match = func_pattern.match(line) |
| 62 | + var_match = var_pattern.match(line) |
| 63 | + |
| 64 | + if func_match: |
| 65 | + symbols.append(func_match.group(1)) |
| 66 | + elif var_match: |
| 67 | + symbols.append(var_match.group(1)) |
| 68 | + |
| 69 | + return symbols |
| 70 | + |
| 71 | +def save_c_file(symbols, output, symbol_table, exclude_symbols=None): |
| 72 | + """ |
| 73 | + Write extern declarations and ESP_ELFSYM structure to a C file, excluding specified symbols. |
| 74 | +
|
| 75 | + :param symbols: List of symbol names. |
| 76 | + :param output: Path to the output C file. |
| 77 | + :param exclude_symbols: List of symbol names to exclude; defaults to ['elf_find_sym']. |
| 78 | + """ |
| 79 | + if exclude_symbols is None: |
| 80 | + exclude_symbols = ['elf_find_sym'] # Set default excluded symbols |
| 81 | + |
| 82 | + # Filter out excluded symbols |
| 83 | + filtered_symbols = [name for name in symbols if name not in exclude_symbols] |
| 84 | + |
| 85 | + # Build the content of the C file |
| 86 | + buf = '/*\n' |
| 87 | + buf += ' * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD\n' |
| 88 | + buf += ' *\n' |
| 89 | + buf += ' * SPDX-License-Identifier: Apache-2.0\n' |
| 90 | + buf += ' */\n\n' |
| 91 | + |
| 92 | + # Add standard library headers |
| 93 | + libc_headers = ['stddef'] # Add more headers as needed |
| 94 | + buf += '\n'.join([f'#include <{h}.h>' for h in libc_headers]) + '\n\n' |
| 95 | + |
| 96 | + # Add custom header |
| 97 | + buf += '#include "private/elf_symbol.h"\n\n' |
| 98 | + |
| 99 | + # Generate extern declarations if there are symbols |
| 100 | + if filtered_symbols: |
| 101 | + buf += '/* Extern declarations from ELF symbol table */\n\n' |
| 102 | + buf += '#pragma GCC diagnostic push\n' |
| 103 | + buf += '#pragma GCC diagnostic ignored "-Wbuiltin-declaration-mismatch"\n' |
| 104 | + for symbol_name in filtered_symbols: |
| 105 | + buf += f'extern int {symbol_name};\n' |
| 106 | + buf += '#pragma GCC diagnostic pop\n\n' |
| 107 | + |
| 108 | + # Define the symbol table structure with dynamic variable name |
| 109 | + symbol_table_var = f'g_{symbol_table}_elfsyms' |
| 110 | + buf += f'/* Available ELF symbols table: {symbol_table_var} */\n' |
| 111 | + buf += f'\nconst struct esp_elfsym {symbol_table_var}[] = {{\n' |
| 112 | + |
| 113 | + # Generate ESP_ELFSYM_EXPORT entries |
| 114 | + if filtered_symbols: |
| 115 | + for symbol_name in filtered_symbols: |
| 116 | + buf += f' ESP_ELFSYM_EXPORT({symbol_name}),\n' |
| 117 | + |
| 118 | + # End the symbol table |
| 119 | + buf += ' ESP_ELFSYM_END\n' |
| 120 | + buf += '};\n' |
| 121 | + |
| 122 | + # Write to the file |
| 123 | + with open(output, 'w+') as f: |
| 124 | + f.write(buf) |
| 125 | + |
| 126 | +def main(): |
| 127 | + """ |
| 128 | + Main function to parse command-line arguments and process the input file's symbol table. |
| 129 | + """ |
| 130 | + parser = argparse.ArgumentParser(description='Extract all public functions from an application project', prog='symbols') |
| 131 | + |
| 132 | + parser.add_argument( |
| 133 | + '--output-file', '-of', |
| 134 | + help='Custom output file path with filename (overrides --output)', |
| 135 | + default=None |
| 136 | + ) |
| 137 | + |
| 138 | + parser.add_argument( |
| 139 | + '--input', '-i', |
| 140 | + help='Input file name with full path', |
| 141 | + required=True |
| 142 | + ) |
| 143 | + |
| 144 | + parser.add_argument( |
| 145 | + '--undefined', '-u', |
| 146 | + action='store_true', |
| 147 | + help='If set, only extract undefined (UND) symbols; otherwise, extract all GLOBAL symbols.', |
| 148 | + default=False |
| 149 | + ) |
| 150 | + |
| 151 | + parser.add_argument( |
| 152 | + '--exclude', '-e', |
| 153 | + nargs='+', |
| 154 | + help='Symbols to exclude from the generated C file (e.g., memcpy __ltdf2). Default: elf_find_sym', |
| 155 | + default=[] # User can extend this list |
| 156 | + ) |
| 157 | + |
| 158 | + parser.add_argument( |
| 159 | + '--type', '-t', |
| 160 | + choices=['e', 'l'], |
| 161 | + required=True, |
| 162 | + help='Type of the input file: "elf" for ELF file, "lib" for static library (.a)' |
| 163 | + ) |
| 164 | + |
| 165 | + parser.add_argument( |
| 166 | + '--debug', '-d', |
| 167 | + help='Debug level(option is \'debug\')', |
| 168 | + default='no', |
| 169 | + type=str) |
| 170 | + |
| 171 | + args = parser.parse_args() |
| 172 | + |
| 173 | + # Configure logging |
| 174 | + if args.debug == 'debug': |
| 175 | + logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') |
| 176 | + else: |
| 177 | + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
| 178 | + |
| 179 | + # Get the absolute path of the current file |
| 180 | + cur_dir = os.path.dirname(os.path.abspath(__file__)) |
| 181 | + |
| 182 | + if args.type == 'e': |
| 183 | + extracted_part = 'customer' |
| 184 | + elif args.type == 'l': |
| 185 | + # Convert relative path to absolute path |
| 186 | + input_abs_path = os.path.abspath(args.input) |
| 187 | + |
| 188 | + # Get the base name of the input file (without extension) |
| 189 | + input_basename = os.path.basename(input_abs_path) |
| 190 | + |
| 191 | + # Use a regular expression to extract the middle part |
| 192 | + match = re.search(r'^lib(.+)\.a$', input_basename) |
| 193 | + if match: |
| 194 | + extracted_part = match.group(1) |
| 195 | + else: |
| 196 | + logging.error('Invalid input file name format. Expected format: lib<name>.a') |
| 197 | + sys.exit(1) |
| 198 | + |
| 199 | + # Determine the output file path |
| 200 | + if args.output_file: |
| 201 | + # Use the custom file path provided by the user |
| 202 | + elfsym_file_dir = os.path.abspath(args.output_file) |
| 203 | + output_dir = os.path.dirname(elfsym_file_dir) |
| 204 | + output_abs_path = os.path.abspath(args.output_file) |
| 205 | + output_basename = os.path.basename(output_abs_path) |
| 206 | + extracted_part = os.path.splitext(output_basename)[0] |
| 207 | + else: |
| 208 | + # Use the default behavior: generate the file in the parent directory's 'src' folder |
| 209 | + parent_dir = os.path.dirname(cur_dir) |
| 210 | + output_dir = os.path.join(parent_dir, 'src') # Default directory is 'src' under the parent directory |
| 211 | + output_file_name = f'esp_all_symbol.c' |
| 212 | + elfsym_file_dir = os.path.join(output_dir, output_file_name) |
| 213 | + |
| 214 | + # Ensure the output directory exists |
| 215 | + os.makedirs(output_dir, exist_ok=True) |
| 216 | + |
| 217 | + # Set default excluded symbols and allow user to extend the list |
| 218 | + exclude_symbols = ['elf_find_sym', 'g_customer_elfsyms'] + args.exclude |
| 219 | + |
| 220 | + if args.type == 'e': |
| 221 | + cmd = ['readelf', '-s', '-W', args.input] |
| 222 | + elif args.type == 'l': |
| 223 | + cmd = ['nm', '--defined-only', '-g', args.input] |
| 224 | + |
| 225 | + # Execute the readelf or nm command for static libraries |
| 226 | + try: |
| 227 | + result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True) |
| 228 | + lines = result.stdout.splitlines() |
| 229 | + except subprocess.CalledProcessError as e: |
| 230 | + logging.error(f'Error executing command: {e.stderr}') |
| 231 | + sys.exit(1) |
| 232 | + except FileNotFoundError: |
| 233 | + logging.error('nm command not found. Please ensure it is installed and available in your PATH.') |
| 234 | + sys.exit(1) |
| 235 | + |
| 236 | + # Extract global symbols from ELF file or static library |
| 237 | + symbols = get_global_symbols(lines, type=args.type, undefined=args.undefined, symbol_types=['FUNC', 'OBJECT']) |
| 238 | + |
| 239 | + if not symbols: |
| 240 | + logging.warning('No global symbols found in the input file.') |
| 241 | + sys.exit(0) |
| 242 | + |
| 243 | + logging.debug('symbols: %s'%(cmd)) |
| 244 | + logging.debug('symbols: %s'%(symbols)) |
| 245 | + logging.debug('elfsym_file_dir: %s'%(elfsym_file_dir)) |
| 246 | + logging.debug('extracted_part: %s'%(extracted_part)) |
| 247 | + logging.debug('exclude_symbols: %s'%(exclude_symbols)) |
| 248 | + |
| 249 | + # Save the C file |
| 250 | + try: |
| 251 | + save_c_file(symbols, elfsym_file_dir, extracted_part, exclude_symbols=exclude_symbols) |
| 252 | + logging.info(f"C file with extern declarations and symbol table has been saved to '{elfsym_file_dir}'.") |
| 253 | + except Exception as e: |
| 254 | + logging.error(f'Error writing C file: {e}') |
| 255 | + sys.exit(1) |
| 256 | + |
| 257 | +def _main(): |
| 258 | + """ |
| 259 | + Wrapper for the main function to catch and handle runtime errors. |
| 260 | + """ |
| 261 | + try: |
| 262 | + main() |
| 263 | + except RuntimeError as e: |
| 264 | + logging.error(f'A fatal error occurred: {e}') |
| 265 | + sys.exit(2) |
| 266 | + |
| 267 | +if __name__ == '__main__': |
| 268 | + _main() |
0 commit comments