18c2ecf20Sopenharmony_ci#!/bin/bash
28c2ecf20Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0
38c2ecf20Sopenharmony_ci#
48c2ecf20Sopenharmony_ci# Translate stack dump function offsets.
58c2ecf20Sopenharmony_ci#
68c2ecf20Sopenharmony_ci# addr2line doesn't work with KASLR addresses.  This works similarly to
78c2ecf20Sopenharmony_ci# addr2line, but instead takes the 'func+0x123' format as input:
88c2ecf20Sopenharmony_ci#
98c2ecf20Sopenharmony_ci#   $ ./scripts/faddr2line ~/k/vmlinux meminfo_proc_show+0x5/0x568
108c2ecf20Sopenharmony_ci#   meminfo_proc_show+0x5/0x568:
118c2ecf20Sopenharmony_ci#   meminfo_proc_show at fs/proc/meminfo.c:27
128c2ecf20Sopenharmony_ci#
138c2ecf20Sopenharmony_ci# If the address is part of an inlined function, the full inline call chain is
148c2ecf20Sopenharmony_ci# printed:
158c2ecf20Sopenharmony_ci#
168c2ecf20Sopenharmony_ci#   $ ./scripts/faddr2line ~/k/vmlinux native_write_msr+0x6/0x27
178c2ecf20Sopenharmony_ci#   native_write_msr+0x6/0x27:
188c2ecf20Sopenharmony_ci#   arch_static_branch at arch/x86/include/asm/msr.h:121
198c2ecf20Sopenharmony_ci#    (inlined by) static_key_false at include/linux/jump_label.h:125
208c2ecf20Sopenharmony_ci#    (inlined by) native_write_msr at arch/x86/include/asm/msr.h:125
218c2ecf20Sopenharmony_ci#
228c2ecf20Sopenharmony_ci# The function size after the '/' in the input is optional, but recommended.
238c2ecf20Sopenharmony_ci# It's used to help disambiguate any duplicate symbol names, which can occur
248c2ecf20Sopenharmony_ci# rarely.  If the size is omitted for a duplicate symbol then it's possible for
258c2ecf20Sopenharmony_ci# multiple code sites to be printed:
268c2ecf20Sopenharmony_ci#
278c2ecf20Sopenharmony_ci#   $ ./scripts/faddr2line ~/k/vmlinux raw_ioctl+0x5
288c2ecf20Sopenharmony_ci#   raw_ioctl+0x5/0x20:
298c2ecf20Sopenharmony_ci#   raw_ioctl at drivers/char/raw.c:122
308c2ecf20Sopenharmony_ci#
318c2ecf20Sopenharmony_ci#   raw_ioctl+0x5/0xb1:
328c2ecf20Sopenharmony_ci#   raw_ioctl at net/ipv4/raw.c:876
338c2ecf20Sopenharmony_ci#
348c2ecf20Sopenharmony_ci# Multiple addresses can be specified on a single command line:
358c2ecf20Sopenharmony_ci#
368c2ecf20Sopenharmony_ci#   $ ./scripts/faddr2line ~/k/vmlinux type_show+0x10/45 free_reserved_area+0x90
378c2ecf20Sopenharmony_ci#   type_show+0x10/0x2d:
388c2ecf20Sopenharmony_ci#   type_show at drivers/video/backlight/backlight.c:213
398c2ecf20Sopenharmony_ci#
408c2ecf20Sopenharmony_ci#   free_reserved_area+0x90/0x123:
418c2ecf20Sopenharmony_ci#   free_reserved_area at mm/page_alloc.c:6429 (discriminator 2)
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ciset -o errexit
458c2ecf20Sopenharmony_ciset -o nounset
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ciusage() {
488c2ecf20Sopenharmony_ci	echo "usage: faddr2line [--list] <object file> <func+offset> <func+offset>..." >&2
498c2ecf20Sopenharmony_ci	exit 1
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ciwarn() {
538c2ecf20Sopenharmony_ci	echo "$1" >&2
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cidie() {
578c2ecf20Sopenharmony_ci	echo "ERROR: $1" >&2
588c2ecf20Sopenharmony_ci	exit 1
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ciREADELF="${CROSS_COMPILE:-}readelf"
628c2ecf20Sopenharmony_ciADDR2LINE="${CROSS_COMPILE:-}addr2line"
638c2ecf20Sopenharmony_ciAWK="awk"
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cicommand -v ${AWK} >/dev/null 2>&1 || die "${AWK} isn't installed"
668c2ecf20Sopenharmony_cicommand -v ${READELF} >/dev/null 2>&1 || die "${READELF} isn't installed"
678c2ecf20Sopenharmony_cicommand -v ${ADDR2LINE} >/dev/null 2>&1 || die "${ADDR2LINE} isn't installed"
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci# Try to figure out the source directory prefix so we can remove it from the
708c2ecf20Sopenharmony_ci# addr2line output.  HACK ALERT: This assumes that start_kernel() is in
718c2ecf20Sopenharmony_ci# init/main.c!  This only works for vmlinux.  Otherwise it falls back to
728c2ecf20Sopenharmony_ci# printing the absolute path.
738c2ecf20Sopenharmony_cifind_dir_prefix() {
748c2ecf20Sopenharmony_ci	local objfile=$1
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	local start_kernel_addr=$(${READELF} --symbols --wide $objfile | sed 's/\[.*\]//' |
778c2ecf20Sopenharmony_ci		${AWK} '$8 == "start_kernel" {printf "0x%s", $2}')
788c2ecf20Sopenharmony_ci	[[ -z $start_kernel_addr ]] && return
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	local file_line=$(${ADDR2LINE} -e $objfile $start_kernel_addr)
818c2ecf20Sopenharmony_ci	[[ -z $file_line ]] && return
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	local prefix=${file_line%init/main.c:*}
848c2ecf20Sopenharmony_ci	if [[ -z $prefix ]] || [[ $prefix = $file_line ]]; then
858c2ecf20Sopenharmony_ci		return
868c2ecf20Sopenharmony_ci	fi
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	DIR_PREFIX=$prefix
898c2ecf20Sopenharmony_ci	return 0
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci__faddr2line() {
938c2ecf20Sopenharmony_ci	local objfile=$1
948c2ecf20Sopenharmony_ci	local func_addr=$2
958c2ecf20Sopenharmony_ci	local dir_prefix=$3
968c2ecf20Sopenharmony_ci	local print_warnings=$4
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	local sym_name=${func_addr%+*}
998c2ecf20Sopenharmony_ci	local func_offset=${func_addr#*+}
1008c2ecf20Sopenharmony_ci	func_offset=${func_offset%/*}
1018c2ecf20Sopenharmony_ci	local user_size=
1028c2ecf20Sopenharmony_ci	local file_type
1038c2ecf20Sopenharmony_ci	local is_vmlinux=0
1048c2ecf20Sopenharmony_ci	[[ $func_addr =~ "/" ]] && user_size=${func_addr#*/}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	if [[ -z $sym_name ]] || [[ -z $func_offset ]] || [[ $sym_name = $func_addr ]]; then
1078c2ecf20Sopenharmony_ci		warn "bad func+offset $func_addr"
1088c2ecf20Sopenharmony_ci		DONE=1
1098c2ecf20Sopenharmony_ci		return
1108c2ecf20Sopenharmony_ci	fi
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	# vmlinux uses absolute addresses in the section table rather than
1138c2ecf20Sopenharmony_ci	# section offsets.
1148c2ecf20Sopenharmony_ci	local file_type=$(${READELF} --file-header $objfile |
1158c2ecf20Sopenharmony_ci		${AWK} '$1 == "Type:" { print $2; exit }')
1168c2ecf20Sopenharmony_ci	if [[ $file_type = "EXEC" ]] || [[ $file_type == "DYN" ]]; then
1178c2ecf20Sopenharmony_ci		is_vmlinux=1
1188c2ecf20Sopenharmony_ci	fi
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	# Go through each of the object's symbols which match the func name.
1218c2ecf20Sopenharmony_ci	# In rare cases there might be duplicates, in which case we print all
1228c2ecf20Sopenharmony_ci	# matches.
1238c2ecf20Sopenharmony_ci	while read line; do
1248c2ecf20Sopenharmony_ci		local fields=($line)
1258c2ecf20Sopenharmony_ci		local sym_addr=0x${fields[1]}
1268c2ecf20Sopenharmony_ci		local sym_elf_size=${fields[2]}
1278c2ecf20Sopenharmony_ci		local sym_sec=${fields[6]}
1288c2ecf20Sopenharmony_ci		local sec_size
1298c2ecf20Sopenharmony_ci		local sec_name
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci		# Get the section size:
1328c2ecf20Sopenharmony_ci		sec_size=$(${READELF} --section-headers --wide $objfile |
1338c2ecf20Sopenharmony_ci			sed 's/\[ /\[/' |
1348c2ecf20Sopenharmony_ci			${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print "0x" $6; exit }')
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci		if [[ -z $sec_size ]]; then
1378c2ecf20Sopenharmony_ci			warn "bad section size: section: $sym_sec"
1388c2ecf20Sopenharmony_ci			DONE=1
1398c2ecf20Sopenharmony_ci			return
1408c2ecf20Sopenharmony_ci		fi
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci		# Get the section name:
1438c2ecf20Sopenharmony_ci		sec_name=$(${READELF} --section-headers --wide $objfile |
1448c2ecf20Sopenharmony_ci			sed 's/\[ /\[/' |
1458c2ecf20Sopenharmony_ci			${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print $2; exit }')
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci		if [[ -z $sec_name ]]; then
1488c2ecf20Sopenharmony_ci			warn "bad section name: section: $sym_sec"
1498c2ecf20Sopenharmony_ci			DONE=1
1508c2ecf20Sopenharmony_ci			return
1518c2ecf20Sopenharmony_ci		fi
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci		# Calculate the symbol size.
1548c2ecf20Sopenharmony_ci		#
1558c2ecf20Sopenharmony_ci		# Unfortunately we can't use the ELF size, because kallsyms
1568c2ecf20Sopenharmony_ci		# also includes the padding bytes in its size calculation.  For
1578c2ecf20Sopenharmony_ci		# kallsyms, the size calculation is the distance between the
1588c2ecf20Sopenharmony_ci		# symbol and the next symbol in a sorted list.
1598c2ecf20Sopenharmony_ci		local sym_size
1608c2ecf20Sopenharmony_ci		local cur_sym_addr
1618c2ecf20Sopenharmony_ci		local found=0
1628c2ecf20Sopenharmony_ci		while read line; do
1638c2ecf20Sopenharmony_ci			local fields=($line)
1648c2ecf20Sopenharmony_ci			cur_sym_addr=0x${fields[1]}
1658c2ecf20Sopenharmony_ci			local cur_sym_elf_size=${fields[2]}
1668c2ecf20Sopenharmony_ci			local cur_sym_name=${fields[7]:-}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci			if [[ $cur_sym_addr = $sym_addr ]] &&
1698c2ecf20Sopenharmony_ci			   [[ $cur_sym_elf_size = $sym_elf_size ]] &&
1708c2ecf20Sopenharmony_ci			   [[ $cur_sym_name = $sym_name ]]; then
1718c2ecf20Sopenharmony_ci				found=1
1728c2ecf20Sopenharmony_ci				continue
1738c2ecf20Sopenharmony_ci			fi
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci			if [[ $found = 1 ]]; then
1768c2ecf20Sopenharmony_ci				sym_size=$(($cur_sym_addr - $sym_addr))
1778c2ecf20Sopenharmony_ci				[[ $sym_size -lt $sym_elf_size ]] && continue;
1788c2ecf20Sopenharmony_ci				found=2
1798c2ecf20Sopenharmony_ci				break
1808c2ecf20Sopenharmony_ci			fi
1818c2ecf20Sopenharmony_ci		done < <(${READELF} --symbols --wide $objfile | sed 's/\[.*\]//' | ${AWK} -v sec=$sym_sec '$7 == sec' | sort --key=2)
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci		if [[ $found = 0 ]]; then
1848c2ecf20Sopenharmony_ci			warn "can't find symbol: sym_name: $sym_name sym_sec: $sym_sec sym_addr: $sym_addr sym_elf_size: $sym_elf_size"
1858c2ecf20Sopenharmony_ci			DONE=1
1868c2ecf20Sopenharmony_ci			return
1878c2ecf20Sopenharmony_ci		fi
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci		# If nothing was found after the symbol, assume it's the last
1908c2ecf20Sopenharmony_ci		# symbol in the section.
1918c2ecf20Sopenharmony_ci		[[ $found = 1 ]] && sym_size=$(($sec_size - $sym_addr))
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci		if [[ -z $sym_size ]] || [[ $sym_size -le 0 ]]; then
1948c2ecf20Sopenharmony_ci			warn "bad symbol size: sym_addr: $sym_addr cur_sym_addr: $cur_sym_addr"
1958c2ecf20Sopenharmony_ci			DONE=1
1968c2ecf20Sopenharmony_ci			return
1978c2ecf20Sopenharmony_ci		fi
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci		sym_size=0x$(printf %x $sym_size)
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci		# Calculate the address from user-supplied offset:
2028c2ecf20Sopenharmony_ci		local addr=$(($sym_addr + $func_offset))
2038c2ecf20Sopenharmony_ci		if [[ -z $addr ]] || [[ $addr = 0 ]]; then
2048c2ecf20Sopenharmony_ci			warn "bad address: $sym_addr + $func_offset"
2058c2ecf20Sopenharmony_ci			DONE=1
2068c2ecf20Sopenharmony_ci			return
2078c2ecf20Sopenharmony_ci		fi
2088c2ecf20Sopenharmony_ci		addr=0x$(printf %x $addr)
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci		# If the user provided a size, make sure it matches the symbol's size:
2118c2ecf20Sopenharmony_ci		if [[ -n $user_size ]] && [[ $user_size -ne $sym_size ]]; then
2128c2ecf20Sopenharmony_ci			[[ $print_warnings = 1 ]] &&
2138c2ecf20Sopenharmony_ci				echo "skipping $sym_name address at $addr due to size mismatch ($user_size != $sym_size)"
2148c2ecf20Sopenharmony_ci			continue;
2158c2ecf20Sopenharmony_ci		fi
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci		# Make sure the provided offset is within the symbol's range:
2188c2ecf20Sopenharmony_ci		if [[ $func_offset -gt $sym_size ]]; then
2198c2ecf20Sopenharmony_ci			[[ $print_warnings = 1 ]] &&
2208c2ecf20Sopenharmony_ci				echo "skipping $sym_name address at $addr due to size mismatch ($func_offset > $sym_size)"
2218c2ecf20Sopenharmony_ci			continue
2228c2ecf20Sopenharmony_ci		fi
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci		# In case of duplicates or multiple addresses specified on the
2258c2ecf20Sopenharmony_ci		# cmdline, separate multiple entries with a blank line:
2268c2ecf20Sopenharmony_ci		[[ $FIRST = 0 ]] && echo
2278c2ecf20Sopenharmony_ci		FIRST=0
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci		echo "$sym_name+$func_offset/$sym_size:"
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci		# Pass section address to addr2line and strip absolute paths
2328c2ecf20Sopenharmony_ci		# from the output:
2338c2ecf20Sopenharmony_ci		local args="--functions --pretty-print --inlines --exe=$objfile"
2348c2ecf20Sopenharmony_ci		[[ $is_vmlinux = 0 ]] && args="$args --section=$sec_name"
2358c2ecf20Sopenharmony_ci		local output=$(${ADDR2LINE} $args $addr | sed "s; $dir_prefix\(\./\)*; ;")
2368c2ecf20Sopenharmony_ci		[[ -z $output ]] && continue
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci		# Default output (non --list):
2398c2ecf20Sopenharmony_ci		if [[ $LIST = 0 ]]; then
2408c2ecf20Sopenharmony_ci			echo "$output" | while read -r line
2418c2ecf20Sopenharmony_ci			do
2428c2ecf20Sopenharmony_ci				echo $line
2438c2ecf20Sopenharmony_ci			done
2448c2ecf20Sopenharmony_ci			DONE=1;
2458c2ecf20Sopenharmony_ci			continue
2468c2ecf20Sopenharmony_ci		fi
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci		# For --list, show each line with its corresponding source code:
2498c2ecf20Sopenharmony_ci		echo "$output" | while read -r line
2508c2ecf20Sopenharmony_ci		do
2518c2ecf20Sopenharmony_ci			echo
2528c2ecf20Sopenharmony_ci			echo $line
2538c2ecf20Sopenharmony_ci			n=$(echo $line | sed 's/.*:\([0-9]\+\).*/\1/g')
2548c2ecf20Sopenharmony_ci			n1=$[$n-5]
2558c2ecf20Sopenharmony_ci			n2=$[$n+5]
2568c2ecf20Sopenharmony_ci			f=$(echo $line | sed 's/.*at \(.\+\):.*/\1/g')
2578c2ecf20Sopenharmony_ci			${AWK} 'NR>=strtonum("'$n1'") && NR<=strtonum("'$n2'") { if (NR=='$n') printf(">%d<", NR); else printf(" %d ", NR); printf("\t%s\n", $0)}' $f
2588c2ecf20Sopenharmony_ci		done
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci		DONE=1
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	done < <(${READELF} --symbols --wide $objfile | sed 's/\[.*\]//' | ${AWK} -v fn=$sym_name '$4 == "FUNC" && $8 == fn')
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci[[ $# -lt 2 ]] && usage
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ciobjfile=$1
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ciLIST=0
2708c2ecf20Sopenharmony_ci[[ "$objfile" == "--list" ]] && LIST=1 && shift && objfile=$1
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci[[ ! -f $objfile ]] && die "can't find objfile $objfile"
2738c2ecf20Sopenharmony_cishift
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ciDIR_PREFIX=supercalifragilisticexpialidocious
2768c2ecf20Sopenharmony_cifind_dir_prefix $objfile
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ciFIRST=1
2798c2ecf20Sopenharmony_ciwhile [[ $# -gt 0 ]]; do
2808c2ecf20Sopenharmony_ci	func_addr=$1
2818c2ecf20Sopenharmony_ci	shift
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	# print any matches found
2848c2ecf20Sopenharmony_ci	DONE=0
2858c2ecf20Sopenharmony_ci	__faddr2line $objfile $func_addr $DIR_PREFIX 0
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	# if no match was found, print warnings
2888c2ecf20Sopenharmony_ci	if [[ $DONE = 0 ]]; then
2898c2ecf20Sopenharmony_ci		__faddr2line $objfile $func_addr $DIR_PREFIX 1
2908c2ecf20Sopenharmony_ci		warn "no match for $func_addr"
2918c2ecf20Sopenharmony_ci	fi
2928c2ecf20Sopenharmony_cidone
293