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