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