#!/bin/bash # (c) 2014, Sasha Levin #set -x if [[ $# < 2 ]] { echo "Usage:" echo " $0 [vmlinux] [base path] [modules path]" exit 1 } setglobal vmlinux = $1 setglobal basepath = $2 setglobal modpath = $3TODO declare -A cache = ''TODO declare -A modcache = '' proc parse_symbol { # The structure of symbol at this point is: # ([name]+[offset]/[total length]) # # For example: # do_basic_setup+0x9c/0xbf if [[ $module == "" ]] { var objfile = $vmlinux } elif [[ "${modcache[$module]+isset}" == "isset" ]] { var objfile = $(modcache[$module]) } else { [[ $modpath == "" ]] && return var objfile = $[find $modpath -name $module.ko -print -quit] [[ $objfile == "" ]] && return compat array-assign modcache '$module' $objfile } # Remove the englobing parenthesis setglobal symbol = $(symbol#\() setglobal symbol = $(symbol%\)) # Strip the symbol name so that we could look it up var 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" ]] { var base_addr = $(cache[$module,$name]) } else { var base_addr = $[nm $objfile | grep -i ' t ' | awk "/ $name\$/ {print \$1}" | head -n1] compat array-assign cache '$module,$name' $base_addr } # Let's start doing the math to get the exact address into the # symbol. First, strip out the symbol total length. var expr = $(symbol%/*) # Now, replace the symbol name with the base address we found # before. set expr = $(expr/$name/0x$base_addr) # Evaluate it to find the actual address set expr = $shExpr('expr') var 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" ]] { var code = $(cache[$module,$address]) } else { var code = $[addr2line -i -e $objfile $address] compat array-assign cache '$module,$address' $code } # 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" ]] { return } # Strip out the base of the path set code = $(code//$basepath/"") # In the case of inlines, move everything to same line set code = $(code//$'\n'/' ') # Replace old address with pretty line numbers setglobal symbol = ""$name ($code)"" } proc decode_code { var scripts = $[dirname $(BASH_SOURCE[0])] echo $1 | $scripts/decodecode } proc handle_line { var 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 var last = $shExpr(' ${#words[@]} - 1 ') for i in [$(!words[@])] { # Remove the address if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]] { unset words[$i] } # Format timestamps with tabs if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]] { unset words[$i] compat array-assign words '$i+1' $[printf "[%13s\n" $(words[$i+1])] } } if [[ ${words[$last]} =~ \[([^]]+)\] ]] { setglobal module = $(words[$last]) setglobal module = $(module#\[) setglobal module = $(module%\]) setglobal symbol = $(words[$last-1]) unset words[$last-1] } else { # The symbol is the last element, process it setglobal symbol = $(words[$last]) setglobal module = '' } unset words[$last] parse_symbol # modifies $symbol # Add up the line number to the symbol echo $(words[@]) "$symbol $module" } while read line { # Let's see if we have an address in the line if [[ $line =~ \[\<([^]]+)\>\] ]] { # Translate address to line numbers handle_line $line # Is it a code line? } elif [[ $line == *Code:* ]] { decode_code $line } else { # Nothing special in this line, show it as is echo $line } }