Skip to content

Commit

Permalink
Merge branch 'bugfix/check_callgraph_ignore_indirect_calls' into 'mas…
Browse files Browse the repository at this point in the history
…ter'

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

See merge request espressif/esp-idf!29850
  • Loading branch information
igrr committed Apr 7, 2024
2 parents 71ceedc + 64757e9 commit 279b67c
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 40 deletions.
10 changes: 9 additions & 1 deletion components/heap/test_apps/heap_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}
)
Expand Down
88 changes: 49 additions & 39 deletions tools/ci/check_callgraph.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -25,7 +31,6 @@ def __init__(self, name: str, rtl_filename: str, tu_filename: str) -> None:
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

Expand Down Expand Up @@ -96,8 +101,14 @@ def __str__(self) -> str:


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):
Expand Down Expand Up @@ -164,7 +175,7 @@ def section_for_addr(self, sym_addr: int) -> Optional[str]:
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
Expand All @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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)

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 279b67c

Please sign in to comment.