Index: tools/deep_memory_profiler/lib/symbol.py |
diff --git a/tools/deep_memory_profiler/lib/symbol.py b/tools/deep_memory_profiler/lib/symbol.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..897d4098d5687630215f3bbc78a0095e3add5ed5 |
--- /dev/null |
+++ b/tools/deep_memory_profiler/lib/symbol.py |
@@ -0,0 +1,189 @@ |
+# Copyright 2013 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 logging |
+import os |
+import sys |
+ |
+_BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
+_FIND_RUNTIME_SYMBOLS_PATH = os.path.join(_BASE_PATH, |
+ os.pardir, |
+ 'find_runtime_symbols') |
+sys.path.append(_FIND_RUNTIME_SYMBOLS_PATH) |
+ |
+import find_runtime_symbols |
+import prepare_symbol_info |
+import proc_maps # pylint: disable=W0611 |
+ |
+LOGGER = logging.getLogger('dmprof') |
+ |
+FUNCTION_SYMBOLS = find_runtime_symbols.FUNCTION_SYMBOLS |
+SOURCEFILE_SYMBOLS = find_runtime_symbols.SOURCEFILE_SYMBOLS |
+TYPEINFO_SYMBOLS = find_runtime_symbols.TYPEINFO_SYMBOLS |
+ |
+ |
+class SymbolDataSources(object): |
+ """Manages symbol data sources in a process. |
+ |
+ The symbol data sources consist of maps (/proc/<pid>/maps), nm, readelf and |
+ so on. They are collected into a directory '|prefix|.symmap' from the binary |
+ files by 'prepare()' with tools/find_runtime_symbols/prepare_symbol_info.py. |
+ |
+ Binaries are not mandatory to profile. The prepared data sources work in |
+ place of the binary even if the binary has been overwritten with another |
+ binary. |
+ |
+ Note that loading the symbol data sources takes a long time. They are often |
+ very big. So, the 'dmprof' profiler is designed to use 'SymbolMappingCache' |
+ which caches actually used symbols. |
+ """ |
+ def __init__(self, prefix, alternative_dirs=None): |
+ self._prefix = prefix |
+ self._prepared_symbol_data_sources_path = None |
+ self._loaded_symbol_data_sources = None |
+ self._alternative_dirs = alternative_dirs or {} |
+ |
+ def prepare(self): |
+ """Prepares symbol data sources by extracting mapping from a binary. |
+ |
+ The prepared symbol data sources are stored in a directory. The directory |
+ name is stored in |self._prepared_symbol_data_sources_path|. |
+ |
+ Returns: |
+ True if succeeded. |
+ """ |
+ LOGGER.info('Preparing symbol mapping...') |
+ self._prepared_symbol_data_sources_path, used_tempdir = ( |
+ prepare_symbol_info.prepare_symbol_info( |
+ self._prefix + '.maps', |
+ output_dir_path=self._prefix + '.symmap', |
+ alternative_dirs=self._alternative_dirs, |
+ use_tempdir=True, |
+ use_source_file_name=True)) |
+ if self._prepared_symbol_data_sources_path: |
+ LOGGER.info(' Prepared symbol mapping.') |
+ if used_tempdir: |
+ LOGGER.warn(' Using a temporary directory for symbol mapping.') |
+ LOGGER.warn(' Delete it by yourself.') |
+ LOGGER.warn(' Or, move the directory by yourself to use it later.') |
+ return True |
+ else: |
+ LOGGER.warn(' Failed to prepare symbol mapping.') |
+ return False |
+ |
+ def get(self): |
+ """Returns the prepared symbol data sources. |
+ |
+ Returns: |
+ The prepared symbol data sources. None if failed. |
+ """ |
+ if not self._prepared_symbol_data_sources_path and not self.prepare(): |
+ return None |
+ if not self._loaded_symbol_data_sources: |
+ LOGGER.info('Loading symbol mapping...') |
+ self._loaded_symbol_data_sources = ( |
+ find_runtime_symbols.RuntimeSymbolsInProcess.load( |
+ self._prepared_symbol_data_sources_path)) |
+ return self._loaded_symbol_data_sources |
+ |
+ def path(self): |
+ """Returns the path of the prepared symbol data sources if possible.""" |
+ if not self._prepared_symbol_data_sources_path and not self.prepare(): |
+ return None |
+ return self._prepared_symbol_data_sources_path |
+ |
+ |
+class SymbolFinder(object): |
+ """Finds corresponding symbols from addresses. |
+ |
+ This class does only 'find()' symbols from a specified |address_list|. |
+ It is introduced to make a finder mockable. |
+ """ |
+ def __init__(self, symbol_type, symbol_data_sources): |
+ self._symbol_type = symbol_type |
+ self._symbol_data_sources = symbol_data_sources |
+ |
+ def find(self, address_list): |
+ return find_runtime_symbols.find_runtime_symbols( |
+ self._symbol_type, self._symbol_data_sources.get(), address_list) |
+ |
+ |
+class SymbolMappingCache(object): |
+ """Caches mapping from actually used addresses to symbols. |
+ |
+ 'update()' updates the cache from the original symbol data sources via |
+ 'SymbolFinder'. Symbols can be looked up by the method 'lookup()'. |
+ """ |
+ def __init__(self): |
+ self._symbol_mapping_caches = { |
+ FUNCTION_SYMBOLS: {}, |
+ SOURCEFILE_SYMBOLS: {}, |
+ TYPEINFO_SYMBOLS: {}, |
+ } |
+ |
+ def update(self, symbol_type, bucket_set, symbol_finder, cache_f): |
+ """Updates symbol mapping cache on memory and in a symbol cache file. |
+ |
+ It reads cached symbol mapping from a symbol cache file |cache_f| if it |
+ exists. Unresolved addresses are then resolved and added to the cache |
+ both on memory and in the symbol cache file with using 'SymbolFinder'. |
+ |
+ A cache file is formatted as follows: |
+ <Address> <Symbol> |
+ <Address> <Symbol> |
+ <Address> <Symbol> |
+ ... |
+ |
+ Args: |
+ symbol_type: A type of symbols to update. It should be one of |
+ FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS and TYPEINFO_SYMBOLS. |
+ bucket_set: A BucketSet object. |
+ symbol_finder: A SymbolFinder object to find symbols. |
+ cache_f: A readable and writable IO object of the symbol cache file. |
+ """ |
+ cache_f.seek(0, os.SEEK_SET) |
+ self._load(cache_f, symbol_type) |
+ |
+ unresolved_addresses = sorted( |
+ address for address in bucket_set.iter_addresses(symbol_type) |
+ if address not in self._symbol_mapping_caches[symbol_type]) |
+ |
+ if not unresolved_addresses: |
+ LOGGER.info('No need to resolve any more addresses.') |
+ return |
+ |
+ cache_f.seek(0, os.SEEK_END) |
+ LOGGER.info('Loading %d unresolved addresses.' % |
+ len(unresolved_addresses)) |
+ symbol_dict = symbol_finder.find(unresolved_addresses) |
+ |
+ for address, symbol in symbol_dict.iteritems(): |
+ stripped_symbol = symbol.strip() or '?' |
+ self._symbol_mapping_caches[symbol_type][address] = stripped_symbol |
+ cache_f.write('%x %s\n' % (address, stripped_symbol)) |
+ |
+ def lookup(self, symbol_type, address): |
+ """Looks up a symbol for a given |address|. |
+ |
+ Args: |
+ symbol_type: A type of symbols to update. It should be one of |
+ FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS and TYPEINFO_SYMBOLS. |
+ address: An integer that represents an address. |
+ |
+ Returns: |
+ A string that represents a symbol. |
+ """ |
+ return self._symbol_mapping_caches[symbol_type].get(address) |
+ |
+ def _load(self, cache_f, symbol_type): |
+ try: |
+ for line in cache_f: |
+ items = line.rstrip().split(None, 1) |
+ if len(items) == 1: |
+ items.append('??') |
+ self._symbol_mapping_caches[symbol_type][int(items[0], 16)] = items[1] |
+ LOGGER.info('Loaded %d entries from symbol cache.' % |
+ len(self._symbol_mapping_caches[symbol_type])) |
+ except IOError as e: |
+ LOGGER.info('The symbol cache file is invalid: %s' % e) |