decode_stacktrace.sh 3.8 KB

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