From 64757e9feaf461208cadafd645492b3caa43c7af Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 26 Mar 2024 14:45:59 +0100 Subject: [PATCH] fix(check_callgraph): rework --ignore-refs to be more generic --- .../heap/test_apps/heap_tests/CMakeLists.txt | 10 ++- tools/ci/check_callgraph.py | 88 +++++++++++-------- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/components/heap/test_apps/heap_tests/CMakeLists.txt b/components/heap/test_apps/heap_tests/CMakeLists.txt index b88d576973..f0d2972932 100644 --- a/components/heap/test_apps/heap_tests/CMakeLists.txt +++ b/components/heap/test_apps/heap_tests/CMakeLists.txt @@ -9,6 +9,14 @@ set(COMPONENTS main) project(test_heap) +string(JOIN "," ignore_refs + heap_caps_*/__func__* + tlsf_*/__func__* + multi_heap_*/__func__* + dram_alloc_to_iram_addr/__func__* + list_*/__func__* +) + if(CONFIG_COMPILER_DUMP_RTL_FILES) add_custom_target(check_test_app_sections ALL COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py @@ -17,7 +25,7 @@ if(CONFIG_COMPILER_DUMP_RTL_FILES) find-refs --from-sections=.iram0.text --to-sections=.flash.text,.flash.rodata - --ignore-symbols=__func__/__assert_func,__func__/heap_caps_alloc_failed + --ignore-refs=${ignore_refs} --exit-code DEPENDS ${elf} ) diff --git a/tools/ci/check_callgraph.py b/tools/ci/check_callgraph.py index 2d99edbe64..ab74b90323 100755 --- a/tools/ci/check_callgraph.py +++ b/tools/ci/check_callgraph.py @@ -1,14 +1,20 @@ #!/usr/bin/env python # # Based on cally.py (https://github.com/chaudron/cally/), Copyright 2018, Eelco Chaudron -# SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 - import argparse +import fnmatch import os import re from functools import partial -from typing import BinaryIO, Callable, Dict, Generator, List, Optional, Tuple +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 import elftools from elftools.elf import elffile @@ -25,7 +31,6 @@ class RtlFunction(object): self.name = name self.rtl_filename = rtl_filename self.tu_filename = tu_filename - self.calls: List[str] = list() self.refs: List[str] = list() self.sym = None @@ -96,8 +101,14 @@ class Reference(object): class IgnorePair(): + """ + A pair of symbol names which should be ignored when checking references. + """ def __init__(self, pair: str) -> None: - self.symbol, self.function_call = pair.split('/') + try: + self.source, self.dest = pair.split('/') + except ValueError: + raise ValueError(f'Invalid ignore pair: {pair}. Must be in the form "source/dest".') class ElfInfo(object): @@ -164,7 +175,7 @@ class ElfInfo(object): return None -def load_rtl_file(rtl_filename: str, tu_filename: str, functions: List[RtlFunction], ignore_pairs: List[IgnorePair]) -> None: +def load_rtl_file(rtl_filename: str, tu_filename: str, functions: List[RtlFunction]) -> None: last_function: Optional[RtlFunction] = None for line in open(rtl_filename): # Find function definition @@ -176,32 +187,14 @@ def load_rtl_file(rtl_filename: str, tu_filename: str, functions: List[RtlFuncti continue if last_function: - # Find direct function calls - match = re.match(CALL_REGEX, line) - if match: - target = match.group('target') - - # if target matches on of the IgnorePair function_call attributes, remove - # the last occurrence of the associated symbol from the last_function.refs list. - call_matching_pairs = [pair for pair in ignore_pairs if pair.function_call == target] - if call_matching_pairs and last_function and last_function.refs: - for pair in call_matching_pairs: - ignored_symbols = [ref for ref in last_function.refs if pair.symbol in ref] - if ignored_symbols: - last_ref = ignored_symbols.pop() - last_function.refs = [ref for ref in last_function.refs if last_ref != ref] - - if target not in last_function.calls: - last_function.calls.append(target) - continue - - # Find symbol references - match = re.match(SYMBOL_REF_REGEX, line) - if match: - target = match.group('target') - if target not in last_function.refs: - last_function.refs.append(target) - continue + # 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 def rtl_filename_matches_sym_filename(rtl_filename: str, symbol_filename: str) -> bool: @@ -219,6 +212,18 @@ def rtl_filename_matches_sym_filename(rtl_filename: str, symbol_filename: str) - return os.path.basename(rtl_filename).startswith(symbol_filename) +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) + + class SymbolNotFound(RuntimeError): pass @@ -298,7 +303,7 @@ def match_rtl_funcs_to_symbols(rtl_functions: List[RtlFunction], elfinfo: ElfInf if sym_from not in symbols: symbols.append(sym_from) - for target_rtl_func_name in source_rtl_func.calls + source_rtl_func.refs: + for target_rtl_func_name in source_rtl_func.refs: if '*.LC' in target_rtl_func_name: # skip local labels continue @@ -325,7 +330,10 @@ def get_symbols_and_refs(rtl_list: List[str], elf_file: BinaryIO, ignore_pairs: rtl_functions: List[RtlFunction] = [] for file_name in rtl_list: - load_rtl_file(file_name, file_name, rtl_functions, ignore_pairs) + load_rtl_file(file_name, file_name, rtl_functions) + + for rtl_func in rtl_functions: + filter_ignore_pairs(rtl_func, ignore_pairs) return match_rtl_funcs_to_symbols(rtl_functions, elfinfo) @@ -378,8 +386,9 @@ def main() -> None: '--to-sections', help='comma-separated list of target sections' ) find_refs_parser.add_argument( - '--ignore-symbols', help='comma-separated list of symbol/function_name pairs. \ - This will force the parser to ignore the symbol preceding the call to function_name' + '--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_*.' ) find_refs_parser.add_argument( '--exit-code', @@ -407,9 +416,10 @@ def main() -> None: if not rtl_list: raise RuntimeError('No RTL files specified') - ignore_pairs = [] - for pair in args.ignore_symbols.split(',') if args.ignore_symbols else []: - ignore_pairs.append(IgnorePair(pair)) + if args.action == 'find-refs' and args.ignore_refs: + ignore_pairs = [IgnorePair(pair) for pair in args.ignore_refs.split(',')] + else: + ignore_pairs = [] _, refs = get_symbols_and_refs(rtl_list, args.elf_file, ignore_pairs)