Blame view

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