Files
nuttx/tools/find_symbol_callers.sh
dongjiuzhu1 8c0850b0bd toos/scipts: add script to find symbol caller
Usage: ./tools/find_symbol_callers.sh <elf_file> <symbol_name> [source_root]

Examples:
  ./tools/find_symbol_callers.sh nuttx __aeabi_f2d
  ./tools/find_symbol_callers.sh nuttx malloc

Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
2025-12-31 08:47:23 -03:00

258 lines
8.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# find_symbol_callers.sh
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# find_symbol_callers.sh - Find all call sites of specified symbols
#
# Usage: ./find_symbol_callers.sh <elf_file> <symbol_name> [source_root]
#
# Examples:
# ./find_symbol_callers.sh nuttx __aeabi_f2d
# ./find_symbol_callers.sh nuttx "__aeabi_.*" ../
#
set -e
# Color definitions
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Check arguments
if [ $# -lt 2 ]; then
echo "Usage: $0 <elf_file> <symbol_name> [source_root]"
echo ""
echo "Examples:"
echo " $0 nuttx __aeabi_f2d"
echo " $0 nuttx __aeabi_f2d ./"
echo " $0 nuttx malloc"
echo ""
exit 1
fi
ELF_FILE="$1"
SYMBOL_NAME="$2"
SRC_ROOT="${3:-.}"
# Check toolchain prefix
if command -v arm-none-eabi-objdump &> /dev/null; then
TOOLCHAIN_PREFIX="arm-none-eabi-"
elif command -v objdump &> /dev/null; then
TOOLCHAIN_PREFIX=""
else
echo -e "${RED}Error: objdump tool not found${NC}"
exit 1
fi
OBJDUMP="${TOOLCHAIN_PREFIX}objdump"
NM="${TOOLCHAIN_PREFIX}nm"
ADDR2LINE="${TOOLCHAIN_PREFIX}addr2line"
# Check if ELF file exists
if [ ! -f "$ELF_FILE" ]; then
echo -e "${RED}Error: ELF file not found: $ELF_FILE${NC}"
exit 1
fi
echo -e "${CYAN}========================================${NC}"
echo -e "${CYAN}Symbol Call Analysis Tool${NC}"
echo -e "${CYAN}========================================${NC}"
echo -e "ELF File: ${GREEN}$ELF_FILE${NC}"
echo -e "Symbol: ${GREEN}$SYMBOL_NAME${NC}"
echo -e "Source Root: ${GREEN}$SRC_ROOT${NC}"
echo ""
# Create temporary file
DISASM_FILE=$(mktemp /tmp/disasm_XXXXXX.txt)
trap "rm -f $DISASM_FILE" EXIT
echo -e "${YELLOW}[1/5] Generating disassembly...${NC}"
$OBJDUMP -d "$ELF_FILE" > "$DISASM_FILE"
echo -e "${GREEN}✓ Disassembly complete${NC}"
echo ""
# Find symbol addresses
echo -e "${YELLOW}[2/5] Finding symbol addresses...${NC}"
SYMBOL_ADDRS=$($NM "$ELF_FILE" | grep -E " [TtWw] " | grep -E "$SYMBOL_NAME" | awk '{print $1, $3}')
if [ -z "$SYMBOL_ADDRS" ]; then
echo -e "${RED}Error: Symbol '$SYMBOL_NAME' not found${NC}"
echo ""
echo "Hint: Use the following command to view all available symbols:"
echo " $NM $ELF_FILE | grep -i '$SYMBOL_NAME'"
exit 1
fi
echo -e "${GREEN}Found symbols:${NC}"
echo "$SYMBOL_ADDRS" | while read addr name; do
echo -e " ${BLUE}0x$addr${NC} - ${MAGENTA}$name${NC}"
done
echo ""
# Find all call sites
echo -e "${YELLOW}[3/5] Finding call sites...${NC}"
# Build address regex pattern
ADDR_PATTERN=$(echo "$SYMBOL_ADDRS" | awk '{print $1}' | sed 's/^0*//' | paste -sd '|')
if [ -z "$ADDR_PATTERN" ]; then
echo -e "${RED}Error: Cannot build address pattern${NC}"
exit 1
fi
# Find all bl/b/jmp instructions to these addresses
CALL_SITES=$(grep -E "bl|b\.w|jmp" "$DISASM_FILE" | grep -E "($ADDR_PATTERN)" | \
sed 's/^[[:space:]]*//' | awk '{print $1}' | sed 's/://')
if [ -z "$CALL_SITES" ]; then
echo -e "${YELLOW}No direct call sites found (possibly inlined or unused)${NC}"
echo ""
exit 0
fi
CALL_COUNT=$(echo "$CALL_SITES" | wc -l)
echo -e "${GREEN}Found $CALL_COUNT call site(s)${NC}"
echo ""
# Analyze each call site
echo -e "${YELLOW}[4/5] Analyzing caller functions...${NC}"
echo ""
declare -A CALLER_FUNCS
CALLER_COUNT=0
for call_addr in $CALL_SITES; do
# Find the containing function from disassembly
FUNC_INFO=$(awk -v addr="$call_addr" '
/^[0-9a-f]+ <.*>:$/ {
funcname=$0
funcaddr=$1
}
$1 == addr":" {
print funcaddr "|" funcname
exit
}
' "$DISASM_FILE")
if [ -n "$FUNC_INFO" ]; then
FUNC_ADDR=$(echo "$FUNC_INFO" | cut -d'|' -f1)
FUNC_NAME=$(echo "$FUNC_INFO" | cut -d'|' -f2)
# Deduplicate and count
if [ -z "${CALLER_FUNCS[$FUNC_NAME]}" ]; then
CALLER_FUNCS[$FUNC_NAME]="$FUNC_ADDR|1"
CALLER_COUNT=$((CALLER_COUNT + 1))
else
OLD_COUNT=$(echo "${CALLER_FUNCS[$FUNC_NAME]}" | cut -d'|' -f2)
NEW_COUNT=$((OLD_COUNT + 1))
CALLER_FUNCS[$FUNC_NAME]="$FUNC_ADDR|$NEW_COUNT"
fi
fi
done
echo -e "${GREEN}Found $CALLER_COUNT distinct caller function(s):${NC}"
echo ""
# Print caller function list
for func in "${!CALLER_FUNCS[@]}"; do
INFO="${CALLER_FUNCS[$func]}"
ADDR=$(echo "$INFO" | cut -d'|' -f1)
COUNT=$(echo "$INFO" | cut -d'|' -f2)
echo -e "${CYAN}$func${NC}"
echo -e " Address: ${BLUE}0x$ADDR${NC}"
echo -e " Call count: ${MAGENTA}$COUNT${NC}"
done
echo ""
# Find source code locations
echo -e "${YELLOW}[5/5] Finding source code locations...${NC}"
echo ""
for func in "${!CALLER_FUNCS[@]}"; do
INFO="${CALLER_FUNCS[$func]}"
ADDR=$(echo "$INFO" | cut -d'|' -f1)
# Extract function name (remove address and brackets)
FUNC_SIMPLE=$(echo "$func" | sed 's/^[0-9a-f]* <//' | sed 's/>:.*//')
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${CYAN}Function: ${MAGENTA}$FUNC_SIMPLE${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# Try using addr2line
LINE_INFO=$($ADDR2LINE -e "$ELF_FILE" -f -i "0x$ADDR" 2>/dev/null || echo "")
if [ -n "$LINE_INFO" ] && ! echo "$LINE_INFO" | grep -q "^?"; then
echo -e "${GREEN}Debug info:${NC}"
echo "$LINE_INFO" | head -2
fi
# Search for function definition in source code
if [ -d "$SRC_ROOT" ]; then
SRC_FILES=$(grep -rl "$FUNC_SIMPLE" "$SRC_ROOT" --include="*.c" --include="*.cpp" 2>/dev/null | head -5)
if [ -n "$SRC_FILES" ]; then
echo -e "${GREEN}Possible source files:${NC}"
echo "$SRC_FILES" | while read file; do
# Find function definition line
LINE_NUM=$(grep -n "^\(static \)\?.*$FUNC_SIMPLE\s*(" "$file" 2>/dev/null | head -1 | cut -d':' -f1)
if [ -n "$LINE_NUM" ]; then
echo -e " ${BLUE}$file:$LINE_NUM${NC}"
else
echo -e " ${BLUE}$file${NC}"
fi
done
else
echo -e "${YELLOW}Function definition not found in source code${NC}"
fi
fi
# Show disassembly code at call sites
echo -e "${GREEN}Disassembly snippet:${NC}"
for call_addr in $CALL_SITES; do
CALL_FUNC=$(awk -v addr="$call_addr" '
/^[0-9a-f]+ <.*>:$/ { funcname=$0 }
$1 == addr":" { print funcname; exit }
' "$DISASM_FILE")
if [ "$CALL_FUNC" = "$func" ]; then
# Show 5 lines before and after call site
grep -A5 -B5 "^[[:space:]]*$call_addr:" "$DISASM_FILE" | \
sed "s/^[[:space:]]*$call_addr:/ >>> $call_addr:/" | head -11
echo ""
fi
done
echo ""
done
echo -e "${CYAN}========================================${NC}"
echo -e "${GREEN}Analysis complete!${NC}"
echo -e "${CYAN}========================================${NC}"
echo ""
echo -e "${YELLOW}Summary:${NC}"
echo -e " • Symbol called ${MAGENTA}$CALL_COUNT${NC} time(s)"
echo -e " • Involves ${MAGENTA}$CALLER_COUNT${NC} distinct function(s)"
echo ""