예제 #1
0
class _NHeapRule(rules.Rule):
    def __init__(self, name, filters):
        super(_NHeapRule, self).__init__(name)

        # The 'stacktrace' filter can be either a string (simple case, one regex) or
        # a list of strings (complex case, see doc in the header of this file).
        stacktrace_regexs = filters.get('stacktrace', [])
        if isinstance(stacktrace_regexs, basestring):
            stacktrace_regexs = [stacktrace_regexs]
        self._stacktrace_regexs = []
        for regex in stacktrace_regexs:
            try:
                self._stacktrace_regexs.append(re.compile(regex))
            except re.error, descr:
                raise exceptions.MemoryInspectorException(
                    'Stacktrace regex error "%s" : %s' % (regex, descr))

        # The 'source_path' regex, instead, simply matches the source file path.
        self._path_regex = None
        path_regex = filters.get('source_path')
        if path_regex:
            try:
                self._path_regex = re.compile(path_regex)
            except re.error, descr:
                raise exceptions.MemoryInspectorException(
                    'Path regex error "%s" : %s' % (path_regex, descr))
예제 #2
0
    def __init__(self, backend, adb):
        super(AndroidDevice, self).__init__(backend=backend,
                                            settings=backends.Settings(
                                                AndroidDevice._SETTINGS_KEYS))
        self.adb = adb
        self._name = '%s %s' % (adb.GetProp('ro.product.model', cached=True),
                                adb.GetProp('ro.build.id', cached=True))
        self._id = adb.serial
        self._sys_stats = None
        self._last_device_stats = None
        self._sys_stats_last_update = None
        self._processes = {}  # pid (int) -> |Process|
        self._initialized = False

        # Determine the available ABIs, |_arch| will contain the primary ABI.
        # TODO(primiano): For the moment we support only one ABI per device (i.e. we
        # assume that all processes are 64 bit on 64 bit device, failing to profile
        # 32 bit ones). Dealing properly with multi-ABIs requires work on ps_ext and
        # at the moment is not an interesting use case.
        self._arch = None
        self._arch32 = None
        self._arch64 = None
        abi = adb.GetProp('ro.product.cpu.abi', cached=True)
        if abi in _SUPPORTED_64BIT_ABIS:
            self._arch = self._arch64 = _SUPPORTED_64BIT_ABIS[abi]
        elif abi in _SUPPORTED_32BIT_ABIS:
            self._arch = self._arch32 = _SUPPORTED_32BIT_ABIS[abi]
        else:
            raise exceptions.MemoryInspectorException('ABI %s not supported' %
                                                      abi)
예제 #3
0
 def IsNativeTracingEnabled(self):
   """Checks whether the libheap_profiler is preloaded in the zygote."""
   zygote_name = 'zygote64' if self._arch64 else 'zygote'
   zygote_process = [p for p in self.ListProcesses() if p.name == zygote_name]
   if not zygote_process:
     raise exceptions.MemoryInspectorException('Zygote process not found')
   zygote_pid = zygote_process[0].pid
   zygote_maps = self.adb.Shell(['cat', '/proc/%d/maps' % zygote_pid])
   return 'libheap_profiler' in zygote_maps
 def __init__(self, name, filters):
     super(_MmapRule, self).__init__(name)
     try:
         self._file_re = (re.compile(filters['mmap_file'])
                          if 'mmap_file' in filters else None)
         self._prot_re = (re.compile(filters['mmap_prot'])
                          if 'mmap_prot' in filters else None)
     except re.error, descr:
         raise exceptions.MemoryInspectorException(
             'Regex parse error "%s" : %s' % (filters, descr))
예제 #5
0
 def __init__(self, name, filters):
     super(_NHeapRule, self).__init__(name)
     stacktrace_regexs = filters.get('stacktrace', [])
     # The 'stacktrace' filter can be either a string (simple case, one regex) or
     # a list of strings (complex case, see doc in the header of this file).
     if isinstance(stacktrace_regexs, basestring):
         stacktrace_regexs = [stacktrace_regexs]
     self._stacktrace_regexs = []
     for regex in stacktrace_regexs:
         try:
             self._stacktrace_regexs.append(re.compile(regex))
         except re.error, descr:
             raise exceptions.MemoryInspectorException(
                 'Regex parse error "%s" : %s' % (regex, descr))
예제 #6
0
  def Initialize(self):
    """Starts adb root and deploys the prebuilt binaries on initialization."""
    try:
      self.adb.RestartShellAsRoot()
      self.adb.WaitForDevice()
    except adb_client.ADBClientError:
      raise exceptions.MemoryInspectorException(
          'The device must be adb root-able in order to use memory_inspector')

    # Download (from GCS) and deploy prebuilt helper binaries on the device.
    self._DeployPrebuiltOnDeviceIfNeeded(
        _MEMDUMP_PREBUILT_PATH % {'arch': self._arch}, _MEMDUMP_PATH_ON_DEVICE)
    self._DeployPrebuiltOnDeviceIfNeeded(
        _PSEXT_PREBUILT_PATH % {'arch': self._arch}, _PSEXT_PATH_ON_DEVICE)
    self._DeployPrebuiltOnDeviceIfNeeded(
        _HEAP_DUMP_PREBUILT_PATH % {'arch': self._arch},
        _HEAP_DUMP_PATH_ON_DEVICE)

    self._initialized = True
  def Initialize(self):
    """Starts adb root and deploys the prebuilt binaries on initialization."""
    try:
      self.adb.EnableRoot()
    except device_errors.CommandFailedError:
      # TODO(jbudorick): Handle this exception appropriately after interface
      # conversions are finished.
      raise exceptions.MemoryInspectorException(
          'The device must be adb root-able in order to use memory_inspector')

    # Download (from GCS) and deploy prebuilt helper binaries on the device.
    self._DeployPrebuiltOnDeviceIfNeeded(
        _MEMDUMP_PREBUILT_PATH % {'arch': self._arch}, _MEMDUMP_PATH_ON_DEVICE)
    self._DeployPrebuiltOnDeviceIfNeeded(
        _PSEXT_PREBUILT_PATH % {'arch': self._arch}, _PSEXT_PATH_ON_DEVICE)
    self._DeployPrebuiltOnDeviceIfNeeded(
        _HEAP_DUMP_PREBUILT_PATH % {'arch': self._arch},
        _HEAP_DUMP_PATH_ON_DEVICE)

    self._initialized = True
예제 #8
0
    def ExtractSymbols(self, native_heaps, sym_paths):
        """Performs symbolization. Returns a |symbol.Symbols| from |NativeHeap|s.

    This method performs the symbolization but does NOT decorate (i.e. add
    symbol/source info) to the stack frames of |native_heaps|. The heaps
    can be decorated as needed using the native_heap.SymbolizeUsingSymbolDB()
    method. Rationale: the most common use case in this application is:
    symbolize-and-store-symbols and load-symbols-and-decorate-heaps (in two
    different stages at two different times).

    Args:
      native_heaps: a collection of native_heap.NativeHeap instances.
      sym_paths: either a list of or a string of comma-separated symbol paths.
    """
        assert (all(
            isinstance(x, native_heap.NativeHeap) for x in native_heaps))
        symbols = symbol.Symbols()

        # Find addr2line in toolchain_path.
        if isinstance(sym_paths, basestring):
            sym_paths = sym_paths.split(',')
        matches = glob.glob(
            os.path.join(self.settings['toolchain_path'], '*addr2line'))
        if not matches:
            raise exceptions.MemoryInspectorException('Cannot find addr2line')
        addr2line_path = matches[0]

        # First group all the stack frames together by lib path.
        frames_by_lib = {}
        for nheap in native_heaps:
            for stack_frame in nheap.stack_frames.itervalues():
                frames = frames_by_lib.setdefault(
                    stack_frame.exec_file_rel_path, set())
                frames.add(stack_frame)

        # The symbolization process is asynchronous (but yet single-threaded). This
        # callback is invoked every time the symbol info for a stack frame is ready.
        def SymbolizeAsyncCallback(sym_info, stack_frame):
            if not sym_info.name:
                return
            sym = symbol.Symbol(name=sym_info.name,
                                source_file_path=sym_info.source_path,
                                line_number=sym_info.source_line)
            symbols.Add(stack_frame.exec_file_rel_path, stack_frame.offset,
                        sym)
            # TODO(primiano): support inline sym info (i.e. |sym_info.inlined_by|).

        # Perform the actual symbolization (ordered by lib).
        for exec_file_rel_path, frames in frames_by_lib.iteritems():
            # Look up the full path of the symbol in the sym paths.
            exec_file_name = posixpath.basename(exec_file_rel_path)
            if exec_file_rel_path.startswith('/'):
                exec_file_rel_path = exec_file_rel_path[1:]
            exec_file_abs_path = ''
            for sym_path in sym_paths:
                # First try to locate the symbol file following the full relative path
                # e.g. /host/syms/ + /system/lib/foo.so => /host/syms/system/lib/foo.so.
                exec_file_abs_path = os.path.join(sym_path, exec_file_rel_path)
                if os.path.exists(exec_file_abs_path):
                    break

                # If no luck, try looking just for the file name in the sym path,
                # e.g. /host/syms/ + (/system/lib/)foo.so => /host/syms/foo.so
                exec_file_abs_path = os.path.join(sym_path, exec_file_name)
                if os.path.exists(exec_file_abs_path):
                    break

            if not os.path.exists(exec_file_abs_path):
                continue

            symbolizer = elf_symbolizer.ELFSymbolizer(
                elf_file_path=exec_file_abs_path,
                addr2line_path=addr2line_path,
                callback=SymbolizeAsyncCallback,
                inlines=False)

            # Kick off the symbolizer and then wait that all callbacks are issued.
            for stack_frame in sorted(frames, key=lambda x: x.offset):
                symbolizer.SymbolizeAsync(stack_frame.offset, stack_frame)
            symbolizer.Join()

        return symbols
예제 #9
0
    def EnableNativeTracing(self, enabled):
        """Installs libheap_profiler in and injects it in the Zygote."""
        def WrapZygote(app_process):
            self.adb.Shell(['mv', app_process, app_process + '.real'])
            with tempfile.NamedTemporaryFile() as wrapper_file:
                wrapper_file.write(
                    '#!/system/bin/sh\n'
                    'LD_PRELOAD="libheap_profiler.so:$LD_PRELOAD" '
                    'exec %s.real "$@"\n' % app_process)
                wrapper_file.close()
                self.adb.Push(wrapper_file.name, app_process)
            self.adb.Shell(['chown', 'root.shell', app_process])
            self.adb.Shell(['chmod', '755', app_process])

        def UnwrapZygote():
            for suffix in ('', '32', '64'):
                # We don't really care if app_processX.real doesn't exists and mv fails.
                # If app_processX.real doesn't exists, either app_processX is already
                # unwrapped or it doesn't exists for the current arch.
                app_process = '/system/bin/app_process' + suffix
                self.adb.Shell(['mv', app_process + '.real', app_process])

        assert (self._initialized)
        self.adb.RemountSystemPartition()

        # Start restoring the original state in any case.
        UnwrapZygote()

        if enabled:
            # Temporarily disable SELinux (until next reboot).
            self.adb.Shell(['setenforce', '0'])

            # Wrap the Zygote startup binary (app_process) with a script which
            # LD_PRELOADs libheap_profiler and invokes the original Zygote process.
            if self._arch64:
                app_process = '/system/bin/app_process64'
                assert (self.adb.FileExists(app_process))
                self._DeployPrebuiltOnDeviceIfNeeded(
                    _LIBHEAPPROF_PREBUILT_PATH % {'arch': self._arch64},
                    '/system/lib64/' + _LIBHEAPPROF_FILE_NAME)
                WrapZygote(app_process)

            if self._arch32:
                # Path is app_process32 for Android >= L, app_process when < L.
                app_process = '/system/bin/app_process32'
                if not self.adb.FileExists(app_process):
                    app_process = '/system/bin/app_process'
                    assert (self.adb.FileExists(app_process))
                self._DeployPrebuiltOnDeviceIfNeeded(
                    _LIBHEAPPROF_PREBUILT_PATH % {'arch': self._arch32},
                    '/system/lib/' + _LIBHEAPPROF_FILE_NAME)
                WrapZygote(app_process)

        # Respawn the zygote (the device will kind of reboot at this point).
        self.adb.Shell('stop')
        self.adb.Shell('start')

        # Wait for the package manger to come back.
        for _ in xrange(10):
            found_pm = 'package:' in self.adb.Shell(['pm', 'path', 'android'])
            if found_pm:
                break
            time.sleep(3)
        if not found_pm:
            raise exceptions.MemoryInspectorException(
                'Device unresponsive (no pm)')

        # Remove the wrapper. This won't have effect until the next reboot, when
        # the profiler will be automatically disarmed.
        UnwrapZygote()

        # We can also unlink the lib files at this point. Once the Zygote has
        # started it will keep the inodes refcounted anyways through its lifetime.
        self.adb.Shell(['rm', '/system/lib*/' + _LIBHEAPPROF_FILE_NAME])