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