Merge tag 'perf-urgent-2021-09-19' of git://git.kernel.org/pub/scm/linux/kernel/git...
[platform/kernel/linux-starfive.git] / scripts / decode_stacktrace.sh
1 #!/bin/bash
2 # SPDX-License-Identifier: GPL-2.0
3 # (c) 2014, Sasha Levin <sasha.levin@oracle.com>
4 #set -x
5
6 usage() {
7         echo "Usage:"
8         echo "  $0 -r <release> | <vmlinux> [<base path>|auto] [<modules path>]"
9 }
10
11 if [[ $1 == "-r" ]] ; then
12         vmlinux=""
13         basepath="auto"
14         modpath=""
15         release=$2
16
17         for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do
18                 if [ -e "$fn" ] ; then
19                         vmlinux=$fn
20                         break
21                 fi
22         done
23
24         if [[ $vmlinux == "" ]] ; then
25                 echo "ERROR! vmlinux image for release $release is not found" >&2
26                 usage
27                 exit 2
28         fi
29 else
30         vmlinux=$1
31         basepath=${2-auto}
32         modpath=$3
33         release=""
34         debuginfod=
35
36         # Can we use debuginfod-find?
37         if type debuginfod-find >/dev/null 2>&1 ; then
38                 debuginfod=${1-only}
39         fi
40
41         if [[ $vmlinux == "" && -z $debuginfod ]] ; then
42                 echo "ERROR! vmlinux image must be specified" >&2
43                 usage
44                 exit 1
45         fi
46 fi
47
48 declare -A cache
49 declare -A modcache
50
51 find_module() {
52         if [[ -n $debuginfod ]] ; then
53                 if [[ -n $modbuildid ]] ; then
54                         debuginfod-find debuginfo $modbuildid && return
55                 fi
56
57                 # Only using debuginfod so don't try to find vmlinux module path
58                 if [[ $debuginfod == "only" ]] ; then
59                         return
60                 fi
61         fi
62
63         if [[ "$modpath" != "" ]] ; then
64                 for fn in $(find "$modpath" -name "${module//_/[-_]}.ko*") ; do
65                         if readelf -WS "$fn" | grep -qwF .debug_line ; then
66                                 echo $fn
67                                 return
68                         fi
69                 done
70                 return 1
71         fi
72
73         modpath=$(dirname "$vmlinux")
74         find_module && return
75
76         if [[ $release == "" ]] ; then
77                 release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" 2>/dev/null | sed -n 's/\$1 = "\(.*\)".*/\1/p')
78         fi
79
80         for dn in {/usr/lib/debug,}/lib/modules/$release ; do
81                 if [ -e "$dn" ] ; then
82                         modpath="$dn"
83                         find_module && return
84                 fi
85         done
86
87         modpath=""
88         return 1
89 }
90
91 parse_symbol() {
92         # The structure of symbol at this point is:
93         #   ([name]+[offset]/[total length])
94         #
95         # For example:
96         #   do_basic_setup+0x9c/0xbf
97
98         if [[ $module == "" ]] ; then
99                 local objfile=$vmlinux
100         elif [[ "${modcache[$module]+isset}" == "isset" ]]; then
101                 local objfile=${modcache[$module]}
102         else
103                 local objfile=$(find_module)
104                 if [[ $objfile == "" ]] ; then
105                         echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2
106                         return
107                 fi
108                 modcache[$module]=$objfile
109         fi
110
111         # Remove the englobing parenthesis
112         symbol=${symbol#\(}
113         symbol=${symbol%\)}
114
115         # Strip segment
116         local segment
117         if [[ $symbol == *:* ]] ; then
118                 segment=${symbol%%:*}:
119                 symbol=${symbol#*:}
120         fi
121
122         # Strip the symbol name so that we could look it up
123         local name=${symbol%+*}
124
125         # Use 'nm vmlinux' to figure out the base address of said symbol.
126         # It's actually faster to call it every time than to load it
127         # all into bash.
128         if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then
129                 local base_addr=${cache[$module,$name]}
130         else
131                 local base_addr=$(nm "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}')
132                 if [[ $base_addr == "" ]] ; then
133                         # address not found
134                         return
135                 fi
136                 cache[$module,$name]="$base_addr"
137         fi
138         # Let's start doing the math to get the exact address into the
139         # symbol. First, strip out the symbol total length.
140         local expr=${symbol%/*}
141
142         # Now, replace the symbol name with the base address we found
143         # before.
144         expr=${expr/$name/0x$base_addr}
145
146         # Evaluate it to find the actual address
147         expr=$((expr))
148         local address=$(printf "%x\n" "$expr")
149
150         # Pass it to addr2line to get filename and line number
151         # Could get more than one result
152         if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then
153                 local code=${cache[$module,$address]}
154         else
155                 local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address" 2>/dev/null)
156                 cache[$module,$address]=$code
157         fi
158
159         # addr2line doesn't return a proper error code if it fails, so
160         # we detect it using the value it prints so that we could preserve
161         # the offset/size into the function and bail out
162         if [[ $code == "??:0" ]]; then
163                 return
164         fi
165
166         # Strip out the base of the path on each line
167         code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code")
168
169         # In the case of inlines, move everything to same line
170         code=${code//$'\n'/' '}
171
172         # Replace old address with pretty line numbers
173         symbol="$segment$name ($code)"
174 }
175
176 debuginfod_get_vmlinux() {
177         local vmlinux_buildid=${1##* }
178
179         if [[ $vmlinux != "" ]]; then
180                 return
181         fi
182
183         if [[ $vmlinux_buildid =~ ^[0-9a-f]+ ]]; then
184                 vmlinux=$(debuginfod-find debuginfo $vmlinux_buildid)
185                 if [[ $? -ne 0 ]] ; then
186                         echo "ERROR! vmlinux image not found via debuginfod-find" >&2
187                         usage
188                         exit 2
189                 fi
190                 return
191         fi
192         echo "ERROR! Build ID for vmlinux not found. Try passing -r or specifying vmlinux" >&2
193         usage
194         exit 2
195 }
196
197 decode_code() {
198         local scripts=`dirname "${BASH_SOURCE[0]}"`
199
200         echo "$1" | $scripts/decodecode
201 }
202
203 handle_line() {
204         if [[ $basepath == "auto" && $vmlinux != "" ]] ; then
205                 module=""
206                 symbol="kernel_init+0x0/0x0"
207                 parse_symbol
208                 basepath=${symbol#kernel_init (}
209                 basepath=${basepath%/init/main.c:*)}
210         fi
211
212         local words
213
214         # Tokenize
215         read -a words <<<"$1"
216
217         # Remove hex numbers. Do it ourselves until it happens in the
218         # kernel
219
220         # We need to know the index of the last element before we
221         # remove elements because arrays are sparse
222         local last=$(( ${#words[@]} - 1 ))
223
224         for i in "${!words[@]}"; do
225                 # Remove the address
226                 if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then
227                         unset words[$i]
228                 fi
229
230                 # Format timestamps with tabs
231                 if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then
232                         unset words[$i]
233                         words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}")
234                 fi
235         done
236
237         if [[ ${words[$last]} =~ ^[0-9a-f]+\] ]]; then
238                 words[$last-1]="${words[$last-1]} ${words[$last]}"
239                 unset words[$last]
240                 last=$(( $last - 1 ))
241         fi
242
243         if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then
244                 module=${words[$last]}
245                 module=${module#\[}
246                 module=${module%\]}
247                 modbuildid=${module#* }
248                 module=${module% *}
249                 if [[ $modbuildid == $module ]]; then
250                         modbuildid=
251                 fi
252                 symbol=${words[$last-1]}
253                 unset words[$last-1]
254         else
255                 # The symbol is the last element, process it
256                 symbol=${words[$last]}
257                 module=
258                 modbuildid=
259         fi
260
261         unset words[$last]
262         parse_symbol # modifies $symbol
263
264         # Add up the line number to the symbol
265         echo "${words[@]}" "$symbol $module"
266 }
267
268 while read line; do
269         # Let's see if we have an address in the line
270         if [[ $line =~ \[\<([^]]+)\>\] ]] ||
271            [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then
272                 # Translate address to line numbers
273                 handle_line "$line"
274         # Is it a code line?
275         elif [[ $line == *Code:* ]]; then
276                 decode_code "$line"
277         # Is it a version line?
278         elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then
279                 debuginfod_get_vmlinux "$line"
280         else
281                 # Nothing special in this line, show it as is
282                 echo "$line"
283         fi
284 done