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 | ||
ecda6e27 | 6 | if [[ $# < 1 ]]; then |
dbd1abb2 | 7 | echo "Usage:" |
ecda6e27 | 8 | echo " $0 <vmlinux> [base path] [modules path]" |
dbd1abb2 SL |
9 | exit 1 |
10 | fi | |
11 | ||
12 | vmlinux=$1 | |
ecda6e27 | 13 | basepath=${2-auto} |
310c6dd0 | 14 | modpath=$3 |
431151b6 KK |
15 | release="" |
16 | ||
dbd1abb2 | 17 | declare -A cache |
310c6dd0 | 18 | declare -A modcache |
dbd1abb2 | 19 | |
431151b6 KK |
20 | find_module() { |
21 | if [[ "$modpath" != "" ]] ; then | |
22 | for fn in $(find "$modpath" -name "${module//_/[-_]}.ko*") ; do | |
23 | if readelf -WS "$fn" | grep -qwF .debug_line ; then | |
24 | echo $fn | |
25 | return | |
26 | fi | |
27 | done | |
28 | return 1 | |
29 | fi | |
30 | ||
31 | modpath=$(dirname "$vmlinux") | |
32 | find_module && return | |
33 | ||
34 | if [[ $release == "" ]] ; then | |
35 | release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" | sed -n 's/\$1 = "\(.*\)".*/\1/p') | |
36 | fi | |
37 | ||
38 | for dn in {/usr/lib/debug,}/lib/modules/$release ; do | |
39 | if [ -e "$dn" ] ; then | |
40 | modpath="$dn" | |
41 | find_module && return | |
42 | fi | |
43 | done | |
44 | ||
45 | modpath="" | |
46 | return 1 | |
47 | } | |
48 | ||
dbd1abb2 SL |
49 | parse_symbol() { |
50 | # The structure of symbol at this point is: | |
e260fe01 | 51 | # ([name]+[offset]/[total length]) |
dbd1abb2 SL |
52 | # |
53 | # For example: | |
54 | # do_basic_setup+0x9c/0xbf | |
55 | ||
310c6dd0 KK |
56 | if [[ $module == "" ]] ; then |
57 | local objfile=$vmlinux | |
58 | elif [[ "${modcache[$module]+isset}" == "isset" ]]; then | |
59 | local objfile=${modcache[$module]} | |
60 | else | |
431151b6 KK |
61 | local objfile=$(find_module) |
62 | if [[ $objfile == "" ]] ; then | |
a5dc8300 SL |
63 | echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2 |
64 | return | |
65 | fi | |
310c6dd0 KK |
66 | modcache[$module]=$objfile |
67 | fi | |
68 | ||
e260fe01 RJ |
69 | # Remove the englobing parenthesis |
70 | symbol=${symbol#\(} | |
71 | symbol=${symbol%\)} | |
dbd1abb2 | 72 | |
1d6693fb KK |
73 | # Strip segment |
74 | local segment | |
75 | if [[ $symbol == *:* ]] ; then | |
76 | segment=${symbol%%:*}: | |
77 | symbol=${symbol#*:} | |
78 | fi | |
79 | ||
dbd1abb2 SL |
80 | # Strip the symbol name so that we could look it up |
81 | local name=${symbol%+*} | |
82 | ||
83 | # Use 'nm vmlinux' to figure out the base address of said symbol. | |
84 | # It's actually faster to call it every time than to load it | |
85 | # all into bash. | |
310c6dd0 KK |
86 | if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then |
87 | local base_addr=${cache[$module,$name]} | |
dbd1abb2 | 88 | else |
f643b9ee KK |
89 | local base_addr=$(nm "$objfile" | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') |
90 | if [[ $base_addr == "" ]] ; then | |
91 | # address not found | |
92 | return | |
93 | fi | |
310c6dd0 | 94 | cache[$module,$name]="$base_addr" |
dbd1abb2 SL |
95 | fi |
96 | # Let's start doing the math to get the exact address into the | |
97 | # symbol. First, strip out the symbol total length. | |
98 | local expr=${symbol%/*} | |
99 | ||
100 | # Now, replace the symbol name with the base address we found | |
101 | # before. | |
102 | expr=${expr/$name/0x$base_addr} | |
103 | ||
104 | # Evaluate it to find the actual address | |
105 | expr=$((expr)) | |
106 | local address=$(printf "%x\n" "$expr") | |
107 | ||
108 | # Pass it to addr2line to get filename and line number | |
310c6dd0 KK |
109 | # Could get more than one result |
110 | if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then | |
111 | local code=${cache[$module,$address]} | |
dbd1abb2 | 112 | else |
c04e32e9 | 113 | local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address") |
310c6dd0 | 114 | cache[$module,$address]=$code |
dbd1abb2 SL |
115 | fi |
116 | ||
117 | # addr2line doesn't return a proper error code if it fails, so | |
118 | # we detect it using the value it prints so that we could preserve | |
119 | # the offset/size into the function and bail out | |
120 | if [[ $code == "??:0" ]]; then | |
121 | return | |
122 | fi | |
123 | ||
d178770d PHS |
124 | # Strip out the base of the path on each line |
125 | code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") | |
dbd1abb2 SL |
126 | |
127 | # In the case of inlines, move everything to same line | |
128 | code=${code//$'\n'/' '} | |
129 | ||
130 | # Replace old address with pretty line numbers | |
1d6693fb | 131 | symbol="$segment$name ($code)" |
dbd1abb2 SL |
132 | } |
133 | ||
134 | decode_code() { | |
135 | local scripts=`dirname "${BASH_SOURCE[0]}"` | |
136 | ||
137 | echo "$1" | $scripts/decodecode | |
138 | } | |
139 | ||
140 | handle_line() { | |
141 | local words | |
142 | ||
143 | # Tokenize | |
144 | read -a words <<<"$1" | |
145 | ||
146 | # Remove hex numbers. Do it ourselves until it happens in the | |
147 | # kernel | |
148 | ||
149 | # We need to know the index of the last element before we | |
150 | # remove elements because arrays are sparse | |
151 | local last=$(( ${#words[@]} - 1 )) | |
152 | ||
153 | for i in "${!words[@]}"; do | |
154 | # Remove the address | |
155 | if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then | |
156 | unset words[$i] | |
157 | fi | |
158 | ||
159 | # Format timestamps with tabs | |
160 | if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then | |
161 | unset words[$i] | |
162 | words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}") | |
163 | fi | |
164 | done | |
165 | ||
310c6dd0 KK |
166 | if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then |
167 | module=${words[$last]} | |
168 | module=${module#\[} | |
169 | module=${module%\]} | |
170 | symbol=${words[$last-1]} | |
171 | unset words[$last-1] | |
172 | else | |
173 | # The symbol is the last element, process it | |
174 | symbol=${words[$last]} | |
175 | module= | |
176 | fi | |
177 | ||
dbd1abb2 SL |
178 | unset words[$last] |
179 | parse_symbol # modifies $symbol | |
180 | ||
181 | # Add up the line number to the symbol | |
310c6dd0 | 182 | echo "${words[@]}" "$symbol $module" |
dbd1abb2 SL |
183 | } |
184 | ||
ecda6e27 KK |
185 | if [[ $basepath == "auto" ]] ; then |
186 | module="" | |
187 | symbol="kernel_init+0x0/0x0" | |
188 | parse_symbol | |
189 | basepath=${symbol#kernel_init (} | |
190 | basepath=${basepath%/init/main.c:*)} | |
191 | fi | |
192 | ||
dbd1abb2 SL |
193 | while read line; do |
194 | # Let's see if we have an address in the line | |
53938ee4 JP |
195 | if [[ $line =~ \[\<([^]]+)\>\] ]] || |
196 | [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then | |
dbd1abb2 SL |
197 | # Translate address to line numbers |
198 | handle_line "$line" | |
199 | # Is it a code line? | |
200 | elif [[ $line == *Code:* ]]; then | |
310c6dd0 KK |
201 | decode_code "$line" |
202 | else | |
dbd1abb2 SL |
203 | # Nothing special in this line, show it as is |
204 | echo "$line" | |
205 | fi | |
206 | done |