decode_stacktrace.sh 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. #!/bin/bash
  2. # SPDX-License-Identifier: GPL-2.0
  3. # (c) 2014, Sasha Levin <sasha.levin@oracle.com>
  4. #set -x
  5. if [[ $# < 1 ]]; then
  6. echo "Usage:"
  7. echo " $0 -r <release> | <vmlinux> [base path] [modules path]"
  8. exit 1
  9. fi
  10. if [[ $1 == "-r" ]] ; then
  11. vmlinux=""
  12. basepath="auto"
  13. modpath=""
  14. release=$2
  15. for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do
  16. if [ -e "$fn" ] ; then
  17. vmlinux=$fn
  18. break
  19. fi
  20. done
  21. if [[ $vmlinux == "" ]] ; then
  22. echo "ERROR! vmlinux image for release $release is not found" >&2
  23. exit 2
  24. fi
  25. else
  26. vmlinux=$1
  27. basepath=${2-auto}
  28. modpath=$3
  29. release=""
  30. fi
  31. declare -A cache
  32. declare -A modcache
  33. find_module() {
  34. if [[ "$modpath" != "" ]] ; then
  35. for fn in $(find "$modpath" -name "${module//_/[-_]}.ko*") ; do
  36. if readelf -WS "$fn" | grep -qwF .debug_line ; then
  37. echo $fn
  38. return
  39. fi
  40. done
  41. return 1
  42. fi
  43. modpath=$(dirname "$vmlinux")
  44. find_module && return
  45. if [[ $release == "" ]] ; then
  46. release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" | sed -n 's/\$1 = "\(.*\)".*/\1/p')
  47. fi
  48. for dn in {/usr/lib/debug,}/lib/modules/$release ; do
  49. if [ -e "$dn" ] ; then
  50. modpath="$dn"
  51. find_module && return
  52. fi
  53. done
  54. modpath=""
  55. return 1
  56. }
  57. parse_symbol() {
  58. # The structure of symbol at this point is:
  59. # ([name]+[offset]/[total length])
  60. #
  61. # For example:
  62. # do_basic_setup+0x9c/0xbf
  63. if [[ $module == "" ]] ; then
  64. local objfile=$vmlinux
  65. elif [[ "${modcache[$module]+isset}" == "isset" ]]; then
  66. local objfile=${modcache[$module]}
  67. else
  68. local objfile=$(find_module)
  69. if [[ $objfile == "" ]] ; then
  70. echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2
  71. return
  72. fi
  73. modcache[$module]=$objfile
  74. fi
  75. # Remove the englobing parenthesis
  76. symbol=${symbol#\(}
  77. symbol=${symbol%\)}
  78. # Strip segment
  79. local segment
  80. if [[ $symbol == *:* ]] ; then
  81. segment=${symbol%%:*}:
  82. symbol=${symbol#*:}
  83. fi
  84. # Strip the symbol name so that we could look it up
  85. local name=${symbol%+*}
  86. # Use 'nm vmlinux' to figure out the base address of said symbol.
  87. # It's actually faster to call it every time than to load it
  88. # all into bash.
  89. if [[ "${cache[$module,$name]+isset}" == "isset" ]]; then
  90. local base_addr=${cache[$module,$name]}
  91. else
  92. local base_addr=$(nm "$objfile" | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}')
  93. if [[ $base_addr == "" ]] ; then
  94. # address not found
  95. return
  96. fi
  97. cache[$module,$name]="$base_addr"
  98. fi
  99. # Let's start doing the math to get the exact address into the
  100. # symbol. First, strip out the symbol total length.
  101. local expr=${symbol%/*}
  102. # Now, replace the symbol name with the base address we found
  103. # before.
  104. expr=${expr/$name/0x$base_addr}
  105. # Evaluate it to find the actual address
  106. expr=$((expr))
  107. local address=$(printf "%x\n" "$expr")
  108. # Pass it to addr2line to get filename and line number
  109. # Could get more than one result
  110. if [[ "${cache[$module,$address]+isset}" == "isset" ]]; then
  111. local code=${cache[$module,$address]}
  112. else
  113. local code=$(${CROSS_COMPILE}addr2line -i -e "$objfile" "$address")
  114. cache[$module,$address]=$code
  115. fi
  116. # addr2line doesn't return a proper error code if it fails, so
  117. # we detect it using the value it prints so that we could preserve
  118. # the offset/size into the function and bail out
  119. if [[ $code == "??:0" ]]; then
  120. return
  121. fi
  122. # Strip out the base of the path on each line
  123. code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code")
  124. # In the case of inlines, move everything to same line
  125. code=${code//$'\n'/' '}
  126. # Replace old address with pretty line numbers
  127. symbol="$segment$name ($code)"
  128. }
  129. decode_code() {
  130. local scripts=`dirname "${BASH_SOURCE[0]}"`
  131. echo "$1" | $scripts/decodecode
  132. }
  133. handle_line() {
  134. local words
  135. # Tokenize
  136. read -a words <<<"$1"
  137. # Remove hex numbers. Do it ourselves until it happens in the
  138. # kernel
  139. # We need to know the index of the last element before we
  140. # remove elements because arrays are sparse
  141. local last=$(( ${#words[@]} - 1 ))
  142. for i in "${!words[@]}"; do
  143. # Remove the address
  144. if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then
  145. unset words[$i]
  146. fi
  147. # Format timestamps with tabs
  148. if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then
  149. unset words[$i]
  150. words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}")
  151. fi
  152. done
  153. if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then
  154. module=${words[$last]}
  155. module=${module#\[}
  156. module=${module%\]}
  157. symbol=${words[$last-1]}
  158. unset words[$last-1]
  159. else
  160. # The symbol is the last element, process it
  161. symbol=${words[$last]}
  162. module=
  163. fi
  164. unset words[$last]
  165. parse_symbol # modifies $symbol
  166. # Add up the line number to the symbol
  167. echo "${words[@]}" "$symbol $module"
  168. }
  169. if [[ $basepath == "auto" ]] ; then
  170. module=""
  171. symbol="kernel_init+0x0/0x0"
  172. parse_symbol
  173. basepath=${symbol#kernel_init (}
  174. basepath=${basepath%/init/main.c:*)}
  175. fi
  176. while read line; do
  177. # Let's see if we have an address in the line
  178. if [[ $line =~ \[\<([^]]+)\>\] ]] ||
  179. [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then
  180. # Translate address to line numbers
  181. handle_line "$line"
  182. # Is it a code line?
  183. elif [[ $line == *Code:* ]]; then
  184. decode_code "$line"
  185. else
  186. # Nothing special in this line, show it as is
  187. echo "$line"
  188. fi
  189. done