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