Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 1 | #!/bin/bash |
Greg Kroah-Hartman | b244131 | 2017-11-01 15:07:57 +0100 | [diff] [blame] | 2 | # SPDX-License-Identifier: GPL-2.0 |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 3 | # (c) 2014, Sasha Levin <sasha.levin@oracle.com> |
| 4 | #set -x |
| 5 | |
Konstantin Khlebnikov | ecda6e2 | 2020-08-06 23:17:38 -0700 | [diff] [blame^] | 6 | if [[ $# < 1 ]]; then |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 7 | echo "Usage:" |
Konstantin Khlebnikov | ecda6e2 | 2020-08-06 23:17:38 -0700 | [diff] [blame^] | 8 | echo " $0 <vmlinux> [base path] [modules path]" |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 9 | exit 1 |
| 10 | fi |
| 11 | |
| 12 | vmlinux=$1 |
Konstantin Khlebnikov | ecda6e2 | 2020-08-06 23:17:38 -0700 | [diff] [blame^] | 13 | basepath=${2-auto} |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 14 | modpath=$3 |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 15 | declare -A cache |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 16 | declare -A modcache |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 17 | |
| 18 | parse_symbol() { |
| 19 | # The structure of symbol at this point is: |
Robert Jarzmik | e260fe0 | 2015-09-04 15:43:26 -0700 | [diff] [blame] | 20 | # ([name]+[offset]/[total length]) |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 21 | # |
| 22 | # For example: |
| 23 | # do_basic_setup+0x9c/0xbf |
| 24 | |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 25 | if [[ $module == "" ]] ; then |
| 26 | local objfile=$vmlinux |
| 27 | elif [[ "${modcache[$module]+isset}" == "isset" ]]; then |
| 28 | local objfile=${modcache[$module]} |
| 29 | else |
Sasha Levin | a5dc830 | 2020-06-15 18:24:27 -0400 | [diff] [blame] | 30 | if [[ $modpath == "" ]]; then |
| 31 | echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2 |
| 32 | return |
| 33 | fi |
Evan Green | ca90bbd | 2019-07-11 20:52:39 -0700 | [diff] [blame] | 34 | local objfile=$(find "$modpath" -name "${module//_/[-_]}.ko*" -print -quit) |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 35 | [[ $objfile == "" ]] && return |
| 36 | modcache[$module]=$objfile |
| 37 | fi |
| 38 | |
Robert Jarzmik | e260fe0 | 2015-09-04 15:43:26 -0700 | [diff] [blame] | 39 | # Remove the englobing parenthesis |
| 40 | symbol=${symbol#\(} |
| 41 | symbol=${symbol%\)} |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 42 | |
Konstantin Khlebnikov | 1d6693f | 2019-03-05 15:41:34 -0800 | [diff] [blame] | 43 | # Strip segment |
| 44 | local segment |
| 45 | if [[ $symbol == *:* ]] ; then |
| 46 | segment=${symbol%%:*}: |
| 47 | symbol=${symbol#*:} |
| 48 | fi |
| 49 | |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 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. |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 56 | if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then |
| 57 | local base_addr=${cache[$module,$name]} |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 58 | else |
Konstantin Khlebnikov | f643b9e | 2020-08-06 23:17:35 -0700 | [diff] [blame] | 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 |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 64 | cache[$module,$name]="$base_addr" |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 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 |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 79 | # Could get more than one result |
| 80 | if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then |
| 81 | local code=${cache[$module,$address]} |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 82 | else |
Manuel Traut | c04e32e | 2019-06-13 15:55:52 -0700 | [diff] [blame] | 83 | local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address") |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 84 | cache[$module,$address]=$code |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 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 | |
Pi-Hsun Shih | d178770 | 2020-07-23 21:15:43 -0700 | [diff] [blame] | 94 | # Strip out the base of the path on each line |
| 95 | code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 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 |
Konstantin Khlebnikov | 1d6693f | 2019-03-05 15:41:34 -0800 | [diff] [blame] | 101 | symbol="$segment$name ($code)" |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 102 | } |
| 103 | |
| 104 | decode_code() { |
| 105 | local scripts=`dirname "${BASH_SOURCE[0]}"` |
| 106 | |
| 107 | echo "$1" | $scripts/decodecode |
| 108 | } |
| 109 | |
| 110 | handle_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 | |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 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 | |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 148 | unset words[$last] |
| 149 | parse_symbol # modifies $symbol |
| 150 | |
| 151 | # Add up the line number to the symbol |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 152 | echo "${words[@]}" "$symbol $module" |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 153 | } |
| 154 | |
Konstantin Khlebnikov | ecda6e2 | 2020-08-06 23:17:38 -0700 | [diff] [blame^] | 155 | if [[ $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:*)} |
| 161 | fi |
| 162 | |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 163 | while read line; do |
| 164 | # Let's see if we have an address in the line |
Josh Poimboeuf | 53938ee | 2016-11-28 17:06:35 -0600 | [diff] [blame] | 165 | if [[ $line =~ \[\<([^]]+)\>\] ]] || |
| 166 | [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 167 | # Translate address to line numbers |
| 168 | handle_line "$line" |
| 169 | # Is it a code line? |
| 170 | elif [[ $line == *Code:* ]]; then |
Konstantin Khlebnikov | 310c6dd | 2016-05-19 17:09:11 -0700 | [diff] [blame] | 171 | decode_code "$line" |
| 172 | else |
Sasha Levin | dbd1abb | 2014-06-04 11:39:23 -0400 | [diff] [blame] | 173 | # Nothing special in this line, show it as is |
| 174 | echo "$line" |
| 175 | fi |
| 176 | done |