1dbd1abb2SSasha Levin#!/bin/bash
2b2441318SGreg Kroah-Hartman# SPDX-License-Identifier: GPL-2.0
3dbd1abb2SSasha Levin# (c) 2014, Sasha Levin <[email protected]>
4dbd1abb2SSasha Levin#set -x
5dbd1abb2SSasha Levin
626681eb3SStephen Boydusage() {
7dbd1abb2SSasha Levin	echo "Usage:"
8d5ce757dSStephen Boyd	echo "	$0 -r <release> | <vmlinux> [<base path>|auto] [<modules path>]"
926681eb3SStephen Boyd}
10dbd1abb2SSasha Levin
1199115db4SMiguel Ojeda# Try to find a Rust demangler
1299115db4SMiguel Ojedaif type llvm-cxxfilt >/dev/null 2>&1 ; then
1399115db4SMiguel Ojeda	cppfilt=llvm-cxxfilt
1499115db4SMiguel Ojedaelif type c++filt >/dev/null 2>&1 ; then
1599115db4SMiguel Ojeda	cppfilt=c++filt
1699115db4SMiguel Ojeda	cppfilt_opts=-i
1799115db4SMiguel Ojedafi
1899115db4SMiguel Ojeda
19f90dde44SKonstantin Khlebnikovif [[ $1 == "-r" ]] ; then
20f90dde44SKonstantin Khlebnikov	vmlinux=""
21f90dde44SKonstantin Khlebnikov	basepath="auto"
22f90dde44SKonstantin Khlebnikov	modpath=""
23f90dde44SKonstantin Khlebnikov	release=$2
24f90dde44SKonstantin Khlebnikov
25f90dde44SKonstantin Khlebnikov	for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do
26f90dde44SKonstantin Khlebnikov		if [ -e "$fn" ] ; then
27f90dde44SKonstantin Khlebnikov			vmlinux=$fn
28f90dde44SKonstantin Khlebnikov			break
29f90dde44SKonstantin Khlebnikov		fi
30f90dde44SKonstantin Khlebnikov	done
31f90dde44SKonstantin Khlebnikov
32f90dde44SKonstantin Khlebnikov	if [[ $vmlinux == "" ]] ; then
33f90dde44SKonstantin Khlebnikov		echo "ERROR! vmlinux image for release $release is not found" >&2
3426681eb3SStephen Boyd		usage
35f90dde44SKonstantin Khlebnikov		exit 2
36f90dde44SKonstantin Khlebnikov	fi
37f90dde44SKonstantin Khlebnikovelse
38dbd1abb2SSasha Levin	vmlinux=$1
39ecda6e27SKonstantin Khlebnikov	basepath=${2-auto}
40310c6dd0SKonstantin Khlebnikov	modpath=$3
41431151b6SKonstantin Khlebnikov	release=""
4226681eb3SStephen Boyd	debuginfod=
4326681eb3SStephen Boyd
4426681eb3SStephen Boyd	# Can we use debuginfod-find?
4526681eb3SStephen Boyd	if type debuginfod-find >/dev/null 2>&1 ; then
4626681eb3SStephen Boyd		debuginfod=${1-only}
4726681eb3SStephen Boyd	fi
4826681eb3SStephen Boyd
4926681eb3SStephen Boyd	if [[ $vmlinux == "" && -z $debuginfod ]] ; then
5026681eb3SStephen Boyd		echo "ERROR! vmlinux image must be specified" >&2
5126681eb3SStephen Boyd		usage
5226681eb3SStephen Boyd		exit 1
5326681eb3SStephen Boyd	fi
54f90dde44SKonstantin Khlebnikovfi
55431151b6SKonstantin Khlebnikov
563af8acf6SSchspa Shideclare aarray_support=true
573af8acf6SSchspa Shideclare -A cache 2>/dev/null
583af8acf6SSchspa Shiif [[ $? != 0 ]]; then
593af8acf6SSchspa Shi	aarray_support=false
603af8acf6SSchspa Shielse
61310c6dd0SKonstantin Khlebnikov	declare -A modcache
623af8acf6SSchspa Shifi
63dbd1abb2SSasha Levin
64431151b6SKonstantin Khlebnikovfind_module() {
6526681eb3SStephen Boyd	if [[ -n $debuginfod ]] ; then
6626681eb3SStephen Boyd		if [[ -n $modbuildid ]] ; then
6726681eb3SStephen Boyd			debuginfod-find debuginfo $modbuildid && return
6826681eb3SStephen Boyd		fi
6926681eb3SStephen Boyd
7026681eb3SStephen Boyd		# Only using debuginfod so don't try to find vmlinux module path
7126681eb3SStephen Boyd		if [[ $debuginfod == "only" ]] ; then
7226681eb3SStephen Boyd			return
7326681eb3SStephen Boyd		fi
7426681eb3SStephen Boyd	fi
7526681eb3SStephen Boyd
76431151b6SKonstantin Khlebnikov	if [[ "$modpath" != "" ]] ; then
77431151b6SKonstantin Khlebnikov		for fn in $(find "$modpath" -name "${module//_/[-_]}.ko*") ; do
78431151b6SKonstantin Khlebnikov			if readelf -WS "$fn" | grep -qwF .debug_line ; then
79431151b6SKonstantin Khlebnikov				echo $fn
80431151b6SKonstantin Khlebnikov				return
81431151b6SKonstantin Khlebnikov			fi
82431151b6SKonstantin Khlebnikov		done
83431151b6SKonstantin Khlebnikov		return 1
84431151b6SKonstantin Khlebnikov	fi
85431151b6SKonstantin Khlebnikov
86431151b6SKonstantin Khlebnikov	modpath=$(dirname "$vmlinux")
87431151b6SKonstantin Khlebnikov	find_module && return
88431151b6SKonstantin Khlebnikov
89431151b6SKonstantin Khlebnikov	if [[ $release == "" ]] ; then
905bf0f3bcSStephen Boyd		release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" 2>/dev/null | sed -n 's/\$1 = "\(.*\)".*/\1/p')
91431151b6SKonstantin Khlebnikov	fi
92431151b6SKonstantin Khlebnikov
93431151b6SKonstantin Khlebnikov	for dn in {/usr/lib/debug,}/lib/modules/$release ; do
94431151b6SKonstantin Khlebnikov		if [ -e "$dn" ] ; then
95431151b6SKonstantin Khlebnikov			modpath="$dn"
96431151b6SKonstantin Khlebnikov			find_module && return
97431151b6SKonstantin Khlebnikov		fi
98431151b6SKonstantin Khlebnikov	done
99431151b6SKonstantin Khlebnikov
100431151b6SKonstantin Khlebnikov	modpath=""
101431151b6SKonstantin Khlebnikov	return 1
102431151b6SKonstantin Khlebnikov}
103431151b6SKonstantin Khlebnikov
104dbd1abb2SSasha Levinparse_symbol() {
105dbd1abb2SSasha Levin	# The structure of symbol at this point is:
106e260fe01SRobert Jarzmik	#   ([name]+[offset]/[total length])
107dbd1abb2SSasha Levin	#
108dbd1abb2SSasha Levin	# For example:
109dbd1abb2SSasha Levin	#   do_basic_setup+0x9c/0xbf
110dbd1abb2SSasha Levin
111310c6dd0SKonstantin Khlebnikov	if [[ $module == "" ]] ; then
112310c6dd0SKonstantin Khlebnikov		local objfile=$vmlinux
1133af8acf6SSchspa Shi	elif [[ $aarray_support == true && "${modcache[$module]+isset}" == "isset" ]]; then
114310c6dd0SKonstantin Khlebnikov		local objfile=${modcache[$module]}
115310c6dd0SKonstantin Khlebnikov	else
116431151b6SKonstantin Khlebnikov		local objfile=$(find_module)
117431151b6SKonstantin Khlebnikov		if [[ $objfile == "" ]] ; then
118a5dc8300SSasha Levin			echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2
119a5dc8300SSasha Levin			return
120a5dc8300SSasha Levin		fi
1213af8acf6SSchspa Shi		if [[ $aarray_support == true ]]; then
122310c6dd0SKonstantin Khlebnikov			modcache[$module]=$objfile
123310c6dd0SKonstantin Khlebnikov		fi
1243af8acf6SSchspa Shi	fi
125310c6dd0SKonstantin Khlebnikov
126e260fe01SRobert Jarzmik	# Remove the englobing parenthesis
127e260fe01SRobert Jarzmik	symbol=${symbol#\(}
128e260fe01SRobert Jarzmik	symbol=${symbol%\)}
129dbd1abb2SSasha Levin
1301d6693fbSKonstantin Khlebnikov	# Strip segment
1311d6693fbSKonstantin Khlebnikov	local segment
1321d6693fbSKonstantin Khlebnikov	if [[ $symbol == *:* ]] ; then
1331d6693fbSKonstantin Khlebnikov		segment=${symbol%%:*}:
1341d6693fbSKonstantin Khlebnikov		symbol=${symbol#*:}
1351d6693fbSKonstantin Khlebnikov	fi
1361d6693fbSKonstantin Khlebnikov
137dbd1abb2SSasha Levin	# Strip the symbol name so that we could look it up
138dbd1abb2SSasha Levin	local name=${symbol%+*}
139dbd1abb2SSasha Levin
140dbd1abb2SSasha Levin	# Use 'nm vmlinux' to figure out the base address of said symbol.
141dbd1abb2SSasha Levin	# It's actually faster to call it every time than to load it
142dbd1abb2SSasha Levin	# all into bash.
1433af8acf6SSchspa Shi	if [[ $aarray_support == true && "${cache[$module,$name]+isset}" == "isset" ]]; then
144310c6dd0SKonstantin Khlebnikov		local base_addr=${cache[$module,$name]}
145dbd1abb2SSasha Levin	else
1465bf0f3bcSStephen Boyd		local base_addr=$(nm "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}')
147f643b9eeSKonstantin Khlebnikov		if [[ $base_addr == "" ]] ; then
148f643b9eeSKonstantin Khlebnikov			# address not found
149f643b9eeSKonstantin Khlebnikov			return
150f643b9eeSKonstantin Khlebnikov		fi
1513af8acf6SSchspa Shi		if [[ $aarray_support == true ]]; then
152310c6dd0SKonstantin Khlebnikov			cache[$module,$name]="$base_addr"
153dbd1abb2SSasha Levin		fi
1543af8acf6SSchspa Shi	fi
155dbd1abb2SSasha Levin	# Let's start doing the math to get the exact address into the
156dbd1abb2SSasha Levin	# symbol. First, strip out the symbol total length.
157dbd1abb2SSasha Levin	local expr=${symbol%/*}
158dbd1abb2SSasha Levin
159dbd1abb2SSasha Levin	# Now, replace the symbol name with the base address we found
160dbd1abb2SSasha Levin	# before.
161dbd1abb2SSasha Levin	expr=${expr/$name/0x$base_addr}
162dbd1abb2SSasha Levin
163dbd1abb2SSasha Levin	# Evaluate it to find the actual address
164dbd1abb2SSasha Levin	expr=$((expr))
165dbd1abb2SSasha Levin	local address=$(printf "%x\n" "$expr")
166dbd1abb2SSasha Levin
167dbd1abb2SSasha Levin	# Pass it to addr2line to get filename and line number
168dbd1abb2SSasha Levin	# Could get more than one result
1693af8acf6SSchspa Shi	if [[ $aarray_support == true && "${cache[$module,$address]+isset}" == "isset" ]]; then
170310c6dd0SKonstantin Khlebnikov		local code=${cache[$module,$address]}
171dbd1abb2SSasha Levin	else
1725bf0f3bcSStephen Boyd		local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address" 2>/dev/null)
1733af8acf6SSchspa Shi		if [[ $aarray_support == true ]]; then
174310c6dd0SKonstantin Khlebnikov			cache[$module,$address]=$code
175dbd1abb2SSasha Levin		fi
1763af8acf6SSchspa Shi	fi
177dbd1abb2SSasha Levin
178dbd1abb2SSasha Levin	# addr2line doesn't return a proper error code if it fails, so
179dbd1abb2SSasha Levin	# we detect it using the value it prints so that we could preserve
180dbd1abb2SSasha Levin	# the offset/size into the function and bail out
181dbd1abb2SSasha Levin	if [[ $code == "??:0" ]]; then
182dbd1abb2SSasha Levin		return
183dbd1abb2SSasha Levin	fi
184dbd1abb2SSasha Levin
185d178770dSPi-Hsun Shih	# Strip out the base of the path on each line
186d178770dSPi-Hsun Shih	code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code")
187dbd1abb2SSasha Levin
188dbd1abb2SSasha Levin	# In the case of inlines, move everything to same line
189dbd1abb2SSasha Levin	code=${code//$'\n'/' '}
190dbd1abb2SSasha Levin
19199115db4SMiguel Ojeda	# Demangle if the name looks like a Rust symbol and if
19299115db4SMiguel Ojeda	# we got a Rust demangler
19399115db4SMiguel Ojeda	if [[ $name =~ ^_R && $cppfilt != "" ]] ; then
19499115db4SMiguel Ojeda		name=$("$cppfilt" "$cppfilt_opts" "$name")
19599115db4SMiguel Ojeda	fi
19699115db4SMiguel Ojeda
197dbd1abb2SSasha Levin	# Replace old address with pretty line numbers
1981d6693fbSKonstantin Khlebnikov	symbol="$segment$name ($code)"
199dbd1abb2SSasha Levin}
200dbd1abb2SSasha Levin
20126681eb3SStephen Boyddebuginfod_get_vmlinux() {
20226681eb3SStephen Boyd	local vmlinux_buildid=${1##* }
20326681eb3SStephen Boyd
20426681eb3SStephen Boyd	if [[ $vmlinux != "" ]]; then
20526681eb3SStephen Boyd		return
20626681eb3SStephen Boyd	fi
20726681eb3SStephen Boyd
20826681eb3SStephen Boyd	if [[ $vmlinux_buildid =~ ^[0-9a-f]+ ]]; then
20926681eb3SStephen Boyd		vmlinux=$(debuginfod-find debuginfo $vmlinux_buildid)
21026681eb3SStephen Boyd		if [[ $? -ne 0 ]] ; then
21126681eb3SStephen Boyd			echo "ERROR! vmlinux image not found via debuginfod-find" >&2
21226681eb3SStephen Boyd			usage
21326681eb3SStephen Boyd			exit 2
21426681eb3SStephen Boyd		fi
21526681eb3SStephen Boyd		return
21626681eb3SStephen Boyd	fi
21726681eb3SStephen Boyd	echo "ERROR! Build ID for vmlinux not found. Try passing -r or specifying vmlinux" >&2
21826681eb3SStephen Boyd	usage
21926681eb3SStephen Boyd	exit 2
22026681eb3SStephen Boyd}
22126681eb3SStephen Boyd
222dbd1abb2SSasha Levindecode_code() {
223dbd1abb2SSasha Levin	local scripts=`dirname "${BASH_SOURCE[0]}"`
224dbd1abb2SSasha Levin
225dbd1abb2SSasha Levin	echo "$1" | $scripts/decodecode
226dbd1abb2SSasha Levin}
227dbd1abb2SSasha Levin
228dbd1abb2SSasha Levinhandle_line() {
22926681eb3SStephen Boyd	if [[ $basepath == "auto" && $vmlinux != "" ]] ; then
23026681eb3SStephen Boyd		module=""
23126681eb3SStephen Boyd		symbol="kernel_init+0x0/0x0"
23226681eb3SStephen Boyd		parse_symbol
23326681eb3SStephen Boyd		basepath=${symbol#kernel_init (}
23426681eb3SStephen Boyd		basepath=${basepath%/init/main.c:*)}
23526681eb3SStephen Boyd	fi
23626681eb3SStephen Boyd
237dbd1abb2SSasha Levin	local words
238dbd1abb2SSasha Levin
239dbd1abb2SSasha Levin	# Tokenize
240dbd1abb2SSasha Levin	read -a words <<<"$1"
241dbd1abb2SSasha Levin
242dbd1abb2SSasha Levin	# Remove hex numbers. Do it ourselves until it happens in the
243dbd1abb2SSasha Levin	# kernel
244dbd1abb2SSasha Levin
245dbd1abb2SSasha Levin	# We need to know the index of the last element before we
246dbd1abb2SSasha Levin	# remove elements because arrays are sparse
247dbd1abb2SSasha Levin	local last=$(( ${#words[@]} - 1 ))
248dbd1abb2SSasha Levin
249dbd1abb2SSasha Levin	for i in "${!words[@]}"; do
250dbd1abb2SSasha Levin		# Remove the address
251dbd1abb2SSasha Levin		if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then
252dbd1abb2SSasha Levin			unset words[$i]
253dbd1abb2SSasha Levin		fi
254dbd1abb2SSasha Levin
255dbd1abb2SSasha Levin		# Format timestamps with tabs
256dbd1abb2SSasha Levin		if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then
257dbd1abb2SSasha Levin			unset words[$i]
258dbd1abb2SSasha Levin			words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}")
259dbd1abb2SSasha Levin		fi
260dbd1abb2SSasha Levin	done
261dbd1abb2SSasha Levin
26226681eb3SStephen Boyd	if [[ ${words[$last]} =~ ^[0-9a-f]+\] ]]; then
26326681eb3SStephen Boyd		words[$last-1]="${words[$last-1]} ${words[$last]}"
26426681eb3SStephen Boyd		unset words[$last]
26526681eb3SStephen Boyd		last=$(( $last - 1 ))
26626681eb3SStephen Boyd	fi
26726681eb3SStephen Boyd
268310c6dd0SKonstantin Khlebnikov	if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then
269310c6dd0SKonstantin Khlebnikov		module=${words[$last]}
270310c6dd0SKonstantin Khlebnikov		module=${module#\[}
271310c6dd0SKonstantin Khlebnikov		module=${module%\]}
27226681eb3SStephen Boyd		modbuildid=${module#* }
27326681eb3SStephen Boyd		module=${module% *}
27426681eb3SStephen Boyd		if [[ $modbuildid == $module ]]; then
27526681eb3SStephen Boyd			modbuildid=
27626681eb3SStephen Boyd		fi
277310c6dd0SKonstantin Khlebnikov		symbol=${words[$last-1]}
278310c6dd0SKonstantin Khlebnikov		unset words[$last-1]
279310c6dd0SKonstantin Khlebnikov	else
280dbd1abb2SSasha Levin		# The symbol is the last element, process it
281dbd1abb2SSasha Levin		symbol=${words[$last]}
282310c6dd0SKonstantin Khlebnikov		module=
28326681eb3SStephen Boyd		modbuildid=
284310c6dd0SKonstantin Khlebnikov	fi
285310c6dd0SKonstantin Khlebnikov
286dbd1abb2SSasha Levin	unset words[$last]
287dbd1abb2SSasha Levin	parse_symbol # modifies $symbol
288dbd1abb2SSasha Levin
289dbd1abb2SSasha Levin	# Add up the line number to the symbol
290310c6dd0SKonstantin Khlebnikov	echo "${words[@]}" "$symbol $module"
291dbd1abb2SSasha Levin}
292dbd1abb2SSasha Levin
293dbd1abb2SSasha Levinwhile read line; do
294*436efd9eSBjorn Andersson	# Strip unexpected carriage return at end of line
295*436efd9eSBjorn Andersson	line=${line%$'\r'}
296*436efd9eSBjorn Andersson
297dbd1abb2SSasha Levin	# Let's see if we have an address in the line
29853938ee4SJosh Poimboeuf	if [[ $line =~ \[\<([^]]+)\>\] ]] ||
29953938ee4SJosh Poimboeuf	   [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then
300dbd1abb2SSasha Levin		# Translate address to line numbers
301dbd1abb2SSasha Levin		handle_line "$line"
302dbd1abb2SSasha Levin	# Is it a code line?
303dbd1abb2SSasha Levin	elif [[ $line == *Code:* ]]; then
304dbd1abb2SSasha Levin		decode_code "$line"
30526681eb3SStephen Boyd	# Is it a version line?
30626681eb3SStephen Boyd	elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then
30726681eb3SStephen Boyd		debuginfod_get_vmlinux "$line"
308dbd1abb2SSasha Levin	else
309dbd1abb2SSasha Levin		# Nothing special in this line, show it as is
310dbd1abb2SSasha Levin		echo "$line"
311dbd1abb2SSasha Levin	fi
312dbd1abb2SSasha Levindone
313