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))
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)
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))
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))
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
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
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])