1076979eeSKent Overstreet#!/usr/bin/env 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:"
8a6d05e82SLuca Ceresoli	echo "	$0 -r <release>"
9a6d05e82SLuca Ceresoli	echo "	$0 [<vmlinux> [<base_path>|auto [<modules_path>]]]"
107e108359SLuca Ceresoli	echo "	$0 -h"
1126681eb3SStephen Boyd}
12dbd1abb2SSasha Levin
1399115db4SMiguel Ojeda# Try to find a Rust demangler
1499115db4SMiguel Ojedaif type llvm-cxxfilt >/dev/null 2>&1 ; then
1599115db4SMiguel Ojeda	cppfilt=llvm-cxxfilt
1699115db4SMiguel Ojedaelif type c++filt >/dev/null 2>&1 ; then
1799115db4SMiguel Ojeda	cppfilt=c++filt
1899115db4SMiguel Ojeda	cppfilt_opts=-i
1999115db4SMiguel Ojedafi
2099115db4SMiguel Ojeda
21efbd6398SCarlos LlamasUTIL_SUFFIX=
22efbd6398SCarlos Llamasif [[ -z ${LLVM:-} ]]; then
23efbd6398SCarlos Llamas	UTIL_PREFIX=${CROSS_COMPILE:-}
24efbd6398SCarlos Llamaselse
25efbd6398SCarlos Llamas	UTIL_PREFIX=llvm-
26efbd6398SCarlos Llamas	if [[ ${LLVM} == */ ]]; then
27efbd6398SCarlos Llamas		UTIL_PREFIX=${LLVM}${UTIL_PREFIX}
28efbd6398SCarlos Llamas	elif [[ ${LLVM} == -* ]]; then
29efbd6398SCarlos Llamas		UTIL_SUFFIX=${LLVM}
30efbd6398SCarlos Llamas	fi
31efbd6398SCarlos Llamasfi
32efbd6398SCarlos Llamas
33efbd6398SCarlos LlamasREADELF=${UTIL_PREFIX}readelf${UTIL_SUFFIX}
34efbd6398SCarlos LlamasADDR2LINE=${UTIL_PREFIX}addr2line${UTIL_SUFFIX}
35b41838feSXiong NandiNM=${UTIL_PREFIX}nm${UTIL_SUFFIX}
36efbd6398SCarlos Llamas
377e108359SLuca Ceresoliif [[ $1 == "-h" ]] ; then
387e108359SLuca Ceresoli	usage
397e108359SLuca Ceresoli	exit 0
407e108359SLuca Ceresolielif [[ $1 == "-r" ]] ; then
41f90dde44SKonstantin Khlebnikov	vmlinux=""
42f90dde44SKonstantin Khlebnikov	basepath="auto"
43f90dde44SKonstantin Khlebnikov	modpath=""
44f90dde44SKonstantin Khlebnikov	release=$2
45f90dde44SKonstantin Khlebnikov
46f90dde44SKonstantin Khlebnikov	for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do
47f90dde44SKonstantin Khlebnikov		if [ -e "$fn" ] ; then
48f90dde44SKonstantin Khlebnikov			vmlinux=$fn
49f90dde44SKonstantin Khlebnikov			break
50f90dde44SKonstantin Khlebnikov		fi
51f90dde44SKonstantin Khlebnikov	done
52f90dde44SKonstantin Khlebnikov
53f90dde44SKonstantin Khlebnikov	if [[ $vmlinux == "" ]] ; then
54f90dde44SKonstantin Khlebnikov		echo "ERROR! vmlinux image for release $release is not found" >&2
5526681eb3SStephen Boyd		usage
56f90dde44SKonstantin Khlebnikov		exit 2
57f90dde44SKonstantin Khlebnikov	fi
58f90dde44SKonstantin Khlebnikovelse
59dbd1abb2SSasha Levin	vmlinux=$1
60ecda6e27SKonstantin Khlebnikov	basepath=${2-auto}
61310c6dd0SKonstantin Khlebnikov	modpath=$3
62431151b6SKonstantin Khlebnikov	release=""
6326681eb3SStephen Boyd	debuginfod=
6426681eb3SStephen Boyd
6526681eb3SStephen Boyd	# Can we use debuginfod-find?
6626681eb3SStephen Boyd	if type debuginfod-find >/dev/null 2>&1 ; then
6726681eb3SStephen Boyd		debuginfod=${1-only}
6826681eb3SStephen Boyd	fi
6926681eb3SStephen Boyd
7026681eb3SStephen Boyd	if [[ $vmlinux == "" && -z $debuginfod ]] ; then
7126681eb3SStephen Boyd		echo "ERROR! vmlinux image must be specified" >&2
7226681eb3SStephen Boyd		usage
7326681eb3SStephen Boyd		exit 1
7426681eb3SStephen Boyd	fi
75f90dde44SKonstantin Khlebnikovfi
76431151b6SKonstantin Khlebnikov
773af8acf6SSchspa Shideclare aarray_support=true
783af8acf6SSchspa Shideclare -A cache 2>/dev/null
793af8acf6SSchspa Shiif [[ $? != 0 ]]; then
803af8acf6SSchspa Shi	aarray_support=false
813af8acf6SSchspa Shielse
82310c6dd0SKonstantin Khlebnikov	declare -A modcache
833af8acf6SSchspa Shifi
84dbd1abb2SSasha Levin
85431151b6SKonstantin Khlebnikovfind_module() {
8626681eb3SStephen Boyd	if [[ -n $debuginfod ]] ; then
8726681eb3SStephen Boyd		if [[ -n $modbuildid ]] ; then
8826681eb3SStephen Boyd			debuginfod-find debuginfo $modbuildid && return
8926681eb3SStephen Boyd		fi
9026681eb3SStephen Boyd
9126681eb3SStephen Boyd		# Only using debuginfod so don't try to find vmlinux module path
9226681eb3SStephen Boyd		if [[ $debuginfod == "only" ]] ; then
9326681eb3SStephen Boyd			return
9426681eb3SStephen Boyd		fi
9526681eb3SStephen Boyd	fi
9626681eb3SStephen Boyd
970f69dc29SLuca Ceresoli	if [ -z $release ] ; then
980f69dc29SLuca Ceresoli		release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" 2>/dev/null | sed -n 's/\$1 = "\(.*\)".*/\1/p')
990f69dc29SLuca Ceresoli	fi
1000f69dc29SLuca Ceresoli	if [ -n "${release}" ] ; then
1010f69dc29SLuca Ceresoli		release_dirs="/usr/lib/debug/lib/modules/$release /lib/modules/$release"
1020f69dc29SLuca Ceresoli	fi
1030f69dc29SLuca Ceresoli
1040f69dc29SLuca Ceresoli	found_without_debug_info=false
1050f69dc29SLuca Ceresoli	for dir in "$modpath" "$(dirname "$vmlinux")" ${release_dirs}; do
1060f69dc29SLuca Ceresoli		if [ -n "${dir}" ] && [ -e "${dir}" ]; then
1070f69dc29SLuca Ceresoli			for fn in $(find "$dir" -name "${module//_/[-_]}.ko*") ; do
108efbd6398SCarlos Llamas				if ${READELF} -WS "$fn" | grep -qwF .debug_line ; then
109431151b6SKonstantin Khlebnikov					echo $fn
110431151b6SKonstantin Khlebnikov					return
111431151b6SKonstantin Khlebnikov				fi
1120f69dc29SLuca Ceresoli				found_without_debug_info=true
113431151b6SKonstantin Khlebnikov			done
114431151b6SKonstantin Khlebnikov		fi
115431151b6SKonstantin Khlebnikov	done
116431151b6SKonstantin Khlebnikov
1170f69dc29SLuca Ceresoli	if [[ ${found_without_debug_info} == true ]]; then
1180f69dc29SLuca Ceresoli		echo "WARNING! No debugging info in module ${module}, rebuild with DEBUG_KERNEL and DEBUG_INFO" >&2
1190f69dc29SLuca Ceresoli	else
1200f69dc29SLuca Ceresoli		echo "WARNING! Cannot find .ko for module ${module}, please pass a valid module path" >&2
1210f69dc29SLuca Ceresoli	fi
1220f69dc29SLuca Ceresoli
123431151b6SKonstantin Khlebnikov	return 1
124431151b6SKonstantin Khlebnikov}
125431151b6SKonstantin Khlebnikov
126dbd1abb2SSasha Levinparse_symbol() {
127dbd1abb2SSasha Levin	# The structure of symbol at this point is:
128e260fe01SRobert Jarzmik	#   ([name]+[offset]/[total length])
129dbd1abb2SSasha Levin	#
130dbd1abb2SSasha Levin	# For example:
131dbd1abb2SSasha Levin	#   do_basic_setup+0x9c/0xbf
132dbd1abb2SSasha Levin
133310c6dd0SKonstantin Khlebnikov	if [[ $module == "" ]] ; then
134310c6dd0SKonstantin Khlebnikov		local objfile=$vmlinux
1353af8acf6SSchspa Shi	elif [[ $aarray_support == true && "${modcache[$module]+isset}" == "isset" ]]; then
136310c6dd0SKonstantin Khlebnikov		local objfile=${modcache[$module]}
137310c6dd0SKonstantin Khlebnikov	else
138431151b6SKonstantin Khlebnikov		local objfile=$(find_module)
139431151b6SKonstantin Khlebnikov		if [[ $objfile == "" ]] ; then
140a5dc8300SSasha Levin			return
141a5dc8300SSasha Levin		fi
1423af8acf6SSchspa Shi		if [[ $aarray_support == true ]]; then
143310c6dd0SKonstantin Khlebnikov			modcache[$module]=$objfile
144310c6dd0SKonstantin Khlebnikov		fi
1453af8acf6SSchspa Shi	fi
146310c6dd0SKonstantin Khlebnikov
147e260fe01SRobert Jarzmik	# Remove the englobing parenthesis
148e260fe01SRobert Jarzmik	symbol=${symbol#\(}
149e260fe01SRobert Jarzmik	symbol=${symbol%\)}
150dbd1abb2SSasha Levin
1511d6693fbSKonstantin Khlebnikov	# Strip segment
1521d6693fbSKonstantin Khlebnikov	local segment
1531d6693fbSKonstantin Khlebnikov	if [[ $symbol == *:* ]] ; then
1541d6693fbSKonstantin Khlebnikov		segment=${symbol%%:*}:
1551d6693fbSKonstantin Khlebnikov		symbol=${symbol#*:}
1561d6693fbSKonstantin Khlebnikov	fi
1571d6693fbSKonstantin Khlebnikov
158dbd1abb2SSasha Levin	# Strip the symbol name so that we could look it up
159dbd1abb2SSasha Levin	local name=${symbol%+*}
160dbd1abb2SSasha Levin
161dbd1abb2SSasha Levin	# Use 'nm vmlinux' to figure out the base address of said symbol.
162dbd1abb2SSasha Levin	# It's actually faster to call it every time than to load it
163dbd1abb2SSasha Levin	# all into bash.
1643af8acf6SSchspa Shi	if [[ $aarray_support == true && "${cache[$module,$name]+isset}" == "isset" ]]; then
165310c6dd0SKonstantin Khlebnikov		local base_addr=${cache[$module,$name]}
166dbd1abb2SSasha Levin	else
167b41838feSXiong Nandi		local base_addr=$(${NM} "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}')
168f643b9eeSKonstantin Khlebnikov		if [[ $base_addr == "" ]] ; then
169f643b9eeSKonstantin Khlebnikov			# address not found
170f643b9eeSKonstantin Khlebnikov			return
171f643b9eeSKonstantin Khlebnikov		fi
1723af8acf6SSchspa Shi		if [[ $aarray_support == true ]]; then
173310c6dd0SKonstantin Khlebnikov			cache[$module,$name]="$base_addr"
174dbd1abb2SSasha Levin		fi
1753af8acf6SSchspa Shi	fi
176dbd1abb2SSasha Levin	# Let's start doing the math to get the exact address into the
177dbd1abb2SSasha Levin	# symbol. First, strip out the symbol total length.
178dbd1abb2SSasha Levin	local expr=${symbol%/*}
179dbd1abb2SSasha Levin
180dbd1abb2SSasha Levin	# Now, replace the symbol name with the base address we found
181dbd1abb2SSasha Levin	# before.
182dbd1abb2SSasha Levin	expr=${expr/$name/0x$base_addr}
183dbd1abb2SSasha Levin
184dbd1abb2SSasha Levin	# Evaluate it to find the actual address
185dbd1abb2SSasha Levin	expr=$((expr))
186dbd1abb2SSasha Levin	local address=$(printf "%x\n" "$expr")
187dbd1abb2SSasha Levin
188dbd1abb2SSasha Levin	# Pass it to addr2line to get filename and line number
189dbd1abb2SSasha Levin	# Could get more than one result
1903af8acf6SSchspa Shi	if [[ $aarray_support == true && "${cache[$module,$address]+isset}" == "isset" ]]; then
191310c6dd0SKonstantin Khlebnikov		local code=${cache[$module,$address]}
192dbd1abb2SSasha Levin	else
193efbd6398SCarlos Llamas		local code=$(${ADDR2LINE} -i -e "$objfile" "$address" 2>/dev/null)
1943af8acf6SSchspa Shi		if [[ $aarray_support == true ]]; then
195310c6dd0SKonstantin Khlebnikov			cache[$module,$address]=$code
196dbd1abb2SSasha Levin		fi
1973af8acf6SSchspa Shi	fi
198dbd1abb2SSasha Levin
199dbd1abb2SSasha Levin	# addr2line doesn't return a proper error code if it fails, so
200dbd1abb2SSasha Levin	# we detect it using the value it prints so that we could preserve
201dbd1abb2SSasha Levin	# the offset/size into the function and bail out
202dbd1abb2SSasha Levin	if [[ $code == "??:0" ]]; then
203dbd1abb2SSasha Levin		return
204dbd1abb2SSasha Levin	fi
205dbd1abb2SSasha Levin
206d178770dSPi-Hsun Shih	# Strip out the base of the path on each line
207d178770dSPi-Hsun Shih	code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code")
208dbd1abb2SSasha Levin
209dbd1abb2SSasha Levin	# In the case of inlines, move everything to same line
210dbd1abb2SSasha Levin	code=${code//$'\n'/' '}
211dbd1abb2SSasha Levin
21299115db4SMiguel Ojeda	# Demangle if the name looks like a Rust symbol and if
21399115db4SMiguel Ojeda	# we got a Rust demangler
21499115db4SMiguel Ojeda	if [[ $name =~ ^_R && $cppfilt != "" ]] ; then
21599115db4SMiguel Ojeda		name=$("$cppfilt" "$cppfilt_opts" "$name")
21699115db4SMiguel Ojeda	fi
21799115db4SMiguel Ojeda
218dbd1abb2SSasha Levin	# Replace old address with pretty line numbers
2191d6693fbSKonstantin Khlebnikov	symbol="$segment$name ($code)"
220dbd1abb2SSasha Levin}
221dbd1abb2SSasha Levin
22226681eb3SStephen Boyddebuginfod_get_vmlinux() {
22326681eb3SStephen Boyd	local vmlinux_buildid=${1##* }
22426681eb3SStephen Boyd
22526681eb3SStephen Boyd	if [[ $vmlinux != "" ]]; then
22626681eb3SStephen Boyd		return
22726681eb3SStephen Boyd	fi
22826681eb3SStephen Boyd
22926681eb3SStephen Boyd	if [[ $vmlinux_buildid =~ ^[0-9a-f]+ ]]; then
23026681eb3SStephen Boyd		vmlinux=$(debuginfod-find debuginfo $vmlinux_buildid)
23126681eb3SStephen Boyd		if [[ $? -ne 0 ]] ; then
23226681eb3SStephen Boyd			echo "ERROR! vmlinux image not found via debuginfod-find" >&2
23326681eb3SStephen Boyd			usage
23426681eb3SStephen Boyd			exit 2
23526681eb3SStephen Boyd		fi
23626681eb3SStephen Boyd		return
23726681eb3SStephen Boyd	fi
23826681eb3SStephen Boyd	echo "ERROR! Build ID for vmlinux not found. Try passing -r or specifying vmlinux" >&2
23926681eb3SStephen Boyd	usage
24026681eb3SStephen Boyd	exit 2
24126681eb3SStephen Boyd}
24226681eb3SStephen Boyd
243dbd1abb2SSasha Levindecode_code() {
244dbd1abb2SSasha Levin	local scripts=`dirname "${BASH_SOURCE[0]}"`
245dbd1abb2SSasha Levin
246dbd1abb2SSasha Levin	echo "$1" | $scripts/decodecode
247dbd1abb2SSasha Levin}
248dbd1abb2SSasha Levin
249dbd1abb2SSasha Levinhandle_line() {
25026681eb3SStephen Boyd	if [[ $basepath == "auto" && $vmlinux != "" ]] ; then
25126681eb3SStephen Boyd		module=""
25226681eb3SStephen Boyd		symbol="kernel_init+0x0/0x0"
25326681eb3SStephen Boyd		parse_symbol
25426681eb3SStephen Boyd		basepath=${symbol#kernel_init (}
25526681eb3SStephen Boyd		basepath=${basepath%/init/main.c:*)}
25626681eb3SStephen Boyd	fi
25726681eb3SStephen Boyd
258dbd1abb2SSasha Levin	local words
259dbd1abb2SSasha Levin
260dbd1abb2SSasha Levin	# Tokenize
261dbd1abb2SSasha Levin	read -a words <<<"$1"
262dbd1abb2SSasha Levin
263dbd1abb2SSasha Levin	# Remove hex numbers. Do it ourselves until it happens in the
264dbd1abb2SSasha Levin	# kernel
265dbd1abb2SSasha Levin
266dbd1abb2SSasha Levin	# We need to know the index of the last element before we
267dbd1abb2SSasha Levin	# remove elements because arrays are sparse
268dbd1abb2SSasha Levin	local last=$(( ${#words[@]} - 1 ))
269dbd1abb2SSasha Levin
270dbd1abb2SSasha Levin	for i in "${!words[@]}"; do
271dbd1abb2SSasha Levin		# Remove the address
272dbd1abb2SSasha Levin		if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then
273dbd1abb2SSasha Levin			unset words[$i]
274dbd1abb2SSasha Levin		fi
275dbd1abb2SSasha Levin
276dbd1abb2SSasha Levin		# Format timestamps with tabs
277dbd1abb2SSasha Levin		if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then
278dbd1abb2SSasha Levin			unset words[$i]
279dbd1abb2SSasha Levin			words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}")
280dbd1abb2SSasha Levin		fi
281dbd1abb2SSasha Levin	done
282dbd1abb2SSasha Levin
28326681eb3SStephen Boyd	if [[ ${words[$last]} =~ ^[0-9a-f]+\] ]]; then
28426681eb3SStephen Boyd		words[$last-1]="${words[$last-1]} ${words[$last]}"
28526681eb3SStephen Boyd		unset words[$last]
28626681eb3SStephen Boyd		last=$(( $last - 1 ))
28726681eb3SStephen Boyd	fi
28826681eb3SStephen Boyd
289*2bff77c6SLuca Ceresoli	# Extract info after the symbol if present. E.g.:
290*2bff77c6SLuca Ceresoli	# func_name+0x54/0x80 (P)
291*2bff77c6SLuca Ceresoli	#                     ^^^
292*2bff77c6SLuca Ceresoli	# The regex assumes only uppercase letters will be used. To be
293*2bff77c6SLuca Ceresoli	# extended if needed.
294*2bff77c6SLuca Ceresoli	local info_str=""
295*2bff77c6SLuca Ceresoli	if [[ ${words[$last]} =~ \([A-Z]*\) ]]; then
296*2bff77c6SLuca Ceresoli		info_str=${words[$last]}
297*2bff77c6SLuca Ceresoli		unset words[$last]
298*2bff77c6SLuca Ceresoli		last=$(( $last - 1 ))
299*2bff77c6SLuca Ceresoli	fi
300*2bff77c6SLuca Ceresoli
301310c6dd0SKonstantin Khlebnikov	if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then
302310c6dd0SKonstantin Khlebnikov		module=${words[$last]}
30378efbfb5SXiong Nandi		# some traces format is "(%pS)", which like "(foo+0x0/0x1 [bar])"
30478efbfb5SXiong Nandi		# so $module may like "[bar])". Strip the right parenthesis firstly
30578efbfb5SXiong Nandi		module=${module%\)}
306310c6dd0SKonstantin Khlebnikov		module=${module#\[}
307310c6dd0SKonstantin Khlebnikov		module=${module%\]}
30826681eb3SStephen Boyd		modbuildid=${module#* }
30926681eb3SStephen Boyd		module=${module% *}
31026681eb3SStephen Boyd		if [[ $modbuildid == $module ]]; then
31126681eb3SStephen Boyd			modbuildid=
31226681eb3SStephen Boyd		fi
313310c6dd0SKonstantin Khlebnikov		symbol=${words[$last-1]}
314310c6dd0SKonstantin Khlebnikov		unset words[$last-1]
315310c6dd0SKonstantin Khlebnikov	else
316dbd1abb2SSasha Levin		# The symbol is the last element, process it
317dbd1abb2SSasha Levin		symbol=${words[$last]}
318310c6dd0SKonstantin Khlebnikov		module=
31926681eb3SStephen Boyd		modbuildid=
320310c6dd0SKonstantin Khlebnikov	fi
321310c6dd0SKonstantin Khlebnikov
322dbd1abb2SSasha Levin	unset words[$last]
323dbd1abb2SSasha Levin	parse_symbol # modifies $symbol
324dbd1abb2SSasha Levin
325dbd1abb2SSasha Levin	# Add up the line number to the symbol
3261bb5d660SBreno Leitao	if [[ -z ${module} ]]
3271bb5d660SBreno Leitao	then
328*2bff77c6SLuca Ceresoli		echo "${words[@]}" "$symbol ${info_str}"
3291bb5d660SBreno Leitao	else
330*2bff77c6SLuca Ceresoli		echo "${words[@]}" "$symbol $module ${info_str}"
3311bb5d660SBreno Leitao	fi
332dbd1abb2SSasha Levin}
333dbd1abb2SSasha Levin
334dbd1abb2SSasha Levinwhile read line; do
335436efd9eSBjorn Andersson	# Strip unexpected carriage return at end of line
336436efd9eSBjorn Andersson	line=${line%$'\r'}
337436efd9eSBjorn Andersson
338dbd1abb2SSasha Levin	# Let's see if we have an address in the line
33953938ee4SJosh Poimboeuf	if [[ $line =~ \[\<([^]]+)\>\] ]] ||
34053938ee4SJosh Poimboeuf	   [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then
341dbd1abb2SSasha Levin		# Translate address to line numbers
342dbd1abb2SSasha Levin		handle_line "$line"
343dbd1abb2SSasha Levin	# Is it a code line?
344dbd1abb2SSasha Levin	elif [[ $line == *Code:* ]]; then
345dbd1abb2SSasha Levin		decode_code "$line"
34626681eb3SStephen Boyd	# Is it a version line?
34726681eb3SStephen Boyd	elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then
34826681eb3SStephen Boyd		debuginfod_get_vmlinux "$line"
349dbd1abb2SSasha Levin	else
350dbd1abb2SSasha Levin		# Nothing special in this line, show it as is
351dbd1abb2SSasha Levin		echo "$line"
352dbd1abb2SSasha Levin	fi
353dbd1abb2SSasha Levindone
354