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