1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3# (c) 2014, Sasha Levin <[email protected]> 4#set -x 5 6if [[ $# < 1 ]]; then 7 echo "Usage:" 8 echo " $0 <vmlinux> [base path] [modules path]" 9 exit 1 10fi 11 12vmlinux=$1 13basepath=${2-auto} 14modpath=$3 15declare -A cache 16declare -A modcache 17 18parse_symbol() { 19 # The structure of symbol at this point is: 20 # ([name]+[offset]/[total length]) 21 # 22 # For example: 23 # do_basic_setup+0x9c/0xbf 24 25 if [[ $module == "" ]] ; then 26 local objfile=$vmlinux 27 elif [[ "${modcache[$module]+isset}" == "isset" ]]; then 28 local objfile=${modcache[$module]} 29 else 30 if [[ $modpath == "" ]]; then 31 echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2 32 return 33 fi 34 local objfile=$(find "$modpath" -name "${module//_/[-_]}.ko*" -print -quit) 35 [[ $objfile == "" ]] && return 36 modcache[$module]=$objfile 37 fi 38 39 # Remove the englobing parenthesis 40 symbol=${symbol#\(} 41 symbol=${symbol%\)} 42 43 # Strip segment 44 local segment 45 if [[ $symbol == *:* ]] ; then 46 segment=${symbol%%:*}: 47 symbol=${symbol#*:} 48 fi 49 50 # Strip the symbol name so that we could look it up 51 local name=${symbol%+*} 52 53 # Use 'nm vmlinux' to figure out the base address of said symbol. 54 # It's actually faster to call it every time than to load it 55 # all into bash. 56 if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then 57 local base_addr=${cache[$module,$name]} 58 else 59 local base_addr=$(nm "$objfile" | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') 60 if [[ $base_addr == "" ]] ; then 61 # address not found 62 return 63 fi 64 cache[$module,$name]="$base_addr" 65 fi 66 # Let's start doing the math to get the exact address into the 67 # symbol. First, strip out the symbol total length. 68 local expr=${symbol%/*} 69 70 # Now, replace the symbol name with the base address we found 71 # before. 72 expr=${expr/$name/0x$base_addr} 73 74 # Evaluate it to find the actual address 75 expr=$((expr)) 76 local address=$(printf "%x\n" "$expr") 77 78 # Pass it to addr2line to get filename and line number 79 # Could get more than one result 80 if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then 81 local code=${cache[$module,$address]} 82 else 83 local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address") 84 cache[$module,$address]=$code 85 fi 86 87 # addr2line doesn't return a proper error code if it fails, so 88 # we detect it using the value it prints so that we could preserve 89 # the offset/size into the function and bail out 90 if [[ $code == "??:0" ]]; then 91 return 92 fi 93 94 # Strip out the base of the path on each line 95 code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") 96 97 # In the case of inlines, move everything to same line 98 code=${code//$'\n'/' '} 99 100 # Replace old address with pretty line numbers 101 symbol="$segment$name ($code)" 102} 103 104decode_code() { 105 local scripts=`dirname "${BASH_SOURCE[0]}"` 106 107 echo "$1" | $scripts/decodecode 108} 109 110handle_line() { 111 local words 112 113 # Tokenize 114 read -a words <<<"$1" 115 116 # Remove hex numbers. Do it ourselves until it happens in the 117 # kernel 118 119 # We need to know the index of the last element before we 120 # remove elements because arrays are sparse 121 local last=$(( ${#words[@]} - 1 )) 122 123 for i in "${!words[@]}"; do 124 # Remove the address 125 if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then 126 unset words[$i] 127 fi 128 129 # Format timestamps with tabs 130 if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then 131 unset words[$i] 132 words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}") 133 fi 134 done 135 136 if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then 137 module=${words[$last]} 138 module=${module#\[} 139 module=${module%\]} 140 symbol=${words[$last-1]} 141 unset words[$last-1] 142 else 143 # The symbol is the last element, process it 144 symbol=${words[$last]} 145 module= 146 fi 147 148 unset words[$last] 149 parse_symbol # modifies $symbol 150 151 # Add up the line number to the symbol 152 echo "${words[@]}" "$symbol $module" 153} 154 155if [[ $basepath == "auto" ]] ; then 156 module="" 157 symbol="kernel_init+0x0/0x0" 158 parse_symbol 159 basepath=${symbol#kernel_init (} 160 basepath=${basepath%/init/main.c:*)} 161fi 162 163while read line; do 164 # Let's see if we have an address in the line 165 if [[ $line =~ \[\<([^]]+)\>\] ]] || 166 [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then 167 # Translate address to line numbers 168 handle_line "$line" 169 # Is it a code line? 170 elif [[ $line == *Code:* ]]; then 171 decode_code "$line" 172 else 173 # Nothing special in this line, show it as is 174 echo "$line" 175 fi 176done 177