blob: 8f8dd423add7b09d0694a05c18268fbd3076c804 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (c) 2015 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.
"""Identifies address adjustments required for native crash dumps."""
import glob
import os.path
import re
import subprocess
_BASE_APK = 'base.apk'
_LIBCHROME_SO = 'libchrome.so'
_BUILD_FINGERPRINT_RE = re.compile('.*Build fingerprint: (.*)$')
def GetTargetAndroidVersionNumber(lines):
"""Return the Android major version number from the build fingerprint.
Args:
lines: Lines read from the tombstone file, before preprocessing.
Returns:
5, 6, etc, or None if not determinable (developer build?)
"""
# For example, "Build fingerprint: 'Android/aosp_flo/flo:5.1.1/...'" is 5.
for line in lines:
m = _BUILD_FINGERPRINT_RE.match(line)
if not m:
continue
fingerprint = m.group(1)
try:
version = fingerprint.split('/')[2].split(':')[1].split('.')[0]
return int(version)
except Exception:
pass
return None
def _HasElfHeader(path):
"""Return True if the file at the given path has an ELF magic header.
Minimal check only, for 'ELF' in bytes 1 to 3 of the file. Filters out
the zero-byte false-positives such as libchromeview.so returned by glob.
Args:
path: Path to file to check.
Returns:
True or False
"""
with open(path) as stream:
elf_header = stream.read(4)
return len(elf_header) == 4 and elf_header[1:4] == 'ELF'
def _ReadElfProgramHeaders(lib):
"""Return an iterable of program headers, from 'readelf -l ...'.
Uses the platform readelf in all cases. This is somewhat lazy, but suffices
in practice because program headers in ELF files are architecture-agnostic.
Args:
lib: Library file to read.
Returns:
[readelf -l output line, ...]
"""
string = subprocess.check_output(['readelf', '-l', lib])
return string.split('\n')
def _FindMinLoadVaddr(lib):
"""Return the minimum VirtAddr field of all library LOAD segments.
Args:
lib: Library file to read.
Returns:
Min VirtAddr field for all LOAD segments, or 0 if none found.
"""
vaddrs = []
# Locate LOAD lines and find the smallest VirtAddr field, eg:
# Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
# LOAD 0x000000 0x001d6000 0x001d6000 0x20f63fc 0x20f63fc R E 0x1000
# LOAD 0x20f6970 0x022cd970 0x022cd970 0x182df8 0x1b4490 RW 0x1000
# would return 0x1d6000. Ignores all non-LOAD lines.
for line in _ReadElfProgramHeaders(lib):
elements = line.split()
if elements and elements[0] == 'LOAD':
vaddrs.append(int(elements[2], 16))
if vaddrs:
return min(vaddrs)
return 0
def GetLoadVaddrs(stripped_libs=None, stripped_libs_dir=None):
"""Return a dict of minimum VirtAddr for libraries in the given directory.
The dictionary returned may be passed to stack_core.ConvertTrace(). In
pre-M Android releases the addresses printed by debuggerd into tombstones
do not take account of non-zero vaddrs. Here we collect this information,
so that we can use it later to correct such debuggerd tombstones.
Args:
stripped_libs_dir: Path to directory containing apk's stripped libraries.
Returns:
{'libchrome.so': 12345, ...}
"""
if not stripped_libs:
stripped_libs = []
if stripped_libs_dir:
stripped_libs.extend(glob.glob(os.path.join(stripped_libs_dir, '*.so')))
libs = [l for l in stripped_libs if _HasElfHeader(l)]
load_vaddrs = {}
for lib in libs:
min_vaddr = _FindMinLoadVaddr(lib)
if min_vaddr:
# Store with the library basename as the key. This is because once on
# the device its path may not fully match its place in the APK staging
# directory
load_vaddrs[os.path.basename(lib)] = min_vaddr
# Direct load from APK causes debuggerd to tag trace lines as if from the
# file .../base.apk. So if we encounter a libchrome.so with packed
# relocations, replicate this as base.apk so that later adjustment code
# finds the appropriate adjustment.
if _LIBCHROME_SO in load_vaddrs:
load_vaddrs[_BASE_APK] = load_vaddrs[_LIBCHROME_SO]
return load_vaddrs