summaryrefslogtreecommitdiffstats
path: root/scripts/decode_stacktrace.sh
diff options
context:
space:
mode:
authorSasha Levin <sasha.levin@oracle.com>2014-06-04 11:39:23 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2014-06-10 18:29:43 -0400
commitdbd1abb209715544bf37ffa0a3798108e140e3ec (patch)
tree9580eb21035d2b01251eef4c245696fbae1f2396 /scripts/decode_stacktrace.sh
parentd1e1cda862c16252087374ac75949b0e89a5717e (diff)
decode_stacktrace: make stack dump output useful again
Right now when people try to report issues in the kernel they send stack dumps to eachother, which looks something like this: [ 6.906437] [<ffffffff811f0e90>] ? backtrace_test_irq_callback+0x20/0x20 [ 6.907121] [<ffffffff84388ce8>] dump_stack+0x52/0x7f [ 6.907640] [<ffffffff811f0ec8>] backtrace_regression_test+0x38/0x110 [ 6.908281] [<ffffffff813596a0>] ? proc_create_data+0xa0/0xd0 [ 6.908870] [<ffffffff870a8040>] ? proc_modules_init+0x22/0x22 [ 6.909480] [<ffffffff810020c2>] do_one_initcall+0xc2/0x1e0 [...] However, most of the text you get is pure garbage. The only useful thing above is the function name. Due to the amount of different kernel code versions and various configurations being used, the kernel address and the offset into the function are not really helpful in determining where the problem actually occured. Too often the result of someone looking at a stack dump is asking the person who sent it for a translation for one or more 'addr2line' translations. Which slows down the entire process of debugging the issue (and really annoying). The decode_stacktrace script is an attempt to make the output more useful and easy to work with by translating all kernel addresses in the stack dump into line numbers. Which means that the stack dump would look like this: [ 635.148361] dump_stack (lib/dump_stack.c:52) [ 635.149127] warn_slowpath_common (kernel/panic.c:418) [ 635.150214] warn_slowpath_null (kernel/panic.c:453) [ 635.151031] _oalloc_pages_slowpath+0x6a/0x7d0 [ 635.152171] ? zone_watermark_ok (mm/page_alloc.c:1728) [ 635.152988] ? get_page_from_freelist (mm/page_alloc.c:1939) [ 635.154766] __alloc_pages_nodemask (mm/page_alloc.c:2766) It's pretty obvious why this is better than the previous stack dump before. Usage is pretty simple: ./decode_stacktrace.sh [vmlinux] [base path] Where vmlinux is the vmlinux to extract line numbers from and base path is the path that points to the root of the build tree, for example: ./decode_stacktrace.sh vmlinux /home/sasha/linux/ < input.log > output.log The stack trace should be piped through it (I, for example, just pipe the output of the serial console of my KVM test box through it). Signed-off-by: Sasha Levin <sasha.levin@oracle.com> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'scripts/decode_stacktrace.sh')
-rwxr-xr-xscripts/decode_stacktrace.sh126
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
5if [[ $# != 2 ]]; then
6 echo "Usage:"
7 echo " $0 [vmlinux] [base path]"
8 exit 1
9fi
10
11vmlinux=$1
12basepath=$2
13declare -A cache
14
15parse_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
73decode_code() {
74 local scripts=`dirname "${BASH_SOURCE[0]}"`
75
76 echo "$1" | $scripts/decodecode
77}
78
79handle_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
114while 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
126done