Merge branch 'bugfix/check_callgraph_ignore_indirect_calls' into 'master'

fix(check_callgraph): rework --ignore-symbols to be more generic

See merge request espressif/esp-idf!29850
This commit is contained in:
Ivan Grokhotkov 2024-04-07 15:05:08 +08:00
commit 279b67cc5e
2 changed files with 58 additions and 40 deletions

View File

@ -9,6 +9,14 @@ set(COMPONENTS main)
project(test_heap) 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) if(CONFIG_COMPILER_DUMP_RTL_FILES)
add_custom_target(check_test_app_sections ALL add_custom_target(check_test_app_sections ALL
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
@ -17,7 +25,7 @@ if(CONFIG_COMPILER_DUMP_RTL_FILES)
find-refs find-refs
--from-sections=.iram0.text --from-sections=.iram0.text
--to-sections=.flash.text,.flash.rodata --to-sections=.flash.text,.flash.rodata
--ignore-symbols=__func__/__assert_func,__func__/heap_caps_alloc_failed --ignore-refs=${ignore_refs}
--exit-code --exit-code
DEPENDS ${elf} DEPENDS ${elf}
) )

View File

@ -1,14 +1,20 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# Based on cally.py (https://github.com/chaudron/cally/), Copyright 2018, Eelco Chaudron # 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 # SPDX-License-Identifier: Apache-2.0
import argparse import argparse
import fnmatch
import os import os
import re import re
from functools import partial 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 import elftools
from elftools.elf import elffile from elftools.elf import elffile
@ -25,7 +31,6 @@ class RtlFunction(object):
self.name = name self.name = name
self.rtl_filename = rtl_filename self.rtl_filename = rtl_filename
self.tu_filename = tu_filename self.tu_filename = tu_filename
self.calls: List[str] = list()
self.refs: List[str] = list() self.refs: List[str] = list()
self.sym = None self.sym = None
@ -96,8 +101,14 @@ class Reference(object):
class IgnorePair(): class IgnorePair():
"""
A pair of symbol names which should be ignored when checking references.
"""
def __init__(self, pair: str) -> None: 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): class ElfInfo(object):
@ -164,7 +175,7 @@ class ElfInfo(object):
return None 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 last_function: Optional[RtlFunction] = None
for line in open(rtl_filename): for line in open(rtl_filename):
# Find function definition # Find function definition
@ -176,32 +187,14 @@ def load_rtl_file(rtl_filename: str, tu_filename: str, functions: List[RtlFuncti
continue continue
if last_function: if last_function:
# Find direct function calls # Find direct calls and indirect references
match = re.match(CALL_REGEX, line) for regex in [CALL_REGEX, SYMBOL_REF_REGEX]:
if match: match = re.match(regex, line)
target = match.group('target') if match:
target = match.group('target')
# if target matches on of the IgnorePair function_call attributes, remove if target not in last_function.refs:
# the last occurrence of the associated symbol from the last_function.refs list. last_function.refs.append(target)
call_matching_pairs = [pair for pair in ignore_pairs if pair.function_call == target] continue
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
def rtl_filename_matches_sym_filename(rtl_filename: str, symbol_filename: str) -> bool: 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) 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): class SymbolNotFound(RuntimeError):
pass pass
@ -298,7 +303,7 @@ def match_rtl_funcs_to_symbols(rtl_functions: List[RtlFunction], elfinfo: ElfInf
if sym_from not in symbols: if sym_from not in symbols:
symbols.append(sym_from) 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 if '*.LC' in target_rtl_func_name: # skip local labels
continue continue
@ -325,7 +330,10 @@ def get_symbols_and_refs(rtl_list: List[str], elf_file: BinaryIO, ignore_pairs:
rtl_functions: List[RtlFunction] = [] rtl_functions: List[RtlFunction] = []
for file_name in rtl_list: 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) 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' '--to-sections', help='comma-separated list of target sections'
) )
find_refs_parser.add_argument( find_refs_parser.add_argument(
'--ignore-symbols', help='comma-separated list of symbol/function_name pairs. \ '--ignore-refs', help='Comma-separated list of symbol pairs to exclude from the references list.'
This will force the parser to ignore the symbol preceding the call to function_name' '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( find_refs_parser.add_argument(
'--exit-code', '--exit-code',
@ -407,9 +416,10 @@ def main() -> None:
if not rtl_list: if not rtl_list:
raise RuntimeError('No RTL files specified') raise RuntimeError('No RTL files specified')
ignore_pairs = [] if args.action == 'find-refs' and args.ignore_refs:
for pair in args.ignore_symbols.split(',') if args.ignore_symbols else []: ignore_pairs = [IgnorePair(pair) for pair in args.ignore_refs.split(',')]
ignore_pairs.append(IgnorePair(pair)) else:
ignore_pairs = []
_, refs = get_symbols_and_refs(rtl_list, args.elf_file, ignore_pairs) _, refs = get_symbols_and_refs(rtl_list, args.elf_file, ignore_pairs)