1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3# (c) 2014, Sasha Levin <[email protected]>
4#set -x
5
6if [[ $# < 1 ]]; then
7	echo "Usage:"
8	echo "	$0 <vmlinux> [base path] [modules path]"
9	exit 1
10fi
11
12vmlinux=$1
13basepath=${2-auto}
14modpath=$3
15release=""
16
17declare -A cache
18declare -A modcache
19
20find_module() {
21	if [[ "$modpath" != "" ]] ; then
22		for fn in $(find "$modpath" -name "${module//_/[-_]}.ko*") ; do
23			if readelf -WS "$fn" | grep -qwF .debug_line ; then
24				echo $fn
25				return
26			fi
27		done
28		return 1
29	fi
30
31	modpath=$(dirname "$vmlinux")
32	find_module && return
33
34	if [[ $release == "" ]] ; then
35		release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" | sed -n 's/\$1 = "\(.*\)".*/\1/p')
36	fi
37
38	for dn in {/usr/lib/debug,}/lib/modules/$release ; do
39		if [ -e "$dn" ] ; then
40			modpath="$dn"
41			find_module && return
42		fi
43	done
44
45	modpath=""
46	return 1
47}
48
49parse_symbol() {
50	# The structure of symbol at this point is:
51	#   ([name]+[offset]/[total length])
52	#
53	# For example:
54	#   do_basic_setup+0x9c/0xbf
55
56	if [[ $module == "" ]] ; then
57		local objfile=$vmlinux
58	elif [[ "${modcache[$module]+isset}" == "isset" ]]; then
59		local objfile=${modcache[$module]}
60	else
61		local objfile=$(find_module)
62		if [[ $objfile == "" ]] ; then
63			echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2
64			return
65		fi
66		modcache[$module]=$objfile
67	fi
68
69	# Remove the englobing parenthesis
70	symbol=${symbol#\(}
71	symbol=${symbol%\)}
72
73	# Strip segment
74	local segment
75	if [[ $symbol == *:* ]] ; then
76		segment=${symbol%%:*}:
77		symbol=${symbol#*:}
78	fi
79
80	# Strip the symbol name so that we could look it up
81	local name=${symbol%+*}
82
83	# Use 'nm vmlinux' to figure out the base address of said symbol.
84	# It's actually faster to call it every time than to load it
85	# all into bash.
86	if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then
87		local base_addr=${cache[$module,$name]}
88	else
89		local base_addr=$(nm "$objfile" | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}')
90		if [[ $base_addr == "" ]] ; then
91			# address not found
92			return
93		fi
94		cache[$module,$name]="$base_addr"
95	fi
96	# Let's start doing the math to get the exact address into the
97	# symbol. First, strip out the symbol total length.
98	local expr=${symbol%/*}
99
100	# Now, replace the symbol name with the base address we found
101	# before.
102	expr=${expr/$name/0x$base_addr}
103
104	# Evaluate it to find the actual address
105	expr=$((expr))
106	local address=$(printf "%x\n" "$expr")
107
108	# Pass it to addr2line to get filename and line number
109	# Could get more than one result
110	if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then
111		local code=${cache[$module,$address]}
112	else
113		local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address")
114		cache[$module,$address]=$code
115	fi
116
117	# addr2line doesn't return a proper error code if it fails, so
118	# we detect it using the value it prints so that we could preserve
119	# the offset/size into the function and bail out
120	if [[ $code == "??:0" ]]; then
121		return
122	fi
123
124	# Strip out the base of the path on each line
125	code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code")
126
127	# In the case of inlines, move everything to same line
128	code=${code//$'\n'/' '}
129
130	# Replace old address with pretty line numbers
131	symbol="$segment$name ($code)"
132}
133
134decode_code() {
135	local scripts=`dirname "${BASH_SOURCE[0]}"`
136
137	echo "$1" | $scripts/decodecode
138}
139
140handle_line() {
141	local words
142
143	# Tokenize
144	read -a words <<<"$1"
145
146	# Remove hex numbers. Do it ourselves until it happens in the
147	# kernel
148
149	# We need to know the index of the last element before we
150	# remove elements because arrays are sparse
151	local last=$(( ${#words[@]} - 1 ))
152
153	for i in "${!words[@]}"; do
154		# Remove the address
155		if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then
156			unset words[$i]
157		fi
158
159		# Format timestamps with tabs
160		if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then
161			unset words[$i]
162			words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}")
163		fi
164	done
165
166	if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then
167		module=${words[$last]}
168		module=${module#\[}
169		module=${module%\]}
170		symbol=${words[$last-1]}
171		unset words[$last-1]
172	else
173		# The symbol is the last element, process it
174		symbol=${words[$last]}
175		module=
176	fi
177
178	unset words[$last]
179	parse_symbol # modifies $symbol
180
181	# Add up the line number to the symbol
182	echo "${words[@]}" "$symbol $module"
183}
184
185if [[ $basepath == "auto" ]] ; then
186	module=""
187	symbol="kernel_init+0x0/0x0"
188	parse_symbol
189	basepath=${symbol#kernel_init (}
190	basepath=${basepath%/init/main.c:*)}
191fi
192
193while read line; do
194	# Let's see if we have an address in the line
195	if [[ $line =~ \[\<([^]]+)\>\] ]] ||
196	   [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then
197		# Translate address to line numbers
198		handle_line "$line"
199	# Is it a code line?
200	elif [[ $line == *Code:* ]]; then
201		decode_code "$line"
202	else
203		# Nothing special in this line, show it as is
204		echo "$line"
205	fi
206done
207