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 | |
dbd1abb2 | 48 | declare -A cache |
310c6dd0 | 49 | declare -A modcache |
dbd1abb2 | 50 | |
431151b6 | 51 | find_module() { |
26681eb3 SB |
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 | ||
431151b6 KK |
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 | |
5bf0f3bc | 77 | 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 |
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 | ||
dbd1abb2 SL |
91 | parse_symbol() { |
92 | # The structure of symbol at this point is: | |
e260fe01 | 93 | # ([name]+[offset]/[total length]) |
dbd1abb2 SL |
94 | # |
95 | # For example: | |
96 | # do_basic_setup+0x9c/0xbf | |
97 | ||
310c6dd0 KK |
98 | if [[ $module == "" ]] ; then |
99 | local objfile=$vmlinux | |
100 | elif [[ "${modcache[$module]+isset}" == "isset" ]]; then | |
101 | local objfile=${modcache[$module]} | |
102 | else | |
431151b6 KK |
103 | local objfile=$(find_module) |
104 | if [[ $objfile == "" ]] ; then | |
a5dc8300 SL |
105 | echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2 |
106 | return | |
107 | fi | |
310c6dd0 KK |
108 | modcache[$module]=$objfile |
109 | fi | |
110 | ||
e260fe01 RJ |
111 | # Remove the englobing parenthesis |
112 | symbol=${symbol#\(} | |
113 | symbol=${symbol%\)} | |
dbd1abb2 | 114 | |
1d6693fb KK |
115 | # Strip segment |
116 | local segment | |
117 | if [[ $symbol == *:* ]] ; then | |
118 | segment=${symbol%%:*}: | |
119 | symbol=${symbol#*:} | |
120 | fi | |
121 | ||
dbd1abb2 SL |
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. | |
310c6dd0 KK |
128 | if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then |
129 | local base_addr=${cache[$module,$name]} | |
dbd1abb2 | 130 | else |
5bf0f3bc | 131 | local base_addr=$(nm "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') |
f643b9ee KK |
132 | if [[ $base_addr == "" ]] ; then |
133 | # address not found | |
134 | return | |
135 | fi | |
310c6dd0 | 136 | cache[$module,$name]="$base_addr" |
dbd1abb2 SL |
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 | |
310c6dd0 KK |
151 | # Could get more than one result |
152 | if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then | |
153 | local code=${cache[$module,$address]} | |
dbd1abb2 | 154 | else |
5bf0f3bc | 155 | local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address" 2>/dev/null) |
310c6dd0 | 156 | cache[$module,$address]=$code |
dbd1abb2 SL |
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 | ||
d178770d PHS |
166 | # Strip out the base of the path on each line |
167 | code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") | |
dbd1abb2 SL |
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 | |
1d6693fb | 173 | symbol="$segment$name ($code)" |
dbd1abb2 SL |
174 | } |
175 | ||
26681eb3 SB |
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 | ||
dbd1abb2 SL |
197 | decode_code() { |
198 | local scripts=`dirname "${BASH_SOURCE[0]}"` | |
199 | ||
200 | echo "$1" | $scripts/decodecode | |
201 | } | |
202 | ||
203 | handle_line() { | |
26681eb3 SB |
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 | ||
dbd1abb2 SL |
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 | ||
26681eb3 SB |
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 | ||
310c6dd0 KK |
243 | if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then |
244 | module=${words[$last]} | |
245 | module=${module#\[} | |
246 | module=${module%\]} | |
26681eb3 SB |
247 | modbuildid=${module#* } |
248 | module=${module% *} | |
249 | if [[ $modbuildid == $module ]]; then | |
250 | modbuildid= | |
251 | fi | |
310c6dd0 KK |
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= | |
26681eb3 | 258 | modbuildid= |
310c6dd0 KK |
259 | fi |
260 | ||
dbd1abb2 SL |
261 | unset words[$last] |
262 | parse_symbol # modifies $symbol | |
263 | ||
264 | # Add up the line number to the symbol | |
310c6dd0 | 265 | echo "${words[@]}" "$symbol $module" |
dbd1abb2 SL |
266 | } |
267 | ||
268 | while read line; do | |
269 | # Let's see if we have an address in the line | |
53938ee4 JP |
270 | if [[ $line =~ \[\<([^]]+)\>\] ]] || |
271 | [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then | |
dbd1abb2 SL |
272 | # Translate address to line numbers |
273 | handle_line "$line" | |
274 | # Is it a code line? | |
275 | elif [[ $line == *Code:* ]]; then | |
310c6dd0 | 276 | decode_code "$line" |
26681eb3 SB |
277 | # Is it a version line? |
278 | elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then | |
279 | debuginfod_get_vmlinux "$line" | |
310c6dd0 | 280 | else |
dbd1abb2 SL |
281 | # Nothing special in this line, show it as is |
282 | echo "$line" | |
283 | fi | |
284 | done |