linux/scripts/decode_stacktrace.sh
<<
>>
Prefs
   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