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