blob: a470546e46420bcef822293e094fa5de242c68cb [file] [log] [blame]
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import datetime
import logging
import os
import platform
import re
import subprocess
from telemetry.internal.backends.chrome import minidump_symbolizer
from telemetry.internal.results import artifact_logger
from telemetry.internal.util import local_first_binary_manager
# Directories relative to the build directory that may contain symbol binaries
# that can be dumped to symbolize a minidump.
_POSSIBLE_SYMBOL_BINARY_DIRECTORIES = [
'lib.unstripped',
os.path.join('android_clang_arm', 'lib.unstripped'),
os.path.join('android_clang_arm64', 'lib.unstripped'),
]
# Mappings from Crashpad/Breakpad processor architecture values to regular
# expressions that will match the output of running "file" on a .so compiled
# for that architecture.
# The Breakpad processor architecture values are hex representations of the
# values in MDCPUArchitecture from Breakpad's minidump_format.h.
_BREAKPAD_ARCH_TO_FILE_REGEX = {
# 32-bit ARM.
'0x5': r'.*32-bit.*ARM.*',
# 64-bit ARM.
'0xc': r'.*64-bit.*ARM.*',
}
# Line looks like " processor_architecture = 0xc ".
_PROCESSOR_ARCH_REGEX = r'\s*processor_architecture\s*\=\s*(?P<arch>\w*)\s*'
class AndroidMinidumpSymbolizer(minidump_symbolizer.MinidumpSymbolizer):
def __init__(self, dump_finder, build_dir, symbols_dir=None):
"""Class for handling all minidump symbolizing code on Android.
Args:
dump_finder: The minidump_finder.MinidumpFinder instance that is being
used to find minidumps for the test.
build_dir: The directory containing Chromium build artifacts to generate
symbols from.
symbols_dir: An optional path to a directory to store symbols for re-use.
Re-using symbols will result in faster symbolization times, but the
provided directory *must* be unique per browser binary, e.g. by
including the hash of the binary in the directory name.
"""
# Map from minidump path (string) to minidump_dump output (string).
self._minidump_dump_output = {}
# Map from minidump path (string) to the directory that should be used when
# looking for symbol binaries (string).
self._minidump_symbol_binaries_directories = {}
# We use the OS/arch of the host, not the device.
super(AndroidMinidumpSymbolizer, self).__init__(
platform.system().lower(), platform.machine(), dump_finder, build_dir,
symbols_dir=symbols_dir)
def SymbolizeMinidump(self, minidump):
if platform.system() != 'Linux':
logging.warning(
'Cannot get Android stack traces unless running on a Posix host.')
return None
if not self._build_dir:
logging.warning(
'Cannot get Android stack traces without build directory.')
return None
return super(AndroidMinidumpSymbolizer, self).SymbolizeMinidump(minidump)
def GetSymbolBinaries(self, minidump):
"""Returns a list of paths to binaries where symbols may be located.
Args:
minidump: The path to the minidump being symbolized.
"""
libraries = self._ExtractLibraryNamesFromDump(minidump)
symbol_binary_dir = self._GetSymbolBinaryDirectory(minidump, libraries)
if not symbol_binary_dir:
return []
return [os.path.join(symbol_binary_dir, lib) for lib in libraries]
def GetBreakpadPlatformOverride(self):
return 'android'
def _ExtractLibraryNamesFromDump(self, minidump):
"""Extracts library names that may contain symbols from the minidump.
This is a duplicate of the logic in Chromium's
//build/android/stacktrace/crashpad_stackwalker.py.
Returns:
A list of strings containing library names of interest for symbols.
"""
default_library_name = 'libmonochrome.so'
minidump_dump_output = self._GetMinidumpDumpOutput(minidump)
if not minidump_dump_output:
logging.warning(
'Could not get minidump_dump output, defaulting to library %s',
default_library_name)
return [default_library_name]
library_names = []
module_library_line_re = re.compile(r'[(]code_file[)]\s+= '
r'"(?P<library_name>lib[^. ]+.so)"')
in_module = False
for line in minidump_dump_output.splitlines():
line = line.lstrip().rstrip('\n')
if line == 'MDRawModule':
in_module = True
continue
if line == '':
in_module = False
continue
if in_module:
m = module_library_line_re.match(line)
if m:
library_names.append(m.group('library_name'))
if not library_names:
logging.warning(
'Could not find any library name in the dump, '
'default to: %s', default_library_name)
return [default_library_name]
return library_names
def _GetSymbolBinaryDirectory(self, minidump, libraries):
"""Gets the directory that should contain symbol binaries for |minidump|.
Args:
minidump: The path to the minidump being analyzed.
libraries: A list of library names that are within the minidump.
Returns:
A string containing the path to the directory that should contain the
symbol binaries that can be dumped to symbolize |minidump|. Returns None
if the directory is unable to be determined for some reason.
"""
if minidump in self._minidump_symbol_binaries_directories:
return self._minidump_symbol_binaries_directories[minidump]
# Get the processor architecture reported by the minidump.
arch = None
matcher = re.compile(_PROCESSOR_ARCH_REGEX)
for line in self._GetMinidumpDumpOutput(minidump).splitlines():
match = matcher.match(line)
if match:
arch = match.groupdict()['arch'].lower()
break
if not arch:
logging.error('Unable to find processor architecture for minidump %s',
minidump)
self._minidump_symbol_binaries_directories[minidump] = None
return None
if arch not in _BREAKPAD_ARCH_TO_FILE_REGEX:
logging.error(
'Unsupported processor architecture %s for minidump %s. This is '
'likely fixable by adding the correct mapping for the architecture '
'in android_minidump_symbolizer._BREAKPAD_ARCH_TO_FILE_REGEX.',
arch, minidump)
self._minidump_symbol_binaries_directories[minidump] = None
return None
# Look for a directory that contains binaries with the correct architecture.
matcher = re.compile(_BREAKPAD_ARCH_TO_FILE_REGEX[arch])
symbol_dir = None
for symbol_subdir in _POSSIBLE_SYMBOL_BINARY_DIRECTORIES:
possible_symbol_dir = os.path.join(self._build_dir, symbol_subdir)
if not os.path.exists(possible_symbol_dir):
continue
for f in os.listdir(possible_symbol_dir):
if f not in libraries:
continue
binary_path = os.path.join(possible_symbol_dir, f)
stdout = subprocess.check_output(
['file', binary_path], stderr=subprocess.STDOUT)
if matcher.match(stdout):
symbol_dir = possible_symbol_dir
break
if not symbol_dir:
logging.error(
'Unable to find suitable symbol binary directory for architecture %s.'
'This is likely fixable by adding the correct directory to '
'android_minidump_symbolizer._POSSIBLE_SYMBOL_BINARY_DIRECTORIES.',
arch)
self._minidump_symbol_binaries_directories[minidump] = symbol_dir
return symbol_dir
def _GetMinidumpDumpOutput(self, minidump):
"""Runs minidump_dump on the given minidump.
Caches the result for re-use.
Args:
minidump: The path to the minidump being analyzed.
Returns:
A string containing the output of minidump_dump, or None if it could not
be retrieved for some reason.
"""
if minidump in self._minidump_dump_output:
logging.debug('Returning cached minidump_dump output for %s', minidump)
return self._minidump_dump_output[minidump]
dumper_path = local_first_binary_manager.GetInstance().FetchPath(
'minidump_dump')
if not os.access(dumper_path, os.X_OK):
logging.warning('Cannot run minidump_dump because %s is not found.',
dumper_path)
return None
# Using subprocess.check_output with stdout/stderr mixed can result in
# errors due to log messages showing up in the minidump_dump output. So,
# use Popen and combine into a single string afterwards.
p = subprocess.Popen(
[dumper_path, minidump], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
stdout = stdout + '\n' + stderr
if p.returncode != 0:
# Dumper errors often do not affect stack walkability, just a warning.
# It's possible for the same stack to be symbolized multiple times, so
# add a timestamp suffix to prevent artifact collisions.
now = datetime.datetime.now()
suffix = now.strftime('%Y-%m-%d-%H-%M-%S')
artifact_name = 'dumper_errors/%s-%s' % (
os.path.basename(minidump), suffix)
logging.warning(
'Reading minidump failed, but likely not actually an issue. Saving '
'output to artifact %s', artifact_name)
artifact_logger.CreateArtifact(artifact_name, stdout)
if stdout:
self._minidump_dump_output[minidump] = stdout
return stdout