• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import os
7import sys
8
9_BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
10_FIND_RUNTIME_SYMBOLS_PATH = os.path.join(_BASE_PATH,
11                                          os.pardir,
12                                          'find_runtime_symbols')
13_TOOLS_LINUX_PATH = os.path.join(_BASE_PATH,
14                                 os.pardir,
15                                 'linux')
16sys.path.append(_FIND_RUNTIME_SYMBOLS_PATH)
17sys.path.append(_TOOLS_LINUX_PATH)
18
19import find_runtime_symbols
20import prepare_symbol_info
21import procfs  # pylint: disable=W0611,F0401
22
23LOGGER = logging.getLogger('dmprof')
24
25FUNCTION_SYMBOLS = find_runtime_symbols.FUNCTION_SYMBOLS
26SOURCEFILE_SYMBOLS = find_runtime_symbols.SOURCEFILE_SYMBOLS
27TYPEINFO_SYMBOLS = find_runtime_symbols.TYPEINFO_SYMBOLS
28
29
30class SymbolDataSources(object):
31  """Manages symbol data sources in a process.
32
33  The symbol data sources consist of maps (/proc/<pid>/maps), nm, readelf and
34  so on.  They are collected into a directory '|prefix|.symmap' from the binary
35  files by 'prepare()' with tools/find_runtime_symbols/prepare_symbol_info.py.
36
37  Binaries are not mandatory to profile.  The prepared data sources work in
38  place of the binary even if the binary has been overwritten with another
39  binary.
40
41  Note that loading the symbol data sources takes a long time.  They are often
42  very big.  So, the 'dmprof' profiler is designed to use 'SymbolMappingCache'
43  which caches actually used symbols.
44  """
45  def __init__(self, prefix, alternative_dirs=None):
46    self._prefix = prefix
47    self._prepared_symbol_data_sources_path = None
48    self._loaded_symbol_data_sources = None
49    self._alternative_dirs = alternative_dirs or {}
50
51  def prepare(self):
52    """Prepares symbol data sources by extracting mapping from a binary.
53
54    The prepared symbol data sources are stored in a directory.  The directory
55    name is stored in |self._prepared_symbol_data_sources_path|.
56
57    Returns:
58        True if succeeded.
59    """
60    LOGGER.info('Preparing symbol mapping...')
61    self._prepared_symbol_data_sources_path, used_tempdir = (
62        prepare_symbol_info.prepare_symbol_info(
63            self._prefix + '.maps',
64            output_dir_path=self._prefix + '.symmap',
65            alternative_dirs=self._alternative_dirs,
66            use_tempdir=True,
67            use_source_file_name=True))
68    if self._prepared_symbol_data_sources_path:
69      LOGGER.info('  Prepared symbol mapping.')
70      if used_tempdir:
71        LOGGER.warn('  Using a temporary directory for symbol mapping.')
72        LOGGER.warn('  Delete it by yourself.')
73        LOGGER.warn('  Or, move the directory by yourself to use it later.')
74      return True
75    else:
76      LOGGER.warn('  Failed to prepare symbol mapping.')
77      return False
78
79  def get(self):
80    """Returns the prepared symbol data sources.
81
82    Returns:
83        The prepared symbol data sources.  None if failed.
84    """
85    if not self._prepared_symbol_data_sources_path and not self.prepare():
86      return None
87    if not self._loaded_symbol_data_sources:
88      LOGGER.info('Loading symbol mapping...')
89      self._loaded_symbol_data_sources = (
90          find_runtime_symbols.RuntimeSymbolsInProcess.load(
91              self._prepared_symbol_data_sources_path))
92    return self._loaded_symbol_data_sources
93
94  def path(self):
95    """Returns the path of the prepared symbol data sources if possible."""
96    if not self._prepared_symbol_data_sources_path and not self.prepare():
97      return None
98    return self._prepared_symbol_data_sources_path
99
100
101class SymbolFinder(object):
102  """Finds corresponding symbols from addresses.
103
104  This class does only 'find()' symbols from a specified |address_list|.
105  It is introduced to make a finder mockable.
106  """
107  def __init__(self, symbol_type, symbol_data_sources):
108    self._symbol_type = symbol_type
109    self._symbol_data_sources = symbol_data_sources
110
111  def find(self, address_list):
112    return find_runtime_symbols.find_runtime_symbols(
113        self._symbol_type, self._symbol_data_sources.get(), address_list)
114
115
116class SymbolMappingCache(object):
117  """Caches mapping from actually used addresses to symbols.
118
119  'update()' updates the cache from the original symbol data sources via
120  'SymbolFinder'.  Symbols can be looked up by the method 'lookup()'.
121  """
122  def __init__(self):
123    self._symbol_mapping_caches = {
124        FUNCTION_SYMBOLS: {},
125        SOURCEFILE_SYMBOLS: {},
126        TYPEINFO_SYMBOLS: {},
127        }
128
129  def update(self, symbol_type, bucket_set, symbol_finder, cache_f):
130    """Updates symbol mapping cache on memory and in a symbol cache file.
131
132    It reads cached symbol mapping from a symbol cache file |cache_f| if it
133    exists.  Unresolved addresses are then resolved and added to the cache
134    both on memory and in the symbol cache file with using 'SymbolFinder'.
135
136    A cache file is formatted as follows:
137      <Address> <Symbol>
138      <Address> <Symbol>
139      <Address> <Symbol>
140      ...
141
142    Args:
143        symbol_type: A type of symbols to update.  It should be one of
144            FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS and TYPEINFO_SYMBOLS.
145        bucket_set: A BucketSet object.
146        symbol_finder: A SymbolFinder object to find symbols.
147        cache_f: A readable and writable IO object of the symbol cache file.
148    """
149    cache_f.seek(0, os.SEEK_SET)
150    self._load(cache_f, symbol_type)
151
152    unresolved_addresses = sorted(
153        address for address in bucket_set.iter_addresses(symbol_type)
154        if address not in self._symbol_mapping_caches[symbol_type])
155
156    if not unresolved_addresses:
157      LOGGER.info('No need to resolve any more addresses.')
158      return
159
160    cache_f.seek(0, os.SEEK_END)
161    LOGGER.info('Loading %d unresolved addresses.' %
162                len(unresolved_addresses))
163    symbol_dict = symbol_finder.find(unresolved_addresses)
164
165    for address, symbol in symbol_dict.iteritems():
166      stripped_symbol = symbol.strip() or '?'
167      self._symbol_mapping_caches[symbol_type][address] = stripped_symbol
168      cache_f.write('%x %s\n' % (address, stripped_symbol))
169
170  def lookup(self, symbol_type, address):
171    """Looks up a symbol for a given |address|.
172
173    Args:
174        symbol_type: A type of symbols to update.  It should be one of
175            FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS and TYPEINFO_SYMBOLS.
176        address: An integer that represents an address.
177
178    Returns:
179        A string that represents a symbol.
180    """
181    return self._symbol_mapping_caches[symbol_type].get(address)
182
183  def _load(self, cache_f, symbol_type):
184    try:
185      for line in cache_f:
186        items = line.rstrip().split(None, 1)
187        if len(items) == 1:
188          items.append('??')
189        self._symbol_mapping_caches[symbol_type][int(items[0], 16)] = items[1]
190      LOGGER.info('Loaded %d entries from symbol cache.' %
191                     len(self._symbol_mapping_caches[symbol_type]))
192    except IOError as e:
193      LOGGER.info('The symbol cache file is invalid: %s' % e)
194