You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
6.3 KiB
157 lines
6.3 KiB
#!/usr/bin/env python
|
|
#
|
|
# Copyright (C) 2017 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Cleans up overlapping portions of traces provided by logcat."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import re
|
|
import os
|
|
|
|
STACK_DIVIDER = 65 * "="
|
|
|
|
|
|
def match_to_int(match):
|
|
"""Returns trace line number matches as integers for sorting.
|
|
Maps other matches to negative integers.
|
|
"""
|
|
# Hard coded string are necessary since each trace must have the address
|
|
# accessed, which is printed before trace lines.
|
|
if match == "use-after-poison" or match == "unknown-crash":
|
|
return -2
|
|
elif match == "READ":
|
|
return -1
|
|
# Cutting off non-integer part of match
|
|
return int(match[1:-1])
|
|
|
|
|
|
def clean_trace_if_valid(trace, stack_min_size, prune_exact):
|
|
"""Cleans trace if it meets a certain standard. Returns None otherwise."""
|
|
# Note: Sample input may contain "unknown-crash" instead of
|
|
# "use-after-poison"
|
|
#
|
|
# Sample input:
|
|
# trace:
|
|
# "...ERROR: AddressSanitizer: use-after-poison on address 0x0071126a870a...
|
|
# ...READ of size 2 at 0x0071126a870a thread T0 (droid.deskclock)
|
|
# ... #0 0x71281013b3 (/data/asan/system/lib64/libart.so+0x2263b3)
|
|
# ... #1 0x71280fe6b7 (/data/asan/system/lib64/libart.so+0x2236b7)
|
|
# ... #3 0x71280c22ef (/data/asan/system/lib64/libart.so+0x1e72ef)
|
|
# ... #2 0x712810a84f (/data/asan/system/lib64/libart.so+0x22f84f)"
|
|
#
|
|
# stack_min_size: 2
|
|
# prune_exact: False
|
|
#
|
|
# Sample output:
|
|
#
|
|
# "...ERROR: AddressSanitizer: use-after-poison on address 0x0071126a870a...
|
|
# ...READ of size 2 at 0x0071126a870a thread T0 (droid.deskclock)
|
|
# ... #0 0x71281013b3 (/data/asan/system/lib64/libart.so+0x2263b3)
|
|
# ... #1 0x71280fe6b7 (/data/asan/system/lib64/libart.so+0x2236b7)
|
|
# "
|
|
|
|
# Adds a newline character if not present at the end of trace
|
|
trace = trace if trace[-1] == "\n" else trace + "\n"
|
|
trace_line_matches = [(match_to_int(match.group()), match.start())
|
|
for match in re.finditer("#[0-9]+ "
|
|
"|use-after-poison"
|
|
"|unknown-crash"
|
|
"|READ", trace)
|
|
]
|
|
# Finds the first index where the line number ordering isn't in sequence or
|
|
# returns the number of matches if it everything is in order.
|
|
bad_line_no = next((i - 2 for i, match in enumerate(trace_line_matches)
|
|
if i - 2 != match[0]), len(trace_line_matches) - 2)
|
|
# If the number ordering breaks after minimum stack size, then the trace is
|
|
# still valid.
|
|
if bad_line_no >= stack_min_size:
|
|
# Added if the trace is already clean
|
|
trace_line_matches.append((trace_line_matches[-1][0] + 1, len(trace)))
|
|
bad_match = trace_line_matches[bad_line_no + 2]
|
|
if prune_exact:
|
|
bad_match = trace_line_matches[stack_min_size + 2]
|
|
# Up to new-line that comes before bad line number
|
|
return trace[:trace.rindex("\n", 0, bad_match[1]) + 1]
|
|
return None
|
|
|
|
|
|
def extant_directory(path_name):
|
|
"""Checks if a path is an actual directory."""
|
|
if not os.path.isdir(path_name):
|
|
dir_error = "%s is not a directory" % (path_name)
|
|
raise argparse.ArgumentTypeError(dir_error)
|
|
return path_name
|
|
|
|
|
|
def parse_args():
|
|
"""Parses arguments passed in."""
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("-d", action="store",
|
|
default="", dest="out_dir_name", type=extant_directory,
|
|
help="Output Directory")
|
|
parser.add_argument("-e", action="store_true",
|
|
default=False, dest="check_exact",
|
|
help="Forces each trace to be cut to have "
|
|
"minimum number of lines")
|
|
parser.add_argument("-m", action="store",
|
|
default=4, dest="stack_min_size", type=int,
|
|
help="minimum number of lines a trace should have")
|
|
parser.add_argument("trace_file", action="store",
|
|
type=argparse.FileType("r"),
|
|
help="File only containing lines that are related to "
|
|
"Sanitizer traces")
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
"""Parses arguments and cleans up traces using other functions."""
|
|
stack_min_size = 4
|
|
check_exact = False
|
|
|
|
parsed_argv = parse_args()
|
|
stack_min_size = parsed_argv.stack_min_size
|
|
check_exact = parsed_argv.check_exact
|
|
out_dir_name = parsed_argv.out_dir_name
|
|
trace_file = parsed_argv.trace_file
|
|
|
|
trace_split = trace_file.read().split(STACK_DIVIDER)
|
|
trace_file.close()
|
|
trace_clean_split = [clean_trace_if_valid(trace,
|
|
stack_min_size,
|
|
check_exact)
|
|
for trace in trace_split
|
|
]
|
|
trace_clean_split = [trace for trace in trace_clean_split
|
|
if trace is not None]
|
|
filename = os.path.basename(trace_file.name + "_filtered")
|
|
outfile = os.path.join(out_dir_name, filename)
|
|
with open(outfile, "w") as output_file:
|
|
output_file.write(STACK_DIVIDER.join(trace_clean_split))
|
|
|
|
filter_percent = 100.0 - (float(len(trace_clean_split)) /
|
|
len(trace_split) * 100)
|
|
filter_amount = len(trace_split) - len(trace_clean_split)
|
|
print("Filtered out %d (%f%%) of %d. %d (%f%%) remain."
|
|
% (filter_amount, filter_percent, len(trace_split),
|
|
len(trace_split) - filter_amount, 1 - filter_percent))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|