1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3# (c) 2014, Sasha Levin <sasha.levin@oracle.com> 4#set -x 5 6if [[ $# < 1 ]]; then 7 echo "Usage:" 8 echo " $0 -r <release> | <vmlinux> [base path] [modules path]" 9 exit 1 10fi 11 12# Try to find a Rust demangler 13if type llvm-cxxfilt >/dev/null 2>&1 ; then 14 cppfilt=llvm-cxxfilt 15elif type c++filt >/dev/null 2>&1 ; then 16 cppfilt=c++filt 17 cppfilt_opts=-i 18fi 19 20UTIL_SUFFIX= 21if [[ -z ${LLVM:-} ]]; then 22 UTIL_PREFIX=${CROSS_COMPILE:-} 23else 24 UTIL_PREFIX=llvm- 25 if [[ ${LLVM} == */ ]]; then 26 UTIL_PREFIX=${LLVM}${UTIL_PREFIX} 27 elif [[ ${LLVM} == -* ]]; then 28 UTIL_SUFFIX=${LLVM} 29 fi 30fi 31 32READELF=${UTIL_PREFIX}readelf${UTIL_SUFFIX} 33ADDR2LINE=${UTIL_PREFIX}addr2line${UTIL_SUFFIX} 34 35if [[ $1 == "-r" ]] ; then 36 vmlinux="" 37 basepath="auto" 38 modpath="" 39 release=$2 40 41 for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do 42 if [ -e "$fn" ] ; then 43 vmlinux=$fn 44 break 45 fi 46 done 47 48 if [[ $vmlinux == "" ]] ; then 49 echo "ERROR! vmlinux image for release $release is not found" >&2 50 exit 2 51 fi 52else 53 vmlinux=$1 54 basepath=${2-auto} 55 modpath=$3 56 release="" 57fi 58 59declare aarray_support=true 60declare -A cache 2>/dev/null 61if [[ $? != 0 ]]; then 62 aarray_support=false 63else 64 declare -A modcache 65fi 66 67find_module() { 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=$(${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 # Demangle if the name looks like a Rust symbol and if 184 # we got a Rust demangler 185 if [[ $name =~ ^_R && $cppfilt != "" ]] ; then 186 name=$("$cppfilt" "$cppfilt_opts" "$name") 187 fi 188 189 # Replace old address with pretty line numbers 190 symbol="$segment$name ($code)" 191} 192 193decode_code() { 194 local scripts=`dirname "${BASH_SOURCE[0]}"` 195 196 echo "$1" | $scripts/decodecode 197} 198 199handle_line() { 200 local words 201 202 # Tokenize 203 read -a words <<<"$1" 204 205 # Remove hex numbers. Do it ourselves until it happens in the 206 # kernel 207 208 # We need to know the index of the last element before we 209 # remove elements because arrays are sparse 210 local last=$(( ${#words[@]} - 1 )) 211 212 for i in "${!words[@]}"; do 213 # Remove the address 214 if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then 215 unset words[$i] 216 fi 217 218 # Format timestamps with tabs 219 if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then 220 unset words[$i] 221 words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}") 222 fi 223 done 224 225 if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then 226 module=${words[$last]} 227 module=${module#\[} 228 module=${module%\]} 229 symbol=${words[$last-1]} 230 unset words[$last-1] 231 else 232 # The symbol is the last element, process it 233 symbol=${words[$last]} 234 module= 235 fi 236 237 unset words[$last] 238 parse_symbol # modifies $symbol 239 240 # Add up the line number to the symbol 241 echo "${words[@]}" "$symbol $module" 242} 243 244if [[ $basepath == "auto" ]] ; then 245 module="" 246 symbol="kernel_init+0x0/0x0" 247 parse_symbol 248 basepath=${symbol#kernel_init (} 249 basepath=${basepath%/init/main.c:*)} 250fi 251 252while read line; do 253 # Let's see if we have an address in the line 254 if [[ $line =~ \[\<([^]]+)\>\] ]] || 255 [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then 256 # Translate address to line numbers 257 handle_line "$line" 258 # Is it a code line? 259 elif [[ $line == *Code:* ]]; then 260 decode_code "$line" 261 else 262 # Nothing special in this line, show it as is 263 echo "$line" 264 fi 265done 266