diff options
Diffstat (limited to 'scripts/decode_stacktrace.sh')
-rwxr-xr-x | scripts/decode_stacktrace.sh | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/scripts/decode_stacktrace.sh b/scripts/decode_stacktrace.sh new file mode 100755 index 000000000000..515c4c00e957 --- /dev/null +++ b/scripts/decode_stacktrace.sh | |||
@@ -0,0 +1,126 @@ | |||
1 | #!/bin/bash | ||
2 | # (c) 2014, Sasha Levin <sasha.levin@oracle.com> | ||
3 | #set -x | ||
4 | |||
5 | if [[ $# != 2 ]]; then | ||
6 | echo "Usage:" | ||
7 | echo " $0 [vmlinux] [base path]" | ||
8 | exit 1 | ||
9 | fi | ||
10 | |||
11 | vmlinux=$1 | ||
12 | basepath=$2 | ||
13 | declare -A cache | ||
14 | |||
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 | |||
22 | |||
23 | # Strip the symbol name so that we could look it up | ||
24 | local name=${symbol%+*} | ||
25 | |||
26 | # Use 'nm vmlinux' to figure out the base address of said symbol. | ||
27 | # It's actually faster to call it every time than to load it | ||
28 | # all into bash. | ||
29 | if [[ "${cache[$name]+isset}" == "isset" ]]; then | ||
30 | local base_addr=${cache[$name]} | ||
31 | else | ||
32 | local base_addr=$(nm "$vmlinux" | grep -i ' t ' | awk "/ $name\$/ {print \$1}" | head -n1) | ||
33 | cache["$name"]="$base_addr" | ||
34 | fi | ||
35 | # Let's start doing the math to get the exact address into the | ||
36 | # symbol. First, strip out the symbol total length. | ||
37 | local expr=${symbol%/*} | ||
38 | |||
39 | # Now, replace the symbol name with the base address we found | ||
40 | # before. | ||
41 | expr=${expr/$name/0x$base_addr} | ||
42 | |||
43 | # Evaluate it to find the actual address | ||
44 | expr=$((expr)) | ||
45 | local address=$(printf "%x\n" "$expr") | ||
46 | |||
47 | # Pass it to addr2line to get filename and line number | ||
48 | # Could get more than one result | ||
49 | if [[ "${cache[$address]+isset}" == "isset" ]]; then | ||
50 | local code=${cache[$address]} | ||
51 | else | ||
52 | local code=$(addr2line -i -e "$vmlinux" "$address") | ||
53 | cache[$address]=$code | ||
54 | fi | ||
55 | |||
56 | # addr2line doesn't return a proper error code if it fails, so | ||
57 | # we detect it using the value it prints so that we could preserve | ||
58 | # the offset/size into the function and bail out | ||
59 | if [[ $code == "??:0" ]]; then | ||
60 | return | ||
61 | fi | ||
62 | |||
63 | # Strip out the base of the path | ||
64 | code=${code//$basepath/""} | ||
65 | |||
66 | # In the case of inlines, move everything to same line | ||
67 | code=${code//$'\n'/' '} | ||
68 | |||
69 | # Replace old address with pretty line numbers | ||
70 | symbol="$name ($code)" | ||
71 | } | ||
72 | |||
73 | decode_code() { | ||
74 | local scripts=`dirname "${BASH_SOURCE[0]}"` | ||
75 | |||
76 | echo "$1" | $scripts/decodecode | ||
77 | } | ||
78 | |||
79 | handle_line() { | ||
80 | local words | ||
81 | |||
82 | # Tokenize | ||
83 | read -a words <<<"$1" | ||
84 | |||
85 | # Remove hex numbers. Do it ourselves until it happens in the | ||
86 | # kernel | ||
87 | |||
88 | # We need to know the index of the last element before we | ||
89 | # remove elements because arrays are sparse | ||
90 | local last=$(( ${#words[@]} - 1 )) | ||
91 | |||
92 | for i in "${!words[@]}"; do | ||
93 | # Remove the address | ||
94 | if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then | ||
95 | unset words[$i] | ||
96 | fi | ||
97 | |||
98 | # Format timestamps with tabs | ||
99 | if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then | ||
100 | unset words[$i] | ||
101 | words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}") | ||
102 | fi | ||
103 | done | ||
104 | |||
105 | # The symbol is the last element, process it | ||
106 | symbol=${words[$last]} | ||
107 | unset words[$last] | ||
108 | parse_symbol # modifies $symbol | ||
109 | |||
110 | # Add up the line number to the symbol | ||
111 | echo "${words[@]}" "$symbol" | ||
112 | } | ||
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 | ||