#!/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