xref: /linux/scripts/decode_stacktrace.sh (revision e20043b4765cdf7ec8e963d706bb91469cba8cb8)
1#!/usr/bin/env bash
2# SPDX-License-Identifier: GPL-2.0
3# (c) 2014, Sasha Levin <sasha.levin@oracle.com>
4#set -x
5
6usage() {
7	echo "Usage:"
8	echo "	$0 [-R] -r <release>"
9	echo "	$0 [-R] [<vmlinux> [<base_path>|auto [<modules_path>]]]"
10	echo "	$0 -h"
11	echo "Options:"
12	echo "  -R: decode return address instead of caller address."
13}
14
15# Try to find a Rust demangler
16if type llvm-cxxfilt >/dev/null 2>&1 ; then
17	cppfilt=llvm-cxxfilt
18elif type c++filt >/dev/null 2>&1 ; then
19	cppfilt=c++filt
20	cppfilt_opts=-i
21fi
22
23UTIL_SUFFIX=
24if [[ -z ${LLVM:-} ]]; then
25	UTIL_PREFIX=${CROSS_COMPILE:-}
26else
27	UTIL_PREFIX=llvm-
28	if [[ ${LLVM} == */ ]]; then
29		UTIL_PREFIX=${LLVM}${UTIL_PREFIX}
30	elif [[ ${LLVM} == -* ]]; then
31		UTIL_SUFFIX=${LLVM}
32	fi
33fi
34
35READELF=${UTIL_PREFIX}readelf${UTIL_SUFFIX}
36ADDR2LINE=${UTIL_PREFIX}addr2line${UTIL_SUFFIX}
37NM=${UTIL_PREFIX}nm${UTIL_SUFFIX}
38decode_retaddr=false
39
40if [[ $1 == "-h" ]] ; then
41	usage
42	exit 0
43elif [[ $1 == "-R" ]] ; then
44	decode_retaddr=true
45	shift 1
46fi
47
48if [[ $1 == "-r" ]] ; then
49	vmlinux=""
50	basepath="auto"
51	modpath=""
52	release=$2
53
54	for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do
55		if [ -e "$fn" ] ; then
56			vmlinux=$fn
57			break
58		fi
59	done
60
61	if [[ $vmlinux == "" ]] ; then
62		echo "ERROR! vmlinux image for release $release is not found" >&2
63		usage
64		exit 2
65	fi
66else
67	vmlinux=$1
68	basepath=${2-auto}
69	modpath=$3
70	release=""
71	debuginfod=
72
73	# Can we use debuginfod-find?
74	if type debuginfod-find >/dev/null 2>&1 ; then
75		debuginfod=${1-only}
76	fi
77
78	if [[ $vmlinux == "" && -z $debuginfod ]] ; then
79		echo "ERROR! vmlinux image must be specified" >&2
80		usage
81		exit 1
82	fi
83fi
84
85declare aarray_support=true
86declare -A cache 2>/dev/null
87if [[ $? != 0 ]]; then
88	aarray_support=false
89else
90	declare -A modcache
91fi
92
93find_module() {
94	if [[ -n $debuginfod ]] ; then
95		if [[ -n $modbuildid ]] ; then
96			debuginfod-find debuginfo $modbuildid && return
97		fi
98
99		# Only using debuginfod so don't try to find vmlinux module path
100		if [[ $debuginfod == "only" ]] ; then
101			return
102		fi
103	fi
104
105	if [ -z $release ] ; then
106		release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" 2>/dev/null | sed -n 's/\$1 = "\(.*\)".*/\1/p')
107	fi
108	if [ -n "${release}" ] ; then
109		release_dirs="/usr/lib/debug/lib/modules/$release /lib/modules/$release"
110	fi
111
112	found_without_debug_info=false
113	for dir in "$modpath" "$(dirname "$vmlinux")" ${release_dirs}; do
114		if [ -n "${dir}" ] && [ -e "${dir}" ]; then
115			for fn in $(find "$dir" -name "${module//_/[-_]}.ko*") ; do
116				if ${READELF} -WS "$fn" | grep -qwF .debug_line ; then
117					echo $fn
118					return
119				fi
120				found_without_debug_info=true
121			done
122		fi
123	done
124
125	if [[ ${found_without_debug_info} == true ]]; then
126		echo "WARNING! No debugging info in module ${module}, rebuild with DEBUG_KERNEL and DEBUG_INFO" >&2
127	else
128		echo "WARNING! Cannot find .ko for module ${module}, please pass a valid module path" >&2
129	fi
130
131	return 1
132}
133
134parse_symbol() {
135	# The structure of symbol at this point is:
136	#   ([name]+[offset]/[total length])
137	#
138	# For example:
139	#   do_basic_setup+0x9c/0xbf
140
141	if [[ $module == "" ]] ; then
142		local objfile=$vmlinux
143	elif [[ $aarray_support == true && "${modcache[$module]+isset}" == "isset" ]]; then
144		local objfile=${modcache[$module]}
145	else
146		local objfile=$(find_module)
147		if [[ $objfile == "" ]] ; then
148			return
149		fi
150		if [[ $aarray_support == true ]]; then
151			modcache[$module]=$objfile
152		fi
153	fi
154
155	# Remove the englobing parenthesis
156	symbol=${symbol#\(}
157	symbol=${symbol%\)}
158
159	# Strip segment
160	local segment
161	if [[ $symbol == *:* ]] ; then
162		segment=${symbol%%:*}:
163		symbol=${symbol#*:}
164	fi
165
166	# Strip the symbol name so that we could look it up
167	local name=${symbol%+*}
168
169	# Use 'nm vmlinux' to figure out the base address of said symbol.
170	# It's actually faster to call it every time than to load it
171	# all into bash.
172	if [[ $aarray_support == true && "${cache[$module,$name]+isset}" == "isset" ]]; then
173		local base_addr=${cache[$module,$name]}
174	else
175		local base_addr=$(${NM} "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}')
176		if [[ $base_addr == "" ]] ; then
177			# address not found
178			return
179		fi
180		if [[ $aarray_support == true ]]; then
181			cache[$module,$name]="$base_addr"
182		fi
183	fi
184	# Let's start doing the math to get the exact address into the
185	# symbol. First, strip out the symbol total length.
186	local expr=${symbol%/*}
187	# Also parse the offset from symbol.
188	local offset=${expr#*+}
189	offset=$((offset))
190
191	# Now, replace the symbol name with the base address we found
192	# before.
193	expr=${expr/$name/0x$base_addr}
194
195	# Evaluate it to find the actual address
196	# The stack trace shows the return address, which is the next
197	# instruction after the actual call, so as long as it's in the same
198	# symbol, subtract one from that to point the call instruction.
199	if [[ $decode_retaddr == false && $offset != 0 ]]; then
200		expr=$((expr-1))
201	else
202		expr=$((expr))
203	fi
204	local address=$(printf "%x\n" "$expr")
205
206	# Pass it to addr2line to get filename and line number
207	# Could get more than one result
208	if [[ $aarray_support == true && "${cache[$module,$address]+isset}" == "isset" ]]; then
209		local code=${cache[$module,$address]}
210	else
211		local code=$(${ADDR2LINE} -i -e "$objfile" "$address" 2>/dev/null)
212		if [[ $aarray_support == true ]]; then
213			cache[$module,$address]=$code
214		fi
215	fi
216
217	# addr2line doesn't return a proper error code if it fails, so
218	# we detect it using the value it prints so that we could preserve
219	# the offset/size into the function and bail out
220	if [[ $code == "??:0" ]]; then
221		return
222	fi
223
224	# Strip out the base of the path on each line
225	code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code")
226
227	# In the case of inlines, move everything to same line
228	code=${code//$'\n'/' '}
229
230	# Demangle if the name looks like a Rust symbol and if
231	# we got a Rust demangler
232	if [[ $name =~ ^_R && $cppfilt != "" ]] ; then
233		name=$("$cppfilt" "$cppfilt_opts" "$name")
234	fi
235
236	# Replace old address with pretty line numbers
237	symbol="$segment$name ($code)"
238}
239
240debuginfod_get_vmlinux() {
241	local vmlinux_buildid=${1##* }
242
243	if [[ $vmlinux != "" ]]; then
244		return
245	fi
246
247	if [[ $vmlinux_buildid =~ ^[0-9a-f]+ ]]; then
248		vmlinux=$(debuginfod-find debuginfo $vmlinux_buildid)
249		if [[ $? -ne 0 ]] ; then
250			echo "ERROR! vmlinux image not found via debuginfod-find" >&2
251			usage
252			exit 2
253		fi
254		return
255	fi
256	echo "ERROR! Build ID for vmlinux not found. Try passing -r or specifying vmlinux" >&2
257	usage
258	exit 2
259}
260
261decode_code() {
262	local scripts=`dirname "${BASH_SOURCE[0]}"`
263	local lim="Code: "
264
265	echo -n "${1%%${lim}*}"
266	echo "${lim}${1##*${lim}}" | $scripts/decodecode
267}
268
269handle_line() {
270	if [[ $basepath == "auto" && $vmlinux != "" ]] ; then
271		module=""
272		symbol="kernel_init+0x0/0x0"
273		parse_symbol
274		basepath=${symbol#kernel_init (}
275		basepath=${basepath%/init/main.c:*)}
276	fi
277
278	local words spaces
279
280	# Tokenize: words and spaces to preserve the alignment
281	read -ra words <<<"$1"
282	IFS='#' read -ra spaces <<<"$(shopt -s extglob; echo "${1//+([^[:space:]])/#}")"
283
284	# Remove hex numbers. Do it ourselves until it happens in the
285	# kernel
286
287	# We need to know the index of the last element before we
288	# remove elements because arrays are sparse
289	local last=$(( ${#words[@]} - 1 ))
290
291	for i in "${!words[@]}"; do
292		# Remove the address
293		if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then
294			unset words[$i] spaces[$i]
295		fi
296	done
297
298	# Extract info after the symbol if present. E.g.:
299	# func_name+0x54/0x80 (P)
300	#                     ^^^
301	# The regex assumes only uppercase letters will be used. To be
302	# extended if needed.
303	local info_str=""
304	if [[ ${words[$last]} =~ \([A-Z]*\) ]]; then
305		info_str=${words[$last]}
306		unset words[$last] spaces[$last]
307		last=$(( $last - 1 ))
308	fi
309
310	# Join module name with its build id if present, as these were
311	# split during tokenization (e.g. "[module" and "modbuildid]").
312	if [[ ${words[$last]} =~ ^[0-9a-f]+\] ]]; then
313		words[$last-1]="${words[$last-1]} ${words[$last]}"
314		unset words[$last] spaces[$last]
315		last=$(( $last - 1 ))
316	fi
317
318	if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then
319		module=${words[$last]}
320		# some traces format is "(%pS)", which like "(foo+0x0/0x1 [bar])"
321		# so $module may like "[bar])". Strip the right parenthesis firstly
322		module=${module%\)}
323		module=${module#\[}
324		module=${module%\]}
325		modbuildid=${module#* }
326		module=${module% *}
327		if [[ $modbuildid == $module ]]; then
328			modbuildid=
329		fi
330		symbol=${words[$last-1]}
331		unset words[$last-1] spaces[$last-1]
332	else
333		# The symbol is the last element, process it
334		symbol=${words[$last]}
335		module=
336		modbuildid=
337	fi
338
339	unset words[$last]
340	parse_symbol # modifies $symbol
341
342	# Add up the line number to the symbol
343	for i in "${!words[@]}"; do
344		echo -n "${spaces[i]}${words[i]}"
345	done
346	echo "${spaces[$last]}${symbol}${module:+ ${module}}${info_str:+ ${info_str}}"
347}
348
349while read line; do
350	# Strip unexpected carriage return at end of line
351	line=${line%$'\r'}
352
353	# Let's see if we have an address in the line
354	if [[ $line =~ \[\<([^]]+)\>\] ]] ||
355	   [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then
356		# Translate address to line numbers
357		handle_line "$line"
358	# Is it a code line?
359	elif [[ $line == *Code:* ]]; then
360		decode_code "$line"
361	# Is it a version line?
362	elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then
363		debuginfod_get_vmlinux "$line"
364	else
365		# Nothing special in this line, show it as is
366		echo "$line"
367	fi
368done
369