am a861beec: Merge "Update the stack script for apk handling."

* commit 'a861beec5ca91342c976221762ac5dfb513d09c4':
  Update the stack script for apk handling.
This commit is contained in:
Christopher Ferris
2015-09-04 18:16:20 +00:00
committed by Android Git Automerger
2 changed files with 114 additions and 11 deletions

View File

@@ -16,8 +16,11 @@
"""stack symbolizes native crash dumps."""
import os
import re
import subprocess
import symbol
import tempfile
import unittest
import example_crashes
@@ -41,11 +44,13 @@ class TraceConverter:
sanitizer_trace_line = re.compile("$a")
value_line = re.compile("$a")
code_line = re.compile("$a")
unzip_line = re.compile("\s*(\d+)\s+\S+\s+\S+\s+(\S+)")
trace_lines = []
value_lines = []
last_frame = -1
width = "{8}"
spacing = ""
apk_info = dict()
def __init__(self):
self.UpdateAbiRegexes()
@@ -87,6 +92,7 @@ class TraceConverter:
"(?P<offset>[0-9a-f]" + self.width + ")[ \t]+" # Offset (hex number given without
# 0x prefix).
"(?P<dso>[^\r\n \t]*)" # Library name.
"( \(offset (?P<so_offset>0x[0-9a-fA-F]+)\))?" # Offset into the file to find the start of the shared so.
"(?P<symbolpresent> \((?P<symbol>.*)\))?") # Is the symbol there?
# pylint: disable-msg=C6310
# Sanitizer output. This is different from debuggerd output, and it is easier to handle this as
@@ -160,17 +166,28 @@ class TraceConverter:
print
print "-----------------------------------------------------\n"
def DeleteApkTmpFiles(self):
for _, offset_list in self.apk_info.values():
for _, _, tmp_file in offset_list:
if tmp_file:
os.unlink(tmp_file)
def ConvertTrace(self, lines):
lines = map(self.CleanLine, lines)
for line in lines:
self.ProcessLine(line)
self.PrintOutput(self.trace_lines, self.value_lines)
try:
for line in lines:
self.ProcessLine(line)
self.PrintOutput(self.trace_lines, self.value_lines)
finally:
# Delete any temporary files created while processing the lines.
self.DeleteApkTmpFiles()
def MatchTraceLine(self, line):
if self.trace_line.match(line):
match = self.trace_line.match(line)
return {"frame": match.group("frame"),
"offset": match.group("offset"),
"so_offset": match.group("so_offset"),
"dso": match.group("dso"),
"symbol_present": bool(match.group("symbolpresent")),
"symbol_name": match.group("symbol")}
@@ -183,6 +200,79 @@ class TraceConverter:
"symbol_name": None}
return None
def ExtractLibFromApk(self, apk, shared_lib_name):
# Create a temporary file containing the shared library from the apk.
tmp_file = None
try:
tmp_fd, tmp_file = tempfile.mkstemp()
if subprocess.call(["unzip", "-p", apk, shared_lib_name], stdout=tmp_fd) == 0:
os.close(tmp_fd)
shared_file = tmp_file
tmp_file = None
return shared_file
finally:
if tmp_file:
os.close(tmp_fd)
os.unlink(tmp_file)
return None
def GetLibFromApk(self, apk, offset):
# Convert the string to hex.
offset = int(offset, 16)
# Check if we already have information about this offset.
if apk in self.apk_info:
apk_full_path, offset_list = self.apk_info[apk]
for current_offset, file_name, tmp_file in offset_list:
if offset <= current_offset:
if tmp_file:
return file_name, tmp_file
# This modifies the value in offset_list.
tmp_file = self.ExtractLibFromApk(apk_full_path, file_name)
if tmp_file:
return file_name, tmp_file
break
return None, None
if not "ANDROID_PRODUCT_OUT" in os.environ:
print "ANDROID_PRODUCT_OUT environment variable not set."
return None, None
out_dir = os.environ["ANDROID_PRODUCT_OUT"]
if not os.path.exists(out_dir):
print "ANDROID_PRODUCT_OUT " + out_dir + " does not exist."
return None, None
if apk.startswith("/"):
apk_full_path = out_dir + apk
else:
apk_full_path = os.path.join(out_dir, apk)
if not os.path.exists(apk_full_path):
print "Cannot find apk " + apk;
return None, None
cmd = subprocess.Popen(["unzip", "-lqq", apk_full_path], stdout=subprocess.PIPE)
current_offset = 0
file_entry = None
offset_list = []
for line in cmd.stdout:
match = self.unzip_line.match(line)
if match:
# Round the size up to a page boundary.
current_offset += (int(match.group(1), 10) + 0x1000) & ~0xfff
offset_entry = [current_offset - 1, match.group(2), None]
offset_list.append(offset_entry)
if offset < current_offset and not file_entry:
file_entry = offset_entry
# Save the information from the zip.
self.apk_info[apk] = [apk_full_path, offset_list]
if not file_entry:
return None, None
tmp_shared_lib = self.ExtractLibFromApk(apk_full_path, file_entry[1])
if tmp_shared_lib:
file_entry[2] = tmp_shared_lib
return file_entry[1], file_entry[2]
return None, None
def ProcessLine(self, line):
ret = False
process_header = self.process_info_line.search(line)
@@ -230,6 +320,7 @@ class TraceConverter:
frame = trace_line_dict["frame"]
code_addr = trace_line_dict["offset"]
area = trace_line_dict["dso"]
so_offset = trace_line_dict["so_offset"]
symbol_present = trace_line_dict["symbol_present"]
symbol_name = trace_line_dict["symbol_name"]
@@ -243,9 +334,19 @@ class TraceConverter:
if area == "<unknown>" or area == "[heap]" or area == "[stack]":
self.trace_lines.append((code_addr, "", area))
else:
# If this is an apk, it usually means that there is actually
# a shared so that was loaded directly out of it. In that case,
# extract the shared library and the name of the shared library.
lib = None
if area.endswith(".apk") and so_offset:
lib_name, lib = self.GetLibFromApk(area, so_offset)
if not lib:
lib = area
lib_name = None
# If a calls b which further calls c and c is inlined to b, we want to
# display "a -> b -> c" in the stack trace instead of just "a -> c"
info = symbol.SymbolInformation(area, code_addr)
info = symbol.SymbolInformation(lib, code_addr)
nest_count = len(info) - 1
for (source_symbol, source_location, object_symbol_with_offset) in info:
if not source_symbol:
@@ -255,6 +356,8 @@ class TraceConverter:
source_symbol = "<unknown>"
if not source_location:
source_location = area
if lib_name:
source_location += "(" + lib_name + ")"
if nest_count > 0:
nest_count = nest_count - 1
arrow = "v------>"

View File

@@ -185,7 +185,9 @@ def CallAddr2LineForSet(lib, unique_addrs):
symbols = SYMBOLS_DIR + lib
if not os.path.exists(symbols):
return None
symbols = lib
if not os.path.exists(symbols):
return None
cmd = [ToolPath("addr2line"), "--functions", "--inlines",
"--demangle", "--exe=" + symbols]
@@ -203,7 +205,7 @@ def CallAddr2LineForSet(lib, unique_addrs):
if symbol == "??":
symbol = None
location = child.stdout.readline().strip()
if location == "??:0":
if location == "??:0" or location == "??:?":
location = None
if symbol is None and location is None:
break
@@ -250,11 +252,9 @@ def CallObjdumpForSet(lib, unique_addrs):
symbols = SYMBOLS_DIR + lib
if not os.path.exists(symbols):
return None
symbols = SYMBOLS_DIR + lib
if not os.path.exists(symbols):
return None
symbols = lib
if not os.path.exists(symbols):
return None
addrs = sorted(unique_addrs)
start_addr_dec = str(StripPC(int(addrs[0], 16)))