decode_stacktrace.sh 3.0 KB

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