Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / tools / memory_inspector / memory_inspector / backends / android / android_backend.py
index c49a504..4bae745 100644 (file)
@@ -8,19 +8,26 @@ See core/backends.py for more docs.
 """
 
 import datetime
+import glob
 import hashlib
 import json
 import os
+import posixpath
 
 from memory_inspector import constants
 from memory_inspector.backends import prebuilts_fetcher
 from memory_inspector.backends.android import dumpheap_native_parser
 from memory_inspector.backends.android import memdump_parser
 from memory_inspector.core import backends
+from memory_inspector.core import exceptions
+from memory_inspector.core import native_heap
+from memory_inspector.core import symbol
 
-# The embedder of this module (unittest runner, web server, ...) is expected
-# to add the <CHROME_SRC>/build/android to the PYTHONPATH for pylib.
-from pylib import android_commands  # pylint: disable=F0401
+# The memory_inspector/__init__ module will add the <CHROME_SRC>/build/android
+# deps to the PYTHONPATH for pylib.
+from pylib import android_commands
+from pylib.device import device_utils
+from pylib.symbols import elf_symbolizer
 
 
 _MEMDUMP_PREBUILT_PATH = os.path.join(constants.PROJECT_SRC,
@@ -56,10 +63,90 @@ class AndroidBackend(backends.Backend):
       device = self._devices.get(device_id)
       if not device:
         device = AndroidDevice(
-          self, android_commands.AndroidCommands(device_id))
+          self, device_utils.DeviceUtils(device_id))
         self._devices[device_id] = device
       yield device
 
+  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
+
   @property
   def name(self):
     return 'Android'
@@ -71,13 +158,13 @@ class AndroidDevice(backends.Device):
   _SETTINGS_KEYS = {
       'native_symbol_paths': 'Comma-separated list of native libs search path'}
 
-  def __init__(self, backend, adb):
+  def __init__(self, backend, underlying_device):
     super(AndroidDevice, self).__init__(
         backend=backend,
         settings=backends.Settings(AndroidDevice._SETTINGS_KEYS))
-    self.adb = adb
-    self._id = adb.GetDevice()
-    self._name = adb.GetProductModel()
+    self.underlying_device = underlying_device
+    self._id = underlying_device.old_interface.GetDevice()
+    self._name = underlying_device.old_interface.GetProductModel()
     self._sys_stats = None
     self._last_device_stats = None
     self._sys_stats_last_update = None
@@ -86,7 +173,7 @@ class AndroidDevice(backends.Device):
 
   def Initialize(self):
     """Starts adb root and deploys the prebuilt binaries on initialization."""
-    self.adb.EnableAdbRoot()
+    self.underlying_device.old_interface.EnableAdbRoot()
 
     # Download (from GCS) and deploy prebuilt helper binaries on the device.
     self._DeployPrebuiltOnDeviceIfNeeded(_MEMDUMP_PREBUILT_PATH,
@@ -97,16 +184,18 @@ class AndroidDevice(backends.Device):
 
   def IsNativeTracingEnabled(self):
     """Checks for the libc.debug.malloc system property."""
-    return bool(self.adb.system_properties[_DLMALLOC_DEBUG_SYSPROP])
+    return bool(self.underlying_device.old_interface.system_properties[
+        _DLMALLOC_DEBUG_SYSPROP])
 
   def EnableNativeTracing(self, enabled):
     """Enables libc.debug.malloc and restarts the shell."""
     assert(self._initialized)
     prop_value = '1' if enabled else ''
-    self.adb.system_properties[_DLMALLOC_DEBUG_SYSPROP] = prop_value
+    self.underlying_device.old_interface.system_properties[
+        _DLMALLOC_DEBUG_SYSPROP] = prop_value
     assert(self.IsNativeTracingEnabled())
     # The libc.debug property takes effect only after restarting the Zygote.
-    self.adb.RestartShell()
+    self.underlying_device.old_interface.RestartShell()
 
   def ListProcesses(self):
     """Returns a sequence of |AndroidProcess|."""
@@ -162,7 +251,9 @@ class AndroidDevice(backends.Device):
         datetime.datetime.now() - self._sys_stats_last_update <= max_ttl):
       return self._sys_stats
 
-    dump_out = '\n'.join(self.adb.RunShellCommand(_PSEXT_PATH_ON_DEVICE))
+    dump_out = '\n'.join(
+        self.underlying_device.old_interface.RunShellCommand(
+            _PSEXT_PATH_ON_DEVICE))
     stats = json.loads(dump_out)
     assert(all([x in stats for x in ['cpu', 'processes', 'time', 'mem']])), (
         'ps_ext returned a malformed JSON dictionary.')
@@ -189,11 +280,13 @@ class AndroidDevice(backends.Device):
     prebuilts_fetcher.GetIfChanged(local_path)
     with open(local_path, 'rb') as f:
       local_hash = hashlib.md5(f.read()).hexdigest()
-    device_md5_out = self.adb.RunShellCommand('md5 "%s"' % path_on_device)
+    device_md5_out = self.underlying_device.old_interface.RunShellCommand(
+        'md5 "%s"' % path_on_device)
     if local_hash in device_md5_out:
       return
-    self.adb.Adb().Push(local_path, path_on_device)
-    self.adb.RunShellCommand('chmod 755 "%s"' % path_on_device)
+    self.underlying_device.old_interface.Adb().Push(local_path, path_on_device)
+    self.underlying_device.old_interface.RunShellCommand(
+        'chmod 755 "%s"' % path_on_device)
 
   @property
   def name(self):
@@ -216,7 +309,7 @@ class AndroidProcess(backends.Process):
   def DumpMemoryMaps(self):
     """Grabs and parses memory maps through memdump."""
     cmd = '%s %d' % (_MEMDUMP_PATH_ON_DEVICE, self.pid)
-    dump_out = self.device.adb.RunShellCommand(cmd)
+    dump_out = self.device.underlying_device.old_interface.RunShellCommand(cmd)
     return memdump_parser.Parse(dump_out)
 
   def DumpNativeHeap(self):
@@ -224,12 +317,14 @@ class AndroidProcess(backends.Process):
     # TODO(primiano): grab also mmap bt (depends on pending framework change).
     dump_file_path = _DUMPHEAP_OUT_FILE_PATH % self.pid
     cmd = 'am dumpheap -n %d %s' % (self.pid, dump_file_path)
-    self.device.adb.RunShellCommand(cmd)
+    self.device.underlying_device.old_interface.RunShellCommand(cmd)
     # TODO(primiano): Some pre-KK versions of Android might need a sleep here
     # as, IIRC, 'am dumpheap' did not wait for the dump to be completed before
     # returning. Double check this and either add a sleep or remove this TODO.
-    dump_out = self.device.adb.GetFileContents(dump_file_path)
-    self.device.adb.RunShellCommand('rm %s' % dump_file_path)
+    dump_out = self.device.underlying_device.old_interface.GetFileContents(
+        dump_file_path)
+    self.device.underlying_device.old_interface.RunShellCommand(
+        'rm %s' % dump_file_path)
     return dumpheap_native_parser.Parse(dump_out)
 
   def GetStats(self):