blob: 3cc51cfa514e44ef825a9554812388c81743824c [file] [log] [blame]
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2017 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Cleans up overlapping portions of traces provided by logcat."""
18
19from __future__ import absolute_import
20from __future__ import division
21from __future__ import print_function
22
23import argparse
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -070024import re
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -070025import os
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -070026
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -070027STACK_DIVIDER = 65 * "="
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -070028
29
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -070030def match_to_int(match):
31 """Returns trace line number matches as integers for sorting.
32 Maps other matches to negative integers.
33 """
34 # Hard coded string are necessary since each trace must have the address
35 # accessed, which is printed before trace lines.
Bharadwaj Kalandhabhatta188ac6c2017-07-17 15:15:41 -070036 if match == "use-after-poison" or match == "unknown-crash":
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -070037 return -2
38 elif match == "READ":
39 return -1
40 # Cutting off non-integer part of match
41 return int(match[1:-1])
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -070042
43
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -070044def clean_trace_if_valid(trace, stack_min_size, prune_exact):
45 """Cleans trace if it meets a certain standard. Returns None otherwise."""
Bharadwaj Kalandhabhatta188ac6c2017-07-17 15:15:41 -070046 # Note: Sample input may contain "unknown-crash" instead of
47 # "use-after-poison"
48 #
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -070049 # Sample input:
50 # trace:
51 # "...ERROR: AddressSanitizer: use-after-poison on address 0x0071126a870a...
52 # ...READ of size 2 at 0x0071126a870a thread T0 (droid.deskclock)
53 # ... #0 0x71281013b3 (/data/asan/system/lib64/libart.so+0x2263b3)
54 # ... #1 0x71280fe6b7 (/data/asan/system/lib64/libart.so+0x2236b7)
55 # ... #3 0x71280c22ef (/data/asan/system/lib64/libart.so+0x1e72ef)
56 # ... #2 0x712810a84f (/data/asan/system/lib64/libart.so+0x22f84f)"
57 #
58 # stack_min_size: 2
59 # prune_exact: False
60 #
61 # Sample output:
62 #
63 # "...ERROR: AddressSanitizer: use-after-poison on address 0x0071126a870a...
64 # ...READ of size 2 at 0x0071126a870a thread T0 (droid.deskclock)
65 # ... #0 0x71281013b3 (/data/asan/system/lib64/libart.so+0x2263b3)
66 # ... #1 0x71280fe6b7 (/data/asan/system/lib64/libart.so+0x2236b7)
67 # "
68
69 # Adds a newline character if not present at the end of trace
70 trace = trace if trace[-1] == "\n" else trace + "\n"
71 trace_line_matches = [(match_to_int(match.group()), match.start())
72 for match in re.finditer("#[0-9]+ "
73 "|use-after-poison"
Bharadwaj Kalandhabhatta188ac6c2017-07-17 15:15:41 -070074 "|unknown-crash"
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -070075 "|READ", trace)
76 ]
77 # Finds the first index where the line number ordering isn't in sequence or
78 # returns the number of matches if it everything is in order.
79 bad_line_no = next((i - 2 for i, match in enumerate(trace_line_matches)
80 if i - 2 != match[0]), len(trace_line_matches) - 2)
81 # If the number ordering breaks after minimum stack size, then the trace is
82 # still valid.
83 if bad_line_no >= stack_min_size:
84 # Added if the trace is already clean
85 trace_line_matches.append((trace_line_matches[-1][0] + 1, len(trace)))
86 bad_match = trace_line_matches[bad_line_no + 2]
87 if prune_exact:
88 bad_match = trace_line_matches[stack_min_size + 2]
89 # Up to new-line that comes before bad line number
90 return trace[:trace.rindex("\n", 0, bad_match[1]) + 1]
91 return None
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -070092
93
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -070094def extant_directory(path_name):
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -070095 """Checks if a path is an actual directory."""
96 if not os.path.isdir(path_name):
97 dir_error = "%s is not a directory" % (path_name)
98 raise argparse.ArgumentTypeError(dir_error)
99 return path_name
100
101
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -0700102def parse_args():
103 """Parses arguments passed in."""
104 parser = argparse.ArgumentParser()
105 parser.add_argument("-d", action="store",
106 default="", dest="out_dir_name", type=extant_directory,
107 help="Output Directory")
108 parser.add_argument("-e", action="store_true",
109 default=False, dest="check_exact",
110 help="Forces each trace to be cut to have "
111 "minimum number of lines")
112 parser.add_argument("-m", action="store",
113 default=4, dest="stack_min_size", type=int,
114 help="minimum number of lines a trace should have")
115 parser.add_argument("trace_file", action="store",
116 type=argparse.FileType("r"),
117 help="File only containing lines that are related to "
118 "Sanitizer traces")
119 return parser.parse_args()
120
121
122def main():
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -0700123 """Parses arguments and cleans up traces using other functions."""
124 stack_min_size = 4
125 check_exact = False
126
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -0700127 parsed_argv = parse_args()
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -0700128 stack_min_size = parsed_argv.stack_min_size
129 check_exact = parsed_argv.check_exact
130 out_dir_name = parsed_argv.out_dir_name
131 trace_file = parsed_argv.trace_file
132
133 trace_split = trace_file.read().split(STACK_DIVIDER)
134 trace_file.close()
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -0700135 trace_clean_split = [clean_trace_if_valid(trace,
136 stack_min_size,
137 check_exact)
138 for trace in trace_split
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -0700139 ]
Bharadwaj Kalandhabhattaad47e4d2017-06-19 16:40:30 -0700140 trace_clean_split = [trace for trace in trace_clean_split
141 if trace is not None]
Bharadwaj Kalandhabhatta188ac6c2017-07-17 15:15:41 -0700142 filename = os.path.basename(trace_file.name + "_filtered")
143 outfile = os.path.join(out_dir_name, filename)
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -0700144 with open(outfile, "w") as output_file:
145 output_file.write(STACK_DIVIDER.join(trace_clean_split))
146
147 filter_percent = 100.0 - (float(len(trace_clean_split)) /
148 len(trace_split) * 100)
149 filter_amount = len(trace_split) - len(trace_clean_split)
Bharadwaj Kalandhabhatta188ac6c2017-07-17 15:15:41 -0700150 print("Filtered out %d (%f%%) of %d. %d (%f%%) remain."
151 % (filter_amount, filter_percent, len(trace_split),
152 len(trace_split) - filter_amount, 1 - filter_percent))
Bharadwaj Kalandhabhatta9e1c45d2017-06-13 08:56:51 -0700153
154
155if __name__ == "__main__":
156 main()