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