Back to home page

LXR

 
 

    


0001 #!/bin/bash
0002 # (c) 2014, Sasha Levin <sasha.levin@oracle.com>
0003 #set -x
0004 
0005 if [[ $# < 2 ]]; then
0006         echo "Usage:"
0007         echo "  $0 [vmlinux] [base path] [modules path]"
0008         exit 1
0009 fi
0010 
0011 vmlinux=$1
0012 basepath=$2
0013 modpath=$3
0014 declare -A cache
0015 declare -A modcache
0016 
0017 parse_symbol() {
0018         # The structure of symbol at this point is:
0019         #   ([name]+[offset]/[total length])
0020         #
0021         # For example:
0022         #   do_basic_setup+0x9c/0xbf
0023 
0024         if [[ $module == "" ]] ; then
0025                 local objfile=$vmlinux
0026         elif [[ "${modcache[$module]+isset}" == "isset" ]]; then
0027                 local objfile=${modcache[$module]}
0028         else
0029                 [[ $modpath == "" ]] && return
0030                 local objfile=$(find "$modpath" -name $module.ko -print -quit)
0031                 [[ $objfile == "" ]] && return
0032                 modcache[$module]=$objfile
0033         fi
0034 
0035         # Remove the englobing parenthesis
0036         symbol=${symbol#\(}
0037         symbol=${symbol%\)}
0038 
0039         # Strip the symbol name so that we could look it up
0040         local name=${symbol%+*}
0041 
0042         # Use 'nm vmlinux' to figure out the base address of said symbol.
0043         # It's actually faster to call it every time than to load it
0044         # all into bash.
0045         if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then
0046                 local base_addr=${cache[$module,$name]}
0047         else
0048                 local base_addr=$(nm "$objfile" | grep -i ' t ' | awk "/ $name\$/ {print \$1}" | head -n1)
0049                 cache[$module,$name]="$base_addr"
0050         fi
0051         # Let's start doing the math to get the exact address into the
0052         # symbol. First, strip out the symbol total length.
0053         local expr=${symbol%/*}
0054 
0055         # Now, replace the symbol name with the base address we found
0056         # before.
0057         expr=${expr/$name/0x$base_addr}
0058 
0059         # Evaluate it to find the actual address
0060         expr=$((expr))
0061         local address=$(printf "%x\n" "$expr")
0062 
0063         # Pass it to addr2line to get filename and line number
0064         # Could get more than one result
0065         if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then
0066                 local code=${cache[$module,$address]}
0067         else
0068                 local code=$(addr2line -i -e "$objfile" "$address")
0069                 cache[$module,$address]=$code
0070         fi
0071 
0072         # addr2line doesn't return a proper error code if it fails, so
0073         # we detect it using the value it prints so that we could preserve
0074         # the offset/size into the function and bail out
0075         if [[ $code == "??:0" ]]; then
0076                 return
0077         fi
0078 
0079         # Strip out the base of the path
0080         code=${code//$basepath/""}
0081 
0082         # In the case of inlines, move everything to same line
0083         code=${code//$'\n'/' '}
0084 
0085         # Replace old address with pretty line numbers
0086         symbol="$name ($code)"
0087 }
0088 
0089 decode_code() {
0090         local scripts=`dirname "${BASH_SOURCE[0]}"`
0091 
0092         echo "$1" | $scripts/decodecode
0093 }
0094 
0095 handle_line() {
0096         local words
0097 
0098         # Tokenize
0099         read -a words <<<"$1"
0100 
0101         # Remove hex numbers. Do it ourselves until it happens in the
0102         # kernel
0103 
0104         # We need to know the index of the last element before we
0105         # remove elements because arrays are sparse
0106         local last=$(( ${#words[@]} - 1 ))
0107 
0108         for i in "${!words[@]}"; do
0109                 # Remove the address
0110                 if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then
0111                         unset words[$i]
0112                 fi
0113 
0114                 # Format timestamps with tabs
0115                 if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then
0116                         unset words[$i]
0117                         words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}")
0118                 fi
0119         done
0120 
0121         if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then
0122                 module=${words[$last]}
0123                 module=${module#\[}
0124                 module=${module%\]}
0125                 symbol=${words[$last-1]}
0126                 unset words[$last-1]
0127         else
0128                 # The symbol is the last element, process it
0129                 symbol=${words[$last]}
0130                 module=
0131         fi
0132 
0133         unset words[$last]
0134         parse_symbol # modifies $symbol
0135 
0136         # Add up the line number to the symbol
0137         echo "${words[@]}" "$symbol $module"
0138 }
0139 
0140 while read line; do
0141         # Let's see if we have an address in the line
0142         if [[ $line =~ \[\<([^]]+)\>\] ]] ||
0143            [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then
0144                 # Translate address to line numbers
0145                 handle_line "$line"
0146         # Is it a code line?
0147         elif [[ $line == *Code:* ]]; then
0148                 decode_code "$line"
0149         else
0150                 # Nothing special in this line, show it as is
0151                 echo "$line"
0152         fi
0153 done