| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | #!/usr/bin/env python | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Based on cally.py (https://github.com/chaudron/cally/), Copyright 2018, Eelco Chaudron | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  | # SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD | 
					
						
							| 
									
										
										
										
											2022-06-15 16:46:55 +02:00
										 |  |  | # SPDX-License-Identifier: Apache-2.0 | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | import argparse | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  | import fnmatch | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | import os | 
					
						
							|  |  |  | import re | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  | from functools import partial | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  | from typing import BinaryIO | 
					
						
							|  |  |  | from typing import Callable | 
					
						
							|  |  |  | from typing import Dict | 
					
						
							|  |  |  | from typing import Generator | 
					
						
							|  |  |  | from typing import List | 
					
						
							|  |  |  | from typing import Optional | 
					
						
							|  |  |  | from typing import Tuple | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import elftools | 
					
						
							|  |  |  | from elftools.elf import elffile | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | FUNCTION_REGEX = re.compile( | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     r'^;; Function (?P<mangle>.*)\s+\((?P<function>\S+)(,.*)?\).*$' | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | CALL_REGEX = re.compile(r'^.*\(call.*"(?P<target>.*)".*$') | 
					
						
							|  |  |  | SYMBOL_REF_REGEX = re.compile(r'^.*\(symbol_ref[^()]*\("(?P<target>.*)"\).*$') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RtlFunction(object): | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def __init__(self, name: str, rtl_filename: str, tu_filename: str) -> None: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         self.name = name | 
					
						
							|  |  |  |         self.rtl_filename = rtl_filename | 
					
						
							|  |  |  |         self.tu_filename = tu_filename | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |         self.refs: List[str] = list() | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         self.sym = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SectionAddressRange(object): | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def __init__(self, name: str, addr: int, size: int) -> None: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         self.name = name | 
					
						
							|  |  |  |         self.low = addr | 
					
						
							|  |  |  |         self.high = addr + size | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def __str__(self) -> str: | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         return '{}: 0x{:08x} - 0x{:08x}'.format(self.name, self.low, self.high) | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def contains_address(self, addr: int) -> bool: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         return self.low <= addr < self.high | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | TARGET_SECTIONS: Dict[str, List[SectionAddressRange]] = { | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     'esp32': [ | 
					
						
							|  |  |  |         SectionAddressRange('.rom.text', 0x40000000, 0x70000), | 
					
						
							|  |  |  |         SectionAddressRange('.rom.rodata', 0x3ff96000, 0x9018) | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ], | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     'esp32s2': [ | 
					
						
							|  |  |  |         SectionAddressRange('.rom.text', 0x40000000, 0x1bed0), | 
					
						
							|  |  |  |         SectionAddressRange('.rom.rodata', 0x3ffac600, 0x392c) | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ], | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     'esp32s3': [ | 
					
						
							|  |  |  |         SectionAddressRange('.rom.text', 0x40000000, 0x568d0), | 
					
						
							|  |  |  |         SectionAddressRange('.rom.rodata', 0x3ff071c0, 0x8e30) | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ] | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Symbol(object): | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def __init__(self, name: str, addr: int, local: bool, filename: Optional[str], section: Optional[str]) -> None: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         self.name = name | 
					
						
							|  |  |  |         self.addr = addr | 
					
						
							|  |  |  |         self.local = local | 
					
						
							|  |  |  |         self.filename = filename | 
					
						
							|  |  |  |         self.section = section | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |         self.refers_to: List[Symbol] = list() | 
					
						
							|  |  |  |         self.referred_from: List[Symbol] = list() | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def __str__(self) -> str: | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         return '{} @0x{:08x} [{}]{} {}'.format( | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |             self.name, | 
					
						
							|  |  |  |             self.addr, | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |             self.section or 'unknown', | 
					
						
							|  |  |  |             ' (local)' if self.local else '', | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |             self.filename | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Reference(object): | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def __init__(self, from_sym: Symbol, to_sym: Symbol) -> None: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         self.from_sym = from_sym | 
					
						
							|  |  |  |         self.to_sym = to_sym | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def __str__(self) -> str: | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         return '{} @0x{:08x} ({}) -> {} @0x{:08x} ({})'.format( | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |             self.from_sym.name, | 
					
						
							|  |  |  |             self.from_sym.addr, | 
					
						
							|  |  |  |             self.from_sym.section, | 
					
						
							|  |  |  |             self.to_sym.name, | 
					
						
							|  |  |  |             self.to_sym.addr, | 
					
						
							|  |  |  |             self.to_sym.section | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-09 10:46:18 +01:00
										 |  |  | class IgnorePair(): | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     A pair of symbol names which should be ignored when checking references. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-11-09 10:46:18 +01:00
										 |  |  |     def __init__(self, pair: str) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  |         try: | 
					
						
							|  |  |  |             self.source, self.dest = pair.split('/') | 
					
						
							|  |  |  |         except ValueError: | 
					
						
							|  |  |  |             raise ValueError(f'Invalid ignore pair: {pair}. Must be in the form "source/dest".') | 
					
						
							| 
									
										
										
										
											2022-11-09 10:46:18 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | class ElfInfo(object): | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def __init__(self, elf_file: BinaryIO) -> None: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         self.elf_file = elf_file | 
					
						
							|  |  |  |         self.elf_obj = elffile.ELFFile(self.elf_file) | 
					
						
							|  |  |  |         self.section_ranges = self._load_sections() | 
					
						
							|  |  |  |         self.symbols = self._load_symbols() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def _load_symbols(self) -> List[Symbol]: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         symbols = [] | 
					
						
							|  |  |  |         for s in self.elf_obj.iter_sections(): | 
					
						
							|  |  |  |             if not isinstance(s, elftools.elf.sections.SymbolTableSection): | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             filename = None | 
					
						
							|  |  |  |             for sym in s.iter_symbols(): | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |                 sym_type = sym.entry['st_info']['type'] | 
					
						
							|  |  |  |                 if sym_type == 'STT_FILE': | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |                     filename = sym.name | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |                 if sym_type in ['STT_NOTYPE', 'STT_FUNC', 'STT_OBJECT']: | 
					
						
							|  |  |  |                     local = sym.entry['st_info']['bind'] == 'STB_LOCAL' | 
					
						
							|  |  |  |                     addr = sym.entry['st_value'] | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |                     symbols.append( | 
					
						
							|  |  |  |                         Symbol( | 
					
						
							|  |  |  |                             sym.name, | 
					
						
							|  |  |  |                             addr, | 
					
						
							|  |  |  |                             local, | 
					
						
							|  |  |  |                             filename if local else None, | 
					
						
							|  |  |  |                             self.section_for_addr(addr), | 
					
						
							|  |  |  |                         ) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |         return symbols | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def _load_sections(self) -> List[SectionAddressRange]: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         result = [] | 
					
						
							|  |  |  |         for segment in self.elf_obj.iter_segments(): | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |             if segment['p_type'] == 'PT_LOAD': | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |                 for section in self.elf_obj.iter_sections(): | 
					
						
							|  |  |  |                     if not segment.section_in_segment(section): | 
					
						
							|  |  |  |                         continue | 
					
						
							|  |  |  |                     result.append( | 
					
						
							|  |  |  |                         SectionAddressRange( | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |                             section.name, section['sh_addr'], section['sh_size'] | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |                         ) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         target = os.environ.get('IDF_TARGET') | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         if target in TARGET_SECTIONS: | 
					
						
							|  |  |  |             result += TARGET_SECTIONS[target] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return result | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def symbols_by_name(self, name: str) -> List['Symbol']: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         res = [] | 
					
						
							|  |  |  |         for sym in self.symbols: | 
					
						
							|  |  |  |             if sym.name == name: | 
					
						
							|  |  |  |                 res.append(sym) | 
					
						
							|  |  |  |         return res | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     def section_for_addr(self, sym_addr: int) -> Optional[str]: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         for sar in self.section_ranges: | 
					
						
							|  |  |  |             if sar.contains_address(sym_addr): | 
					
						
							|  |  |  |                 return sar.name | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  | def load_rtl_file(rtl_filename: str, tu_filename: str, functions: List[RtlFunction]) -> None: | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     last_function: Optional[RtlFunction] = None | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     for line in open(rtl_filename): | 
					
						
							|  |  |  |         # Find function definition | 
					
						
							|  |  |  |         match = re.match(FUNCTION_REGEX, line) | 
					
						
							|  |  |  |         if match: | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |             function_name = match.group('function') | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |             last_function = RtlFunction(function_name, rtl_filename, tu_filename) | 
					
						
							|  |  |  |             functions.append(last_function) | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if last_function: | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  |             # Find direct calls and indirect references | 
					
						
							|  |  |  |             for regex in [CALL_REGEX, SYMBOL_REF_REGEX]: | 
					
						
							|  |  |  |                 match = re.match(regex, line) | 
					
						
							|  |  |  |                 if match: | 
					
						
							|  |  |  |                     target = match.group('target') | 
					
						
							|  |  |  |                     if target not in last_function.refs: | 
					
						
							|  |  |  |                         last_function.refs.append(target) | 
					
						
							|  |  |  |                     continue | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | def rtl_filename_matches_sym_filename(rtl_filename: str, symbol_filename: str) -> bool: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     # Symbol file names (from ELF debug info) are short source file names, without path: "cpu_start.c". | 
					
						
							|  |  |  |     # RTL file names are paths relative to the build directory, e.g.: | 
					
						
							|  |  |  |     # "build/esp-idf/esp_system/CMakeFiles/__idf_esp_system.dir/port/cpu_start.c.234r.expand" | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # The check below may give a false positive if there are two files with the same name in | 
					
						
							|  |  |  |     # different directories. This doesn't seem to happen in IDF now, but if it does happen, | 
					
						
							|  |  |  |     # an assert in find_symbol_by_rtl_func should catch this. | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # If this becomes and issue, consider also loading the .map file and using it to figure out | 
					
						
							|  |  |  |     # which object file was used as the source of each symbol. Names of the object files and RTL files | 
					
						
							|  |  |  |     # should be much easier to match. | 
					
						
							|  |  |  |     return os.path.basename(rtl_filename).startswith(symbol_filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  | def filter_ignore_pairs(function: RtlFunction, ignore_pairs: List[IgnorePair]) -> None: | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Given a function S0 and a list of ignore pairs (S, T), | 
					
						
							|  |  |  |     remove all references to T for which S==S0. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     for ignore_pair in ignore_pairs: | 
					
						
							|  |  |  |         if fnmatch.fnmatch(function.name, ignore_pair.source): | 
					
						
							|  |  |  |             for ref in function.refs: | 
					
						
							|  |  |  |                 if fnmatch.fnmatch(ref, ignore_pair.dest): | 
					
						
							|  |  |  |                     function.refs.remove(ref) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | class SymbolNotFound(RuntimeError): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | def find_symbol_by_name(name: str, elfinfo: ElfInfo, local_func_matcher: Callable[[Symbol], bool]) -> Optional[Symbol]: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Find an ELF symbol for the given name. | 
					
						
							|  |  |  |     local_func_matcher is a callback function which checks is the candidate local symbol is suitable. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     syms = elfinfo.symbols_by_name(name) | 
					
						
							|  |  |  |     if not syms: | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  |     if len(syms) == 1: | 
					
						
							|  |  |  |         return syms[0] | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         # There are multiple symbols with a given name. Find the best fit. | 
					
						
							|  |  |  |         local_candidate = None | 
					
						
							|  |  |  |         global_candidate = None | 
					
						
							|  |  |  |         for sym in syms: | 
					
						
							|  |  |  |             if not sym.local: | 
					
						
							|  |  |  |                 assert not global_candidate  # can't have two global symbols with the same name | 
					
						
							|  |  |  |                 global_candidate = sym | 
					
						
							|  |  |  |             elif local_func_matcher(sym): | 
					
						
							|  |  |  |                 assert not local_candidate  # can't have two symbols with the same name in a single file | 
					
						
							|  |  |  |                 local_candidate = sym | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # If two symbols with the same name are defined, a global and a local one, | 
					
						
							|  |  |  |         # prefer the local symbol as the reference target. | 
					
						
							|  |  |  |         return local_candidate or global_candidate | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | def match_local_source_func(rtl_filename: str, sym: Symbol) -> bool: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Helper for match_rtl_funcs_to_symbols, checks if local symbol sym is a good candidate for the | 
					
						
							|  |  |  |     reference source (caller), based on the RTL file name. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     assert sym.filename  # should be set for local functions | 
					
						
							|  |  |  |     return rtl_filename_matches_sym_filename(rtl_filename, sym.filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | def match_local_target_func(rtl_filename: str, sym_from: Symbol, sym: Symbol) -> bool: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Helper for match_rtl_funcs_to_symbols, checks if local symbol sym is a good candidate for the | 
					
						
							|  |  |  |     reference target (callee or referenced data), based on RTL filename of the source symbol | 
					
						
							|  |  |  |     and the source symbol itself. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     assert sym.filename  # should be set for local functions | 
					
						
							|  |  |  |     if sym_from.local: | 
					
						
							|  |  |  |         # local symbol referencing another local symbol | 
					
						
							|  |  |  |         return sym_from.filename == sym.filename | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         # global symbol referencing a local symbol; | 
					
						
							|  |  |  |         # source filename is not known, use RTL filename as a hint | 
					
						
							|  |  |  |         return rtl_filename_matches_sym_filename(rtl_filename, sym.filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | def match_rtl_funcs_to_symbols(rtl_functions: List[RtlFunction], elfinfo: ElfInfo) -> Tuple[List[Symbol], List[Reference]]: | 
					
						
							|  |  |  |     symbols: List[Symbol] = [] | 
					
						
							|  |  |  |     refs: List[Reference] = [] | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # General idea: | 
					
						
							|  |  |  |     # - iterate over RTL functions. | 
					
						
							|  |  |  |     #   - for each RTL function, find the corresponding symbol | 
					
						
							|  |  |  |     #   - iterate over the functions and variables referenced from this RTL function | 
					
						
							|  |  |  |     #     - find symbols corresponding to the references | 
					
						
							|  |  |  |     #     - record every pair (sym_from, sym_to) as a Reference object | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for source_rtl_func in rtl_functions: | 
					
						
							|  |  |  |         maybe_sym_from = find_symbol_by_name(source_rtl_func.name, elfinfo, partial(match_local_source_func, source_rtl_func.rtl_filename)) | 
					
						
							|  |  |  |         if maybe_sym_from is None: | 
					
						
							|  |  |  |             # RTL references a symbol, but the symbol is not defined in the generated object file. | 
					
						
							|  |  |  |             # This means that the symbol was likely removed (or not included) at link time. | 
					
						
							|  |  |  |             # There is nothing we can do to check section placement in this case. | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         sym_from = maybe_sym_from | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if sym_from not in symbols: | 
					
						
							|  |  |  |             symbols.append(sym_from) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  |         for target_rtl_func_name in source_rtl_func.refs: | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |             if '*.LC' in target_rtl_func_name:  # skip local labels | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             maybe_sym_to = find_symbol_by_name(target_rtl_func_name, elfinfo, partial(match_local_target_func, source_rtl_func.rtl_filename, sym_from)) | 
					
						
							|  |  |  |             if not maybe_sym_to: | 
					
						
							|  |  |  |                 # This may happen for a extern reference in the RTL file, if the reference was later removed | 
					
						
							|  |  |  |                 # by one of the optimization passes, and the external definition got garbage-collected. | 
					
						
							|  |  |  |                 # TODO: consider adding some sanity check that we are here not because of some bug in | 
					
						
							|  |  |  |                 # find_symbol_by_name?.. | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             sym_to = maybe_sym_to | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             sym_from.refers_to.append(sym_to) | 
					
						
							|  |  |  |             sym_to.referred_from.append(sym_from) | 
					
						
							|  |  |  |             refs.append(Reference(sym_from, sym_to)) | 
					
						
							|  |  |  |             if sym_to not in symbols: | 
					
						
							|  |  |  |                 symbols.append(sym_to) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return symbols, refs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-09 10:46:18 +01:00
										 |  |  | def get_symbols_and_refs(rtl_list: List[str], elf_file: BinaryIO, ignore_pairs: List[IgnorePair]) -> Tuple[List[Symbol], List[Reference]]: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     elfinfo = ElfInfo(elf_file) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |     rtl_functions: List[RtlFunction] = [] | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     for file_name in rtl_list: | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  |         load_rtl_file(file_name, file_name, rtl_functions) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for rtl_func in rtl_functions: | 
					
						
							|  |  |  |         filter_ignore_pairs(rtl_func, ignore_pairs) | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return match_rtl_funcs_to_symbols(rtl_functions, elfinfo) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | def list_refs_from_to_sections(refs: List[Reference], from_sections: List[str], to_sections: List[str]) -> int: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     found = 0 | 
					
						
							|  |  |  |     for ref in refs: | 
					
						
							|  |  |  |         if (not from_sections or ref.from_sym.section in from_sections) and \ | 
					
						
							|  |  |  |            (not to_sections or ref.to_sym.section in to_sections): | 
					
						
							|  |  |  |             print(str(ref)) | 
					
						
							|  |  |  |             found += 1 | 
					
						
							|  |  |  |     return found | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | def find_files_recursive(root_path: str, ext: str) -> Generator[str, None, None]: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     for root, _, files in os.walk(root_path): | 
					
						
							|  |  |  |         for basename in files: | 
					
						
							|  |  |  |             if basename.endswith(ext): | 
					
						
							|  |  |  |                 filename = os.path.join(root, basename) | 
					
						
							|  |  |  |                 yield filename | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  | def main() -> None: | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     parser = argparse.ArgumentParser() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     parser.add_argument( | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         '--rtl-list', | 
					
						
							|  |  |  |         help='File with the list of RTL files', | 
					
						
							|  |  |  |         type=argparse.FileType('r'), | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  |     parser.add_argument( | 
					
						
							| 
									
										
										
										
											2023-06-28 11:29:02 +08:00
										 |  |  |         '--rtl-dirs', help='comma-separated list of directories where to look for RTL files, recursively' | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  |     parser.add_argument( | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         '--elf-file', | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         required=True, | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         help='Program ELF file', | 
					
						
							|  |  |  |         type=argparse.FileType('rb'), | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     action_sub = parser.add_subparsers(dest='action') | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     find_refs_parser = action_sub.add_parser( | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         'find-refs', | 
					
						
							|  |  |  |         help='List the references coming from a given list of source sections' | 
					
						
							|  |  |  |              'to a given list of target sections.', | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  |     find_refs_parser.add_argument( | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         '--from-sections', help='comma-separated list of source sections' | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  |     find_refs_parser.add_argument( | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         '--to-sections', help='comma-separated list of target sections' | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2022-11-09 10:46:18 +01:00
										 |  |  |     find_refs_parser.add_argument( | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  |         '--ignore-refs', help='Comma-separated list of symbol pairs to exclude from the references list.' | 
					
						
							|  |  |  |                               'The caller and the callee are separated by a slash. ' | 
					
						
							|  |  |  |                               'Wildcards are supported. Example: my_lib_*/some_lib_in_flash_*.' | 
					
						
							| 
									
										
										
										
											2022-11-09 10:46:18 +01:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     find_refs_parser.add_argument( | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         '--exit-code', | 
					
						
							|  |  |  |         action='store_true', | 
					
						
							|  |  |  |         help='If set, exits with non-zero code when any references found', | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  |     action_sub.add_parser( | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         'all-refs', | 
					
						
							|  |  |  |         help='Print the list of all references', | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     parser.parse_args() | 
					
						
							|  |  |  |     args = parser.parse_args() | 
					
						
							|  |  |  |     if args.rtl_list: | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         with open(args.rtl_list, 'r') as rtl_list_file: | 
					
						
							| 
									
										
										
										
											2022-06-28 19:00:12 +02:00
										 |  |  |             rtl_list = [line.strip() for line in rtl_list_file] | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2023-06-28 11:29:02 +08:00
										 |  |  |         if not args.rtl_dirs: | 
					
						
							|  |  |  |             raise RuntimeError('Either --rtl-list or --rtl-dirs must be specified') | 
					
						
							|  |  |  |         rtl_dirs = args.rtl_dirs.split(',') | 
					
						
							|  |  |  |         rtl_list = [] | 
					
						
							|  |  |  |         for dir in rtl_dirs: | 
					
						
							|  |  |  |             rtl_list.extend(list(find_files_recursive(dir, '.expand'))) | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if not rtl_list: | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |         raise RuntimeError('No RTL files specified') | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-26 14:45:59 +01:00
										 |  |  |     if args.action == 'find-refs' and args.ignore_refs: | 
					
						
							|  |  |  |         ignore_pairs = [IgnorePair(pair) for pair in args.ignore_refs.split(',')] | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         ignore_pairs = [] | 
					
						
							| 
									
										
										
										
											2022-11-09 10:46:18 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     _, refs = get_symbols_and_refs(rtl_list, args.elf_file, ignore_pairs) | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     if args.action == 'find-refs': | 
					
						
							|  |  |  |         from_sections = args.from_sections.split(',') if args.from_sections else [] | 
					
						
							|  |  |  |         to_sections = args.to_sections.split(',') if args.to_sections else [] | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         found = list_refs_from_to_sections( | 
					
						
							|  |  |  |             refs, from_sections, to_sections | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         if args.exit_code and found: | 
					
						
							|  |  |  |             raise SystemExit(1) | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  |     elif args.action == 'all-refs': | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |         for r in refs: | 
					
						
							|  |  |  |             print(str(r)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 10:49:01 +08:00
										 |  |  | if __name__ == '__main__': | 
					
						
							| 
									
										
										
										
											2020-07-07 20:40:17 +02:00
										 |  |  |     main() |