Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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