1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
|
# Copyright 2018 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 bisect
import collections
import logging
import os
import re
from pylib.constants import host_paths
from pylib.symbols import elf_symbolizer
def _AndroidAbiToCpuArch(android_abi):
"""Return the Chromium CPU architecture name for a given Android ABI."""
_ARCH_MAP = {
'armeabi': 'arm',
'armeabi-v7a': 'arm',
'arm64-v8a': 'arm64',
'x86_64': 'x64',
}
return _ARCH_MAP.get(android_abi, android_abi)
def _HexAddressRegexpFor(android_abi):
"""Return a regexp matching hexadecimal addresses for a given Android ABI."""
if android_abi in ['x86_64', 'arm64-v8a', 'mips64']:
width = 16
else:
width = 8
return '[0-9a-f]{%d}' % width
class HostLibraryFinder(object):
"""Translate device library path to matching host unstripped library path.
Usage is the following:
1) Create instance.
2) Call AddSearchDir() once or more times to add host directory path to
look for unstripped native libraries.
3) Call Find(device_libpath) repeatedly to translate a device-specific
library path into the corresponding host path to the unstripped
version.
"""
def __init__(self):
"""Initialize instance."""
self._search_dirs = []
self._lib_map = {} # Map of library name to host file paths.
def AddSearchDir(self, lib_dir):
"""Add a directory to the search path for host native shared libraries.
Args:
lib_dir: host path containing native libraries.
"""
if not os.path.exists(lib_dir):
logging.warning('Ignoring missing host library directory: %s', lib_dir)
return
if not os.path.isdir(lib_dir):
logging.warning('Ignoring invalid host library directory: %s', lib_dir)
return
self._search_dirs.append(lib_dir)
self._lib_map = {} # Reset the map.
def Find(self, device_libpath):
"""Find the host file path matching a specific device library path.
Args:
device_libpath: device-specific file path to library or executable.
Returns:
host file path to the unstripped version of the library, or None.
"""
host_lib_path = None
lib_name = os.path.basename(device_libpath)
host_lib_path = self._lib_map.get(lib_name)
if not host_lib_path:
for search_dir in self._search_dirs:
lib_path = os.path.join(search_dir, lib_name)
if os.path.exists(lib_path):
host_lib_path = lib_path
break
if not host_lib_path:
logging.debug('Could not find host library for: %s', lib_name)
self._lib_map[lib_name] = host_lib_path
return host_lib_path
class SymbolResolver(object):
"""A base class for objets that can symbolize library (path, offset)
pairs into symbol information strings. Usage is the following:
1) Create new instance (by calling the constructor of a derived
class, since this is only the base one).
2) Call SetAndroidAbi() before any call to FindSymbolInfo() in order
to set the Android CPU ABI used for symbolization.
3) Before the first call to FindSymbolInfo(), one can call
AddLibraryOffset(), or AddLibraryOffsets() to record a set of offsets
that you will want to symbolize later through FindSymbolInfo(). Doing
so allows some SymbolResolver derived classes to work faster (e.g. the
one that invokes the 'addr2line' program, since the latter works faster
if the offsets provided as inputs are sorted in increasing order).
3) Call FindSymbolInfo(path, offset) to return the corresponding
symbol information string, or None if this doesn't correspond
to anything the instance can handle.
Note that whether the path is specific to the device or to the
host depends on the derived class implementation.
"""
def __init__(self):
self._android_abi = None
self._lib_offsets_map = collections.defaultdict(set)
def SetAndroidAbi(self, android_abi):
"""Set the Android ABI value for this instance.
Calling this function before FindSymbolInfo() is required by some
derived class implementations.
Args:
android_abi: Native Android CPU ABI name (e.g. 'armeabi-v7a').
Raises:
Exception if the ABI was already set with a different value.
"""
if self._android_abi and self._android_abi != android_abi:
raise Exception('Cannot reset Android ABI to new value %s, already set '
'to %s' % (android_abi, self._android_abi))
self._android_abi = android_abi
def AddLibraryOffset(self, lib_path, offset):
"""Associate a single offset to a given device library.
This must be called before FindSymbolInfo(), otherwise its input arguments
will be ignored.
Args:
lib_path: A library path.
offset: An integer offset within the corresponding library that will be
symbolized by future calls to FindSymbolInfo.
"""
self._lib_offsets_map[lib_path].add(offset)
def AddLibraryOffsets(self, lib_path, lib_offsets):
"""Associate a set of wanted offsets to a given device library.
This must be called before FindSymbolInfo(), otherwise its input arguments
will be ignored.
Args:
lib_path: A library path.
lib_offsets: An iterable of integer offsets within the corresponding
library that will be symbolized by future calls to FindSymbolInfo.
"""
self._lib_offsets_map[lib_path].update(lib_offsets)
# pylint: disable=unused-argument,no-self-use
def FindSymbolInfo(self, lib_path, lib_offset):
"""Symbolize a device library path and offset.
Args:
lib_path: Library path (device or host specific, depending on the
derived class implementation).
lib_offset: Integer offset within the library.
Returns:
Corresponding symbol information string, or None.
"""
# The base implementation cannot symbolize anything.
return None
# pylint: enable=unused-argument,no-self-use
class ElfSymbolResolver(SymbolResolver):
"""A SymbolResolver that can symbolize host path + offset values using
an elf_symbolizer.ELFSymbolizer instance.
"""
def __init__(self, addr2line_path_for_tests=None):
super(ElfSymbolResolver, self).__init__()
self._addr2line_path = addr2line_path_for_tests
# Used to cache one ELFSymbolizer instance per library path.
self._elf_symbolizer_cache = {}
# Used to cache FindSymbolInfo() results. Maps host library paths
# to (offset -> symbol info string) dictionaries.
self._symbol_info_cache = collections.defaultdict(dict)
self._allow_symbolizer = True
def _CreateSymbolizerFor(self, host_path):
"""Create the ELFSymbolizer instance associated with a given lib path."""
addr2line_path = self._addr2line_path
if not addr2line_path:
if not self._android_abi:
raise Exception(
'Android CPU ABI must be set before calling FindSymbolInfo!')
cpu_arch = _AndroidAbiToCpuArch(self._android_abi)
self._addr2line_path = host_paths.ToolPath('addr2line', cpu_arch)
return elf_symbolizer.ELFSymbolizer(
elf_file_path=host_path, addr2line_path=self._addr2line_path,
callback=ElfSymbolResolver._Callback, inlines=True)
def DisallowSymbolizerForTesting(self):
"""Disallow FindSymbolInfo() from using a symbolizer.
This is used during unit-testing to ensure that the offsets that were
recorded via AddLibraryOffset()/AddLibraryOffsets() are properly
symbolized, but not anything else.
"""
self._allow_symbolizer = False
def FindSymbolInfo(self, host_path, offset):
"""Override SymbolResolver.FindSymbolInfo.
Args:
host_path: Host-specific path to the native shared library.
offset: Integer offset within the native library.
Returns:
A symbol info string, or None.
"""
offset_map = self._symbol_info_cache[host_path]
symbol_info = offset_map.get(offset)
if symbol_info:
return symbol_info
# Create symbolizer on demand.
symbolizer = self._elf_symbolizer_cache.get(host_path)
if not symbolizer:
symbolizer = self._CreateSymbolizerFor(host_path)
self._elf_symbolizer_cache[host_path] = symbolizer
# If there are pre-recorded offsets for this path, symbolize them now.
offsets = self._lib_offsets_map.get(host_path)
if offsets:
offset_map = {}
for pre_offset in offsets:
symbolizer.SymbolizeAsync(
pre_offset, callback_arg=(offset_map, pre_offset))
symbolizer.WaitForIdle()
self._symbol_info_cache[host_path] = offset_map
symbol_info = offset_map.get(offset)
if symbol_info:
return symbol_info
if not self._allow_symbolizer:
return None
# Symbolize single offset. Slower if addresses are not provided in
# increasing order to addr2line.
symbolizer.SymbolizeAsync(offset,
callback_arg=(offset_map, offset))
symbolizer.WaitForIdle()
return offset_map.get(offset)
@staticmethod
def _Callback(sym_info, callback_arg):
offset_map, offset = callback_arg
offset_map[offset] = str(sym_info)
class DeviceSymbolResolver(SymbolResolver):
"""A SymbolResolver instance that accepts device-specific path.
Usage is the following:
1) Create new instance, passing a parent SymbolResolver instance that
accepts host-specific paths, and a HostLibraryFinder instance.
2) Optional: call AddApkOffsets() to add offsets from within an APK
that contains uncompressed native shared libraries.
3) Use it as any SymbolResolver instance.
"""
def __init__(self, host_resolver, host_lib_finder):
"""Initialize instance.
Args:
host_resolver: A parent SymbolResolver instance that will be used
to resolve symbols from host library paths.
host_lib_finder: A HostLibraryFinder instance used to locate
unstripped libraries on the host.
"""
super(DeviceSymbolResolver, self).__init__()
self._host_lib_finder = host_lib_finder
self._bad_device_lib_paths = set()
self._host_resolver = host_resolver
def SetAndroidAbi(self, android_abi):
super(DeviceSymbolResolver, self).SetAndroidAbi(android_abi)
self._host_resolver.SetAndroidAbi(android_abi)
def AddLibraryOffsets(self, device_lib_path, lib_offsets):
"""Associate a set of wanted offsets to a given device library.
This must be called before FindSymbolInfo(), otherwise its input arguments
will be ignored.
Args:
device_lib_path: A device-specific library path.
lib_offsets: An iterable of integer offsets within the corresponding
library that will be symbolized by future calls to FindSymbolInfo.
want to symbolize.
"""
if device_lib_path in self._bad_device_lib_paths:
return
host_lib_path = self._host_lib_finder.Find(device_lib_path)
if not host_lib_path:
# NOTE: self._bad_device_lib_paths is only used to only print this
# warning once per bad library.
logging.warning('Could not find host library matching device path: %s',
device_lib_path)
self._bad_device_lib_paths.add(device_lib_path)
return
self._host_resolver.AddLibraryOffsets(host_lib_path, lib_offsets)
def AddApkOffsets(self, device_apk_path, apk_offsets, apk_translator):
"""Associate a set of wanted offsets to a given device APK path.
This converts the APK-relative offsets into offsets relative to the
uncompressed libraries it contains, then calls AddLibraryOffsets()
for each one of the libraries.
Must be called before FindSymbolInfo() as well, otherwise input arguments
will be ignored.
Args:
device_apk_path: Device-specific APK path.
apk_offsets: Iterable of offsets within the APK file.
apk_translator: An ApkLibraryPathTranslator instance used to extract
library paths from the APK.
"""
libraries_map = collections.defaultdict(set)
for offset in apk_offsets:
lib_path, lib_offset = apk_translator.TranslatePath(device_apk_path,
offset)
libraries_map[lib_path].add(lib_offset)
for lib_path, lib_offsets in libraries_map.items():
self.AddLibraryOffsets(lib_path, lib_offsets)
def FindSymbolInfo(self, device_path, offset):
"""Overrides SymbolResolver.FindSymbolInfo.
Args:
device_path: Device-specific library path (e.g.
'/data/app/com.example.app-1/lib/x86/libfoo.so')
offset: Offset in device library path.
Returns:
Corresponding symbol information string, or None.
"""
host_path = self._host_lib_finder.Find(device_path)
if not host_path:
return None
return self._host_resolver.FindSymbolInfo(host_path, offset)
class MemoryMap(object):
"""Models the memory map of a given process. Usage is:
1) Create new instance, passing the Android ABI.
2) Call TranslateLine() whenever you want to detect and translate any
memory map input line.
3) Otherwise, it is possible to parse the whole memory map input with
ParseLines(), then call FindSectionForAddress() repeatedly in order
to translate a memory address into the corresponding mapping and
file information tuple (e.g. to symbolize stack entries).
"""
# A named tuple describing interesting memory map line items.
# Fields:
# addr_start: Mapping start address in memory.
# file_offset: Corresponding file offset.
# file_size: Corresponding mapping size in bytes.
# file_path: Input file path.
# match: Corresponding regular expression match object.
LineTuple = collections.namedtuple('MemoryMapLineTuple',
'addr_start,file_offset,file_size,'
'file_path, match')
# A name tuple describing a memory map section.
# Fields:
# address: Memory address.
# size: Size in bytes in memory
# offset: Starting file offset.
# path: Input file path.
SectionTuple = collections.namedtuple('MemoryMapSection',
'address,size,offset,path')
def __init__(self, android_abi):
"""Initializes instance.
Args:
android_abi: Android CPU ABI name (e.g. 'armeabi-v7a')
"""
hex_addr = _HexAddressRegexpFor(android_abi)
# pylint: disable=line-too-long
# A regular expression used to match memory map entries which look like:
# b278c000-b2790fff r-- 4fda000 5000 /data/app/com.google.android.apps.chrome-2/base.apk
# pylint: enable=line-too-long
self._re_map_section = re.compile(
r'\s*(?P<addr_start>' + hex_addr + r')-(?P<addr_end>' + hex_addr + ')' +
r'\s+' +
r'(?P<perm>...)\s+' +
r'(?P<file_offset>[0-9a-f]+)\s+' +
r'(?P<file_size>[0-9a-f]+)\s*' +
r'(?P<file_path>[^ \t]+)?')
self._addr_map = [] # Sorted list of (address, size, path, offset) tuples.
self._sorted_addresses = [] # Sorted list of address fields in _addr_map.
self._in_section = False
def TranslateLine(self, line, apk_path_translator):
"""Try to translate a memory map input line, if detected.
This only takes care of converting mapped APK file path and offsets
into a corresponding uncompressed native library file path + new offsets,
e.g. '..... <offset> <size> /data/.../base.apk' gets
translated into '.... <new-offset> <size> /data/.../base.apk!lib/libfoo.so'
This function should always work, even if ParseLines() was not called
previously.
Args:
line: Input memory map / tombstone line.
apk_translator: An ApkLibraryPathTranslator instance, used to map
APK offsets into uncompressed native libraries + new offsets.
Returns:
Translated memory map line, if relevant, or unchanged input line
otherwise.
"""
t = self._ParseLine(line.rstrip())
if not t:
return line
new_path, new_offset = apk_path_translator.TranslatePath(
t.file_path, t.file_offset)
if new_path == t.file_path:
return line
pos = t.match.start('file_path')
return '%s%s (offset 0x%x)%s' % (line[0:pos], new_path, new_offset,
line[t.match.end('file_path'):])
def ParseLines(self, input_lines, in_section=False):
"""Parse a list of input lines and extract the APK memory map out of it.
Args:
input_lines: list, or iterable, of input lines.
in_section: Optional. If true, considers that the input lines are
already part of the memory map. Otherwise, wait until the start of
the section appears in the input before trying to record data.
Returns:
True iff APK-related memory map entries were found. False otherwise.
"""
addr_list = [] # list of (address, size, file_path, file_offset) tuples.
self._in_section = in_section
for line in input_lines:
t = self._ParseLine(line.rstrip())
if not t:
continue
addr_list.append(t)
self._addr_map = sorted(addr_list, key=lambda x: x.addr_start)
self._sorted_addresses = [e.addr_start for e in self._addr_map]
return bool(self._addr_map)
def _ParseLine(self, line):
"""Used internally to recognized memory map input lines.
Args:
line: Input logcat or tomstone line.
Returns:
A LineTuple instance on success, or None on failure.
"""
if not self._in_section:
self._in_section = line.startswith('memory map:')
return None
m = self._re_map_section.match(line)
if not m:
self._in_section = False # End of memory map section
return None
# Only accept .apk and .so files that are not from the system partitions.
file_path = m.group('file_path')
if not file_path:
return None
if file_path.startswith('/system') or file_path.startswith('/vendor'):
return None
if not (file_path.endswith('.apk') or file_path.endswith('.so')):
return None
addr_start = int(m.group('addr_start'), 16)
file_offset = int(m.group('file_offset'), 16)
file_size = int(m.group('file_size'), 16)
return self.LineTuple(addr_start, file_offset, file_size, file_path, m)
def Dump(self):
"""Print memory map for debugging."""
print('MEMORY MAP [')
for t in self._addr_map:
print('[%08x-%08x %08x %08x %s]' %
(t.addr_start, t.addr_start + t.file_size, t.file_size,
t.file_offset, t.file_path))
print('] MEMORY MAP')
def FindSectionForAddress(self, addr):
"""Find the map section corresponding to a specific memory address.
Call this method only after using ParseLines() was called to extract
relevant information from the memory map.
Args:
addr: Memory address
Returns:
A SectionTuple instance on success, or None on failure.
"""
pos = bisect.bisect_right(self._sorted_addresses, addr)
if pos > 0:
# All values in [0,pos) are <= addr, just ensure that the last
# one contains the address as well.
entry = self._addr_map[pos - 1]
if entry.addr_start + entry.file_size > addr:
return self.SectionTuple(entry.addr_start, entry.file_size,
entry.file_offset, entry.file_path)
return None
class BacktraceTranslator(object):
"""Translates backtrace-related lines in a tombstone or crash report.
Usage is the following:
1) Create new instance with appropriate arguments.
2) If the tombstone / logcat input is available, one can call
FindLibraryOffsets() in order to detect which library offsets
will need to be symbolized during a future parse. Doing so helps
speed up the ELF symbolizer.
3) For each tombstone/logcat input line, call TranslateLine() to
try to detect and symbolize backtrace lines.
"""
# A named tuple for relevant input backtrace lines.
# Fields:
# rel_pc: Instruction pointer, relative to offset in library start.
# location: Library or APK file path.
# offset: Load base of executable code in library or apk file path.
# match: The corresponding regular expression match object.
# Note:
# The actual instruction pointer always matches the position at
# |offset + rel_pc| in |location|.
LineTuple = collections.namedtuple('BacktraceLineTuple',
'rel_pc,location,offset,match')
def __init__(self, android_abi, apk_translator):
"""Initialize instance.
Args:
android_abi: Android CPU ABI name (e.g. 'armeabi-v7a').
apk_translator: ApkLibraryPathTranslator instance used to convert
mapped APK file offsets into uncompressed library file paths with
new offsets.
"""
hex_addr = _HexAddressRegexpFor(android_abi)
# A regular expression used to match backtrace lines.
self._re_backtrace = re.compile(
r'.*#(?P<frame>[0-9]{2})\s+' +
r'(..)\s+' +
r'(?P<rel_pc>' + hex_addr + r')\s+' +
r'(?P<location>[^ \t]+)' +
r'(\s+\(offset 0x(?P<offset>[0-9a-f]+)\))?')
# In certain cases, offset will be provided as <location>+0x<offset>
# instead of <location> (offset 0x<offset>). This is a regexp to detect
# this.
self._re_location_offset = re.compile(
r'.*\+0x(?P<offset>[0-9a-f]+)$')
self._apk_translator = apk_translator
self._in_section = False
def _ParseLine(self, line):
"""Used internally to detect and decompose backtrace input lines.
Args:
line: input tombstone line.
Returns:
A LineTuple instance on success, None on failure.
"""
if not self._in_section:
self._in_section = line.startswith('backtrace:')
return None
line = line.rstrip()
m = self._re_backtrace.match(line)
if not m:
self._in_section = False
return None
location = m.group('location')
offset = m.group('offset')
if not offset:
m2 = self._re_location_offset.match(location)
if m2:
offset = m2.group('offset')
location = location[0:m2.start('offset') - 3]
if not offset:
return None
offset = int(offset, 16)
rel_pc = int(m.group('rel_pc'), 16)
# Two cases to consider here:
#
# * If this is a library file directly mapped in memory, then |rel_pc|
# if the direct offset within the library, and doesn't need any kind
# of adjustement.
#
# * If this is a library mapped directly from an .apk file, then
# |rel_pc| is the offset in the APK, and |offset| happens to be the
# load base of the corresponding library.
#
if location.endswith('.so'):
# For a native library directly mapped from the file system,
return self.LineTuple(rel_pc, location, offset, m)
if location.endswith('.apk'):
# For a native library inside an memory-mapped APK file,
new_location, new_offset = self._apk_translator.TranslatePath(
location, offset)
return self.LineTuple(rel_pc, new_location, new_offset, m)
# Ignore anything else (e.g. .oat or .odex files).
return None
def FindLibraryOffsets(self, input_lines, in_section=False):
"""Parse a tombstone's backtrace section and find all library offsets in it.
Args:
input_lines: List or iterables of intput tombstone lines.
in_section: Optional. If True, considers that the stack section has
already started.
Returns:
A dictionary mapping device library paths to sets of offsets within
then.
"""
self._in_section = in_section
result = collections.defaultdict(set)
for line in input_lines:
t = self._ParseLine(line)
if not t:
continue
result[t.location].add(t.offset + t.rel_pc)
return result
def TranslateLine(self, line, symbol_resolver):
"""Symbolize backtrace line if recognized.
Args:
line: input backtrace line.
symbol_resolver: symbol resolver instance to use. This method will
call its FindSymbolInfo(device_lib_path, lib_offset) method to
convert offsets into symbol informations strings.
Returns:
Translated line (unchanged if not recognized as a back trace).
"""
t = self._ParseLine(line)
if not t:
return line
symbol_info = symbol_resolver.FindSymbolInfo(t.location,
t.offset + t.rel_pc)
if not symbol_info:
symbol_info = 'offset 0x%x' % t.offset
pos = t.match.start('location')
pos2 = t.match.end('offset') + 1
if pos2 <= 0:
pos2 = t.match.end('location')
return '%s%s (%s)%s' % (line[:pos], t.location, symbol_info, line[pos2:])
class StackTranslator(object):
"""Translates stack-related lines in a tombstone or crash report."""
# A named tuple describing relevant stack input lines.
# Fields:
# address: Address as it appears in the stack.
# lib_path: Library path where |address| is mapped.
# lib_offset: Library load base offset. for |lib_path|.
# match: Corresponding regular expression match object.
LineTuple = collections.namedtuple('StackLineTuple',
'address, lib_path, lib_offset, match')
def __init__(self, android_abi, memory_map, apk_translator):
"""Initialize instance."""
hex_addr = _HexAddressRegexpFor(android_abi)
# pylint: disable=line-too-long
# A regular expression used to recognize stack entries like:
#
# #05 bf89a180 bf89a1e4 [stack]
# bf89a1c8 a0c01c51 /data/app/com.google.android.apps.chrome-2/base.apk
# bf89a080 00000000
# ........ ........
# pylint: enable=line-too-long
self._re_stack_line = re.compile(
r'\s+(?P<frame_number>#[0-9]+)?\s*' +
r'(?P<stack_addr>' + hex_addr + r')\s+' +
r'(?P<stack_value>' + hex_addr + r')' +
r'(\s+(?P<location>[^ \t]+))?')
self._re_stack_abbrev = re.compile(r'\s+[.]+\s+[.]+')
self._memory_map = memory_map
self._apk_translator = apk_translator
self._in_section = False
def _ParseLine(self, line):
"""Check a given input line for a relevant _re_stack_line match.
Args:
line: input tombstone line.
Returns:
A LineTuple instance on success, None on failure.
"""
line = line.rstrip()
if not self._in_section:
self._in_section = line.startswith('stack:')
return None
m = self._re_stack_line.match(line)
if not m:
if not self._re_stack_abbrev.match(line):
self._in_section = False
return None
location = m.group('location')
if not location:
return None
if not location.endswith('.apk') and not location.endswith('.so'):
return None
addr = int(m.group('stack_value'), 16)
t = self._memory_map.FindSectionForAddress(addr)
if t is None:
return None
lib_path = t.path
lib_offset = t.offset + (addr - t.address)
if lib_path.endswith('.apk'):
lib_path, lib_offset = self._apk_translator.TranslatePath(
lib_path, lib_offset)
return self.LineTuple(addr, lib_path, lib_offset, m)
def FindLibraryOffsets(self, input_lines, in_section=False):
"""Parse a tombstone's stack section and find all library offsets in it.
Args:
input_lines: List or iterables of intput tombstone lines.
in_section: Optional. If True, considers that the stack section has
already started.
Returns:
A dictionary mapping device library paths to sets of offsets within
then.
"""
result = collections.defaultdict(set)
self._in_section = in_section
for line in input_lines:
t = self._ParseLine(line)
if t:
result[t.lib_path].add(t.lib_offset)
return result
def TranslateLine(self, line, symbol_resolver=None):
"""Try to translate a line of the stack dump."""
t = self._ParseLine(line)
if not t:
return line
symbol_info = symbol_resolver.FindSymbolInfo(t.lib_path, t.lib_offset)
if not symbol_info:
return line
pos = t.match.start('location')
pos2 = t.match.end('location')
return '%s%s (%s)%s' % (line[:pos], t.lib_path, symbol_info, line[pos2:])
|