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 aarray_support=true 49declare -A cache 2>/dev/null 50if [[ $? != 0 ]]; then 51 aarray_support=false 52else 53 declare -A modcache 54fi 55 56find_module() { 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 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 82 release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" 2>/dev/null | sed -n 's/\$1 = "\(.*\)".*/\1/p') 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 96parse_symbol() { 97 # The structure of symbol at this point is: 98 # ([name]+[offset]/[total length]) 99 # 100 # For example: 101 # do_basic_setup+0x9c/0xbf 102 103 if [[ $module == "" ]] ; then 104 local objfile=$vmlinux 105 elif [[ $aarray_support == true && "${modcache[$module]+isset}" == "isset" ]]; then 106 local objfile=${modcache[$module]} 107 else 108 local objfile=$(find_module) 109 if [[ $objfile == "" ]] ; then 110 echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2 111 return 112 fi 113 if [[ $aarray_support == true ]]; then 114 modcache[$module]=$objfile 115 fi 116 fi 117 118 # Remove the englobing parenthesis 119 symbol=${symbol#\(} 120 symbol=${symbol%\)} 121 122 # Strip segment 123 local segment 124 if [[ $symbol == *:* ]] ; then 125 segment=${symbol%%:*}: 126 symbol=${symbol#*:} 127 fi 128 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. 135 if [[ $aarray_support == true && "${cache[$module,$name]+isset}" == "isset" ]]; then 136 local base_addr=${cache[$module,$name]} 137 else 138 local base_addr=$(nm "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') 139 if [[ $base_addr == "" ]] ; then 140 # address not found 141 return 142 fi 143 if [[ $aarray_support == true ]]; then 144 cache[$module,$name]="$base_addr" 145 fi 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 160 # Could get more than one result 161 if [[ $aarray_support == true && "${cache[$module,$address]+isset}" == "isset" ]]; then 162 local code=${cache[$module,$address]} 163 else 164 local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address" 2>/dev/null) 165 if [[ $aarray_support == true ]]; then 166 cache[$module,$address]=$code 167 fi 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 177 # Strip out the base of the path on each line 178 code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") 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 184 symbol="$segment$name ($code)" 185} 186 187debuginfod_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 208decode_code() { 209 local scripts=`dirname "${BASH_SOURCE[0]}"` 210 211 echo "$1" | $scripts/decodecode 212} 213 214handle_line() { 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 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 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 254 if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then 255 module=${words[$last]} 256 module=${module#\[} 257 module=${module%\]} 258 modbuildid=${module#* } 259 module=${module% *} 260 if [[ $modbuildid == $module ]]; then 261 modbuildid= 262 fi 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= 269 modbuildid= 270 fi 271 272 unset words[$last] 273 parse_symbol # modifies $symbol 274 275 # Add up the line number to the symbol 276 echo "${words[@]}" "$symbol $module" 277} 278 279while read line; do 280 # Let's see if we have an address in the line 281 if [[ $line =~ \[\<([^]]+)\>\] ]] || 282 [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then 283 # Translate address to line numbers 284 handle_line "$line" 285 # Is it a code line? 286 elif [[ $line == *Code:* ]]; then 287 decode_code "$line" 288 # Is it a version line? 289 elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then 290 debuginfod_get_vmlinux "$line" 291 else 292 # Nothing special in this line, show it as is 293 echo "$line" 294 fi 295done 296