1dbd1abb2SSasha Levin#!/bin/bash 2b2441318SGreg Kroah-Hartman# SPDX-License-Identifier: GPL-2.0 3dbd1abb2SSasha Levin# (c) 2014, Sasha Levin <[email protected]> 4dbd1abb2SSasha Levin#set -x 5dbd1abb2SSasha Levin 6ecda6e27SKonstantin Khlebnikovif [[ $# < 1 ]]; then 7dbd1abb2SSasha Levin echo "Usage:" 8*f90dde44SKonstantin Khlebnikov echo " $0 -r <release> | <vmlinux> [base path] [modules path]" 9dbd1abb2SSasha Levin exit 1 10dbd1abb2SSasha Levinfi 11dbd1abb2SSasha Levin 12*f90dde44SKonstantin Khlebnikovif [[ $1 == "-r" ]] ; then 13*f90dde44SKonstantin Khlebnikov vmlinux="" 14*f90dde44SKonstantin Khlebnikov basepath="auto" 15*f90dde44SKonstantin Khlebnikov modpath="" 16*f90dde44SKonstantin Khlebnikov release=$2 17*f90dde44SKonstantin Khlebnikov 18*f90dde44SKonstantin Khlebnikov for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do 19*f90dde44SKonstantin Khlebnikov if [ -e "$fn" ] ; then 20*f90dde44SKonstantin Khlebnikov vmlinux=$fn 21*f90dde44SKonstantin Khlebnikov break 22*f90dde44SKonstantin Khlebnikov fi 23*f90dde44SKonstantin Khlebnikov done 24*f90dde44SKonstantin Khlebnikov 25*f90dde44SKonstantin Khlebnikov if [[ $vmlinux == "" ]] ; then 26*f90dde44SKonstantin Khlebnikov echo "ERROR! vmlinux image for release $release is not found" >&2 27*f90dde44SKonstantin Khlebnikov exit 2 28*f90dde44SKonstantin Khlebnikov fi 29*f90dde44SKonstantin Khlebnikovelse 30dbd1abb2SSasha Levin vmlinux=$1 31ecda6e27SKonstantin Khlebnikov basepath=${2-auto} 32310c6dd0SKonstantin Khlebnikov modpath=$3 33431151b6SKonstantin Khlebnikov release="" 34*f90dde44SKonstantin Khlebnikovfi 35431151b6SKonstantin Khlebnikov 36dbd1abb2SSasha Levindeclare -A cache 37310c6dd0SKonstantin Khlebnikovdeclare -A modcache 38dbd1abb2SSasha Levin 39431151b6SKonstantin Khlebnikovfind_module() { 40431151b6SKonstantin Khlebnikov if [[ "$modpath" != "" ]] ; then 41431151b6SKonstantin Khlebnikov for fn in $(find "$modpath" -name "${module//_/[-_]}.ko*") ; do 42431151b6SKonstantin Khlebnikov if readelf -WS "$fn" | grep -qwF .debug_line ; then 43431151b6SKonstantin Khlebnikov echo $fn 44431151b6SKonstantin Khlebnikov return 45431151b6SKonstantin Khlebnikov fi 46431151b6SKonstantin Khlebnikov done 47431151b6SKonstantin Khlebnikov return 1 48431151b6SKonstantin Khlebnikov fi 49431151b6SKonstantin Khlebnikov 50431151b6SKonstantin Khlebnikov modpath=$(dirname "$vmlinux") 51431151b6SKonstantin Khlebnikov find_module && return 52431151b6SKonstantin Khlebnikov 53431151b6SKonstantin Khlebnikov if [[ $release == "" ]] ; then 54431151b6SKonstantin Khlebnikov release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" | sed -n 's/\$1 = "\(.*\)".*/\1/p') 55431151b6SKonstantin Khlebnikov fi 56431151b6SKonstantin Khlebnikov 57431151b6SKonstantin Khlebnikov for dn in {/usr/lib/debug,}/lib/modules/$release ; do 58431151b6SKonstantin Khlebnikov if [ -e "$dn" ] ; then 59431151b6SKonstantin Khlebnikov modpath="$dn" 60431151b6SKonstantin Khlebnikov find_module && return 61431151b6SKonstantin Khlebnikov fi 62431151b6SKonstantin Khlebnikov done 63431151b6SKonstantin Khlebnikov 64431151b6SKonstantin Khlebnikov modpath="" 65431151b6SKonstantin Khlebnikov return 1 66431151b6SKonstantin Khlebnikov} 67431151b6SKonstantin Khlebnikov 68dbd1abb2SSasha Levinparse_symbol() { 69dbd1abb2SSasha Levin # The structure of symbol at this point is: 70e260fe01SRobert Jarzmik # ([name]+[offset]/[total length]) 71dbd1abb2SSasha Levin # 72dbd1abb2SSasha Levin # For example: 73dbd1abb2SSasha Levin # do_basic_setup+0x9c/0xbf 74dbd1abb2SSasha Levin 75310c6dd0SKonstantin Khlebnikov if [[ $module == "" ]] ; then 76310c6dd0SKonstantin Khlebnikov local objfile=$vmlinux 77310c6dd0SKonstantin Khlebnikov elif [[ "${modcache[$module]+isset}" == "isset" ]]; then 78310c6dd0SKonstantin Khlebnikov local objfile=${modcache[$module]} 79310c6dd0SKonstantin Khlebnikov else 80431151b6SKonstantin Khlebnikov local objfile=$(find_module) 81431151b6SKonstantin Khlebnikov if [[ $objfile == "" ]] ; then 82a5dc8300SSasha Levin echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2 83a5dc8300SSasha Levin return 84a5dc8300SSasha Levin fi 85310c6dd0SKonstantin Khlebnikov modcache[$module]=$objfile 86310c6dd0SKonstantin Khlebnikov fi 87310c6dd0SKonstantin Khlebnikov 88e260fe01SRobert Jarzmik # Remove the englobing parenthesis 89e260fe01SRobert Jarzmik symbol=${symbol#\(} 90e260fe01SRobert Jarzmik symbol=${symbol%\)} 91dbd1abb2SSasha Levin 921d6693fbSKonstantin Khlebnikov # Strip segment 931d6693fbSKonstantin Khlebnikov local segment 941d6693fbSKonstantin Khlebnikov if [[ $symbol == *:* ]] ; then 951d6693fbSKonstantin Khlebnikov segment=${symbol%%:*}: 961d6693fbSKonstantin Khlebnikov symbol=${symbol#*:} 971d6693fbSKonstantin Khlebnikov fi 981d6693fbSKonstantin Khlebnikov 99dbd1abb2SSasha Levin # Strip the symbol name so that we could look it up 100dbd1abb2SSasha Levin local name=${symbol%+*} 101dbd1abb2SSasha Levin 102dbd1abb2SSasha Levin # Use 'nm vmlinux' to figure out the base address of said symbol. 103dbd1abb2SSasha Levin # It's actually faster to call it every time than to load it 104dbd1abb2SSasha Levin # all into bash. 105310c6dd0SKonstantin Khlebnikov if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then 106310c6dd0SKonstantin Khlebnikov local base_addr=${cache[$module,$name]} 107dbd1abb2SSasha Levin else 108f643b9eeSKonstantin Khlebnikov local base_addr=$(nm "$objfile" | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') 109f643b9eeSKonstantin Khlebnikov if [[ $base_addr == "" ]] ; then 110f643b9eeSKonstantin Khlebnikov # address not found 111f643b9eeSKonstantin Khlebnikov return 112f643b9eeSKonstantin Khlebnikov fi 113310c6dd0SKonstantin Khlebnikov cache[$module,$name]="$base_addr" 114dbd1abb2SSasha Levin fi 115dbd1abb2SSasha Levin # Let's start doing the math to get the exact address into the 116dbd1abb2SSasha Levin # symbol. First, strip out the symbol total length. 117dbd1abb2SSasha Levin local expr=${symbol%/*} 118dbd1abb2SSasha Levin 119dbd1abb2SSasha Levin # Now, replace the symbol name with the base address we found 120dbd1abb2SSasha Levin # before. 121dbd1abb2SSasha Levin expr=${expr/$name/0x$base_addr} 122dbd1abb2SSasha Levin 123dbd1abb2SSasha Levin # Evaluate it to find the actual address 124dbd1abb2SSasha Levin expr=$((expr)) 125dbd1abb2SSasha Levin local address=$(printf "%x\n" "$expr") 126dbd1abb2SSasha Levin 127dbd1abb2SSasha Levin # Pass it to addr2line to get filename and line number 128dbd1abb2SSasha Levin # Could get more than one result 129310c6dd0SKonstantin Khlebnikov if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then 130310c6dd0SKonstantin Khlebnikov local code=${cache[$module,$address]} 131dbd1abb2SSasha Levin else 132c04e32e9SManuel Traut local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address") 133310c6dd0SKonstantin Khlebnikov cache[$module,$address]=$code 134dbd1abb2SSasha Levin fi 135dbd1abb2SSasha Levin 136dbd1abb2SSasha Levin # addr2line doesn't return a proper error code if it fails, so 137dbd1abb2SSasha Levin # we detect it using the value it prints so that we could preserve 138dbd1abb2SSasha Levin # the offset/size into the function and bail out 139dbd1abb2SSasha Levin if [[ $code == "??:0" ]]; then 140dbd1abb2SSasha Levin return 141dbd1abb2SSasha Levin fi 142dbd1abb2SSasha Levin 143d178770dSPi-Hsun Shih # Strip out the base of the path on each line 144d178770dSPi-Hsun Shih code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") 145dbd1abb2SSasha Levin 146dbd1abb2SSasha Levin # In the case of inlines, move everything to same line 147dbd1abb2SSasha Levin code=${code//$'\n'/' '} 148dbd1abb2SSasha Levin 149dbd1abb2SSasha Levin # Replace old address with pretty line numbers 1501d6693fbSKonstantin Khlebnikov symbol="$segment$name ($code)" 151dbd1abb2SSasha Levin} 152dbd1abb2SSasha Levin 153dbd1abb2SSasha Levindecode_code() { 154dbd1abb2SSasha Levin local scripts=`dirname "${BASH_SOURCE[0]}"` 155dbd1abb2SSasha Levin 156dbd1abb2SSasha Levin echo "$1" | $scripts/decodecode 157dbd1abb2SSasha Levin} 158dbd1abb2SSasha Levin 159dbd1abb2SSasha Levinhandle_line() { 160dbd1abb2SSasha Levin local words 161dbd1abb2SSasha Levin 162dbd1abb2SSasha Levin # Tokenize 163dbd1abb2SSasha Levin read -a words <<<"$1" 164dbd1abb2SSasha Levin 165dbd1abb2SSasha Levin # Remove hex numbers. Do it ourselves until it happens in the 166dbd1abb2SSasha Levin # kernel 167dbd1abb2SSasha Levin 168dbd1abb2SSasha Levin # We need to know the index of the last element before we 169dbd1abb2SSasha Levin # remove elements because arrays are sparse 170dbd1abb2SSasha Levin local last=$(( ${#words[@]} - 1 )) 171dbd1abb2SSasha Levin 172dbd1abb2SSasha Levin for i in "${!words[@]}"; do 173dbd1abb2SSasha Levin # Remove the address 174dbd1abb2SSasha Levin if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then 175dbd1abb2SSasha Levin unset words[$i] 176dbd1abb2SSasha Levin fi 177dbd1abb2SSasha Levin 178dbd1abb2SSasha Levin # Format timestamps with tabs 179dbd1abb2SSasha Levin if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then 180dbd1abb2SSasha Levin unset words[$i] 181dbd1abb2SSasha Levin words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}") 182dbd1abb2SSasha Levin fi 183dbd1abb2SSasha Levin done 184dbd1abb2SSasha Levin 185310c6dd0SKonstantin Khlebnikov if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then 186310c6dd0SKonstantin Khlebnikov module=${words[$last]} 187310c6dd0SKonstantin Khlebnikov module=${module#\[} 188310c6dd0SKonstantin Khlebnikov module=${module%\]} 189310c6dd0SKonstantin Khlebnikov symbol=${words[$last-1]} 190310c6dd0SKonstantin Khlebnikov unset words[$last-1] 191310c6dd0SKonstantin Khlebnikov else 192dbd1abb2SSasha Levin # The symbol is the last element, process it 193dbd1abb2SSasha Levin symbol=${words[$last]} 194310c6dd0SKonstantin Khlebnikov module= 195310c6dd0SKonstantin Khlebnikov fi 196310c6dd0SKonstantin Khlebnikov 197dbd1abb2SSasha Levin unset words[$last] 198dbd1abb2SSasha Levin parse_symbol # modifies $symbol 199dbd1abb2SSasha Levin 200dbd1abb2SSasha Levin # Add up the line number to the symbol 201310c6dd0SKonstantin Khlebnikov echo "${words[@]}" "$symbol $module" 202dbd1abb2SSasha Levin} 203dbd1abb2SSasha Levin 204ecda6e27SKonstantin Khlebnikovif [[ $basepath == "auto" ]] ; then 205ecda6e27SKonstantin Khlebnikov module="" 206ecda6e27SKonstantin Khlebnikov symbol="kernel_init+0x0/0x0" 207ecda6e27SKonstantin Khlebnikov parse_symbol 208ecda6e27SKonstantin Khlebnikov basepath=${symbol#kernel_init (} 209ecda6e27SKonstantin Khlebnikov basepath=${basepath%/init/main.c:*)} 210ecda6e27SKonstantin Khlebnikovfi 211ecda6e27SKonstantin Khlebnikov 212dbd1abb2SSasha Levinwhile read line; do 213dbd1abb2SSasha Levin # Let's see if we have an address in the line 21453938ee4SJosh Poimboeuf if [[ $line =~ \[\<([^]]+)\>\] ]] || 21553938ee4SJosh Poimboeuf [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then 216dbd1abb2SSasha Levin # Translate address to line numbers 217dbd1abb2SSasha Levin handle_line "$line" 218dbd1abb2SSasha Levin # Is it a code line? 219dbd1abb2SSasha Levin elif [[ $line == *Code:* ]]; then 220dbd1abb2SSasha Levin decode_code "$line" 221dbd1abb2SSasha Levin else 222dbd1abb2SSasha Levin # Nothing special in this line, show it as is 223dbd1abb2SSasha Levin echo "$line" 224dbd1abb2SSasha Levin fi 225dbd1abb2SSasha Levindone 226