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

This commit is contained in:
Ivan Grokhotkov 2024-03-26 14:45:59 +01:00
parent 73041ea10a
commit 64757e9fea
No known key found for this signature in database
GPG Key ID: 1E050E141B280628
2 changed files with 58 additions and 40 deletions

View File

@ -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}
)

View File

@ -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,27 +187,9 @@ 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)
# 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:
@ -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')
if args.action == 'find-refs' and args.ignore_refs:
ignore_pairs = [IgnorePair(pair) for pair in args.ignore_refs.split(',')]
else:
ignore_pairs = []
for pair in args.ignore_symbols.split(',') if args.ignore_symbols else []:
ignore_pairs.append(IgnorePair(pair))
_, refs = get_symbols_and_refs(rtl_list, args.elf_file, ignore_pairs)