Commit | Line | Data |
---|---|---|
dbd1abb2 SL |
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 |