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 | #!/bin/bash # SPDX-License-Identifier: GPL-2.0 # (c) 2014, Sasha Levin <sasha.levin@oracle.com> #set -x if [[ $# < 2 ]]; then echo "Usage:" echo " $0 [vmlinux] [base path] [modules path]" exit 1 fi vmlinux=$1 basepath=$2 modpath=$3 declare -A cache declare -A modcache 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 [[ "${modcache[$module]+isset}" == "isset" ]]; then local objfile=${modcache[$module]} else [[ $modpath == "" ]] && return local objfile=$(find "$modpath" -name $module.ko -print -quit) [[ $objfile == "" ]] && return modcache[$module]=$objfile fi # Remove the englobing parenthesis symbol=${symbol#\(} symbol=${symbol%\)} # 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 [[ "${cache[$module,$name]+isset}" == "isset" ]]; then local base_addr=${cache[$module,$name]} else local base_addr=$(nm "$objfile" | grep -i ' t ' | awk "/ $name\$/ {print \$1}" | head -n1) cache[$module,$name]="$base_addr" 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 [[ "${cache[$module,$address]+isset}" == "isset" ]]; then local code=${cache[$module,$address]} else local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address") cache[$module,$address]=$code 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'/' '} # Replace old address with pretty line numbers symbol="$name ($code)" } decode_code() { local scripts=`dirname "${BASH_SOURCE[0]}"` echo "$1" | $scripts/decodecode } handle_line() { 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]} =~ \[([^]]+)\] ]]; then module=${words[$last]} module=${module#\[} module=${module%\]} symbol=${words[$last-1]} unset words[$last-1] else # The symbol is the last element, process it symbol=${words[$last]} module= fi unset words[$last] parse_symbol # modifies $symbol # Add up the line number to the symbol echo "${words[@]}" "$symbol $module" } while read line; do # 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" else # Nothing special in this line, show it as is echo "$line" fi done |