1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3# (c) 2014, Sasha Levin <sasha.levin@oracle.com> 4#set -x 5 6usage() { 7 echo "Usage:" 8 echo " $0 -r <release> | <vmlinux> [<base path>|auto] [<modules path>]" 9} 10 11if [[ $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 26 usage 27 exit 2 28 fi 29else 30 vmlinux=$1 31 basepath=${2-auto} 32 modpath=$3 33 release="" 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 46fi 47 48declare -A cache 49declare -A modcache 50 51find_module() { 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 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 77 release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" 2>/dev/null | sed -n 's/\$1 = "\(.*\)".*/\1/p') 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 91parse_symbol() { 92 # The structure of symbol at this point is: 93 # ([name]+[offset]/[total length]) 94 # 95 # For example: 96 # do_basic_setup+0x9c/0xbf 97 98 if [[ $module == "" ]] ; then 99 local objfile=$vmlinux 100 elif [[ "${modcache[$module]+isset}" == "isset" ]]; then 101 local objfile=${modcache[$module]} 102 else 103 local objfile=$(find_module) 104 if [[ $objfile == "" ]] ; then 105 echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2 106 return 107 fi 108 modcache[$module]=$objfile 109 fi 110 111 # Remove the englobing parenthesis 112 symbol=${symbol#\(} 113 symbol=${symbol%\)} 114 115 # Strip segment 116 local segment 117 if [[ $symbol == *:* ]] ; then 118 segment=${symbol%%:*}: 119 symbol=${symbol#*:} 120 fi 121 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. 128 if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then 129 local base_addr=${cache[$module,$name]} 130 else 131 local base_addr=$(nm "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') 132 if [[ $base_addr == "" ]] ; then 133 # address not found 134 return 135 fi 136 cache[$module,$name]="$base_addr" 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 151 # Could get more than one result 152 if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then 153 local code=${cache[$module,$address]} 154 else 155 local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address" 2>/dev/null) 156 cache[$module,$address]=$code 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 166 # Strip out the base of the path on each line 167 code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") 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 173 symbol="$segment$name ($code)" 174} 175 176debuginfod_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 197decode_code() { 198 local scripts=`dirname "${BASH_SOURCE[0]}"` 199 200 echo "$1" | $scripts/decodecode 201} 202 203handle_line() { 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 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 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 243 if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then 244 module=${words[$last]} 245 module=${module#\[} 246 module=${module%\]} 247 modbuildid=${module#* } 248 module=${module% *} 249 if [[ $modbuildid == $module ]]; then 250 modbuildid= 251 fi 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= 258 modbuildid= 259 fi 260 261 unset words[$last] 262 parse_symbol # modifies $symbol 263 264 # Add up the line number to the symbol 265 echo "${words[@]}" "$symbol $module" 266} 267 268while read line; do 269 # Let's see if we have an address in the line 270 if [[ $line =~ \[\<([^]]+)\>\] ]] || 271 [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then 272 # Translate address to line numbers 273 handle_line "$line" 274 # Is it a code line? 275 elif [[ $line == *Code:* ]]; then 276 decode_code "$line" 277 # Is it a version line? 278 elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then 279 debuginfod_get_vmlinux "$line" 280 else 281 # Nothing special in this line, show it as is 282 echo "$line" 283 fi 284done 285