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