• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #!/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 
19 from __future__ import absolute_import
20 from __future__ import division
21 from __future__ import print_function
22 
23 import argparse
24 import re
25 import os
26 
27 STACK_DIVIDER = 65 * "="
28 
29 
30 def 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.
36     if match == "use-after-poison" or match == "unknown-crash":
37         return -2
38     elif match == "READ":
39         return -1
40     # Cutting off non-integer part of match
41     return int(match[1:-1])
42 
43 
44 def clean_trace_if_valid(trace, stack_min_size, prune_exact):
45     """Cleans trace if it meets a certain standard. Returns None otherwise."""
46     # Note: Sample input may contain "unknown-crash" instead of
47     # "use-after-poison"
48     #
49     # 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"
74                                                    "|unknown-crash"
75                                                    "|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
92 
93 
94 def extant_directory(path_name):
95     """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 
102 def 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 
122 def main():
123     """Parses arguments and cleans up traces using other functions."""
124     stack_min_size = 4
125     check_exact = False
126 
127     parsed_argv = parse_args()
128     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()
135     trace_clean_split = [clean_trace_if_valid(trace,
136                                               stack_min_size,
137                                               check_exact)
138                          for trace in trace_split
139                          ]
140     trace_clean_split = [trace for trace in trace_clean_split
141                          if trace is not None]
142     filename = os.path.basename(trace_file.name + "_filtered")
143     outfile = os.path.join(out_dir_name, filename)
144     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)
150     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))
153 
154 
155 if __name__ == "__main__":
156     main()
157