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