decode_stacktrace.sh 3.7 KB

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