Update the stack script for apk handling.
The dlopen of a shared library within an apk is not handled by the stack tool. Modify to understand and search through the apk to find the shared library associated with the offset. Then unzip that shared library to use as the target of addr2line. Change-Id: I404302f68dff45c35a1dc2a55547f42ea04f02bf
This commit is contained in:
@@ -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)
|
||||
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------>"
|
||||
|
||||
@@ -184,6 +184,8 @@ def CallAddr2LineForSet(lib, unique_addrs):
|
||||
return None
|
||||
|
||||
symbols = SYMBOLS_DIR + lib
|
||||
if not os.path.exists(symbols):
|
||||
symbols = lib
|
||||
if not os.path.exists(symbols):
|
||||
return None
|
||||
|
||||
@@ -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,9 +252,7 @@ def CallObjdumpForSet(lib, unique_addrs):
|
||||
|
||||
symbols = SYMBOLS_DIR + lib
|
||||
if not os.path.exists(symbols):
|
||||
return None
|
||||
|
||||
symbols = SYMBOLS_DIR + lib
|
||||
symbols = lib
|
||||
if not os.path.exists(symbols):
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user