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