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