decode_stacktrace.sh 3.1 KB

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